Sending events to R2 Pipeline

R2 Pipeline runs automated customer communication for your office. Your business management software stays the source of truth: when a customer is created, an appointment is booked, a sale is written, or an install is completed, your system tells R2 Pipeline, and R2 Pipeline routes that event into the right rep's account. When the customer came through a referring partner, the same event moves the partner's referral along in their portal.

Getting a key

Your office admin creates API keys from the office dashboard and hands you one. A key starts withr2k_ and is shown once. Keep it in your system's secret store, never in a URL or a shared document. The admin can revoke a key at any time and issue a new one.

The one rule: unique event ids

Every event you send carries an external_event_id that is unique for that event in your system. R2 Pipeline stores each id once. If your system retries, or a network hiccup makes you unsure whether a call landed, send it again with the same id: the first call answers 202, every later one answers 200 with the same event id, and nothing is duplicated.

Retries and errors

Money is always whole cents, and dates and times are ISO 8601. A date is 2026-09-10, a time is 2026-09-10T20:15:00Z or with an offset.

Endpoint

POST https://partners.r2pipeline.com/api/v1/events

Headers: Authorization: Bearer r2k_... and Content-Type: application/json. The machine-readable contract is at /api/v1/openapi.json.

Envelope, the same for every event

FieldTypeRequiredNotes
event_type"contact.created"yes
external_event_idstringyesUnique per event in your system. Retries with the same id are stored once.
occurred_atstring (date-time)yesWhen it happened, ISO 8601 with timezone.
rep_emailstring (email)yesThe rep's login email in your system. Routes the event to their account.

contact.created

A new customer record exists in your system. No referral is attached.

FieldTypeRequiredNotes
data.external_idstringyesYour system's id for this customer. Required on every event.
data.first_namestringyes
data.last_namestringyes
data.emailstring (email)no
data.phonestringnoE.164, for example +15125550142.
data.address1stringno
data.citystringno
data.statestringno
data.postal_codestringno
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "contact.created",
  "external_event_id": "evt-20260904-000120",
  "occurred_at": "2026-09-04T14:30:00Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88210",
    "first_name": "Alex",
    "last_name": "Rivera",
    "email": "alex@example.com",
    "phone": "+15125550100"
  }
}'

referral.created

A new customer came in through a referring partner. Include the partner's code or email.

FieldTypeRequiredNotes
data.external_idstringyesYour system's id for this customer. Required on every event.
data.first_namestringyes
data.last_namestringyes
data.emailstring (email)no
data.phonestringnoE.164, for example +15125550142.
data.address1stringno
data.citystringno
data.statestringno
data.postal_codestringno
data.partner_codestringnoThe referring partner's code from their portal. When the office sends referral links to its own page, the code arrives there as the ref query parameter; report it here so the partner is credited.
data.partner_emailstring (email)noThe referring partner's email, when the code is unknown.
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "referral.created",
  "external_event_id": "evt-20260904-000123",
  "occurred_at": "2026-09-04T15:04:05Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88213",
    "first_name": "Jane",
    "last_name": "Smith",
    "email": "jane@example.com",
    "phone": "+15125550142",
    "partner_code": "K7Q2M9AB"
  }
}'

sale.resulted

An appointment resulted in a sale.

FieldTypeRequiredNotes
data.external_idstringyes
data.sale_amount_centsintegeryesWhole cents. 1250000 is $12,500.00.
data.sale_datestring (date)yes
data.sale_type"new" | "repeat" | "referral" | "revisit"yes
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "sale.resulted",
  "external_event_id": "evt-20260910-000301",
  "occurred_at": "2026-09-10T20:15:00Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88213",
    "sale_amount_cents": 1250000,
    "sale_date": "2026-09-10",
    "sale_type": "referral"
  }
}'

sale.canceled

A previously reported sale was canceled.

FieldTypeRequiredNotes
data.external_idstringyes
data.canceled_datestring (date)yes
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "sale.canceled",
  "external_event_id": "evt-20260912-000340",
  "occurred_at": "2026-09-12T16:00:00Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88213",
    "canceled_date": "2026-09-12"
  }
}'

appointment.scheduled

A consultation, measure, or service appointment was booked.

FieldTypeRequiredNotes
data.external_idstringyes
data.appointment_type"consult" | "measure" | "service"yes
data.scheduled_forstring (date-time)yes
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "appointment.scheduled",
  "external_event_id": "evt-20260905-000150",
  "occurred_at": "2026-09-05T13:00:00Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88213",
    "appointment_type": "consult",
    "scheduled_for": "2026-09-09T17:00:00-04:00"
  }
}'

install.scheduled

The installation date was set.

FieldTypeRequiredNotes
data.external_idstringyes
data.install_datestring (date)yes
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "install.scheduled",
  "external_event_id": "evt-20260915-000410",
  "occurred_at": "2026-09-15T18:00:00Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88213",
    "install_date": "2026-10-02"
  }
}'

install.completed

The installation is finished.

FieldTypeRequiredNotes
data.external_idstringyes
data.completed_datestring (date)yes
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "install.completed",
  "external_event_id": "evt-20261002-000512",
  "occurred_at": "2026-10-02T21:30:00Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88213",
    "completed_date": "2026-10-02"
  }
}'

referral.paid

The office paid the referring partner for this customer.

FieldTypeRequiredNotes
data.external_idstringyes
data.paid_datestring (date)yes
data.amount_centsintegerno
curl -X POST https://partners.r2pipeline.com/api/v1/events \
  -H "Authorization: Bearer r2k_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "referral.paid",
  "external_event_id": "evt-20261015-000600",
  "occurred_at": "2026-10-15T15:00:00Z",
  "rep_email": "rep.a@demo-office.example",
  "data": {
    "external_id": "cust-88213",
    "paid_date": "2026-10-15",
    "amount_cents": 50000
  }
}'

Receiving updates

Your office admin can add webhooks on the dashboard. For each event you subscribe to, R2 Pipeline sends a POST to your URL within a minute. Two event types exist: referral.status_changed fires on every status move and carries previous_status; payout.created fires when the office records a payout. The payout object is present on payout.created and on a status change to paid. The Send test button on the dashboard sends test.ping with only the id, event type, time, and office slug.

POST <your webhook url>
X-R2-Timestamp: 1788000000
X-R2-Signature: sha256=<hex HMAC-SHA256 over "<timestamp>.<raw body>" using your webhook secret>
Content-Type: application/json

{
  "id": "<delivery uuid>",
  "event_type": "referral.status_changed",
  "occurred_at": "2026-09-10T14:03:00Z",
  "office_slug": "demo-office",
  "referral": {
    "id": "<referral uuid>",
    "external_id": "cust-88213",
    "status": "paid",
    "previous_status": "installed",
    "partner_code": "K7Q2M9AB",
    "rep_email": "rep.a@demo-office.example"
  },
  "payout": {
    "amount_cents": 50000,
    "sequence_number": 1,
    "marked_at": "2026-09-10T14:03:00Z"
  }
}

Verifying the signature

Read the raw request body as text before parsing it. Join the value of the X-R2-Timestamp header, a period, and the raw body. Compute HMAC-SHA256 of that string with your webhook secret, hex encoded, and compare it in constant time to the X-R2-Signature header after its sha256= prefix. Reject anything older than five minutes by the timestamp to stop replays.

// Node
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, timestamp, signature, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
  return expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
# Python
import hmac, hashlib

def verify(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Retries

Answer with any 2xx status within ten seconds. Anything else, or a timeout, is retried after 1, 5, 15, and 30 minutes. After the fourth failed attempt the delivery is marked failed and shows on the dashboard, where the admin can retry it. Deliveries can arrive more than once if your server answered slowly; use the delivery id to ignore repeats.

Questions

Your office admin is the first stop. For anything on the R2 Pipeline side, write to support@am.r2pipeline.com and include the event id from the response.