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
- 202 means stored. 200 means already stored. Both are success.
- 400 names the first invalid field. Fix the payload; do not retry as-is.
- 401 means the key is missing, wrong, or revoked. Check with your office admin.
- 413 means the body is over 64 KB. Events are small; trim any extra fields.
- Anything in the 500 range is on our side. Retry with the same id after a minute.
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
| Field | Type | Required | Notes |
|---|---|---|---|
| event_type | "contact.created" | yes | |
| external_event_id | string | yes | Unique per event in your system. Retries with the same id are stored once. |
| occurred_at | string (date-time) | yes | When it happened, ISO 8601 with timezone. |
| rep_email | string (email) | yes | The 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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | Your system's id for this customer. Required on every event. |
| data.first_name | string | yes | |
| data.last_name | string | yes | |
| data.email | string (email) | no | |
| data.phone | string | no | E.164, for example +15125550142. |
| data.address1 | string | no | |
| data.city | string | no | |
| data.state | string | no | |
| data.postal_code | string | no |
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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | Your system's id for this customer. Required on every event. |
| data.first_name | string | yes | |
| data.last_name | string | yes | |
| data.email | string (email) | no | |
| data.phone | string | no | E.164, for example +15125550142. |
| data.address1 | string | no | |
| data.city | string | no | |
| data.state | string | no | |
| data.postal_code | string | no | |
| data.partner_code | string | no | The 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_email | string (email) | no | The 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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | |
| data.sale_amount_cents | integer | yes | Whole cents. 1250000 is $12,500.00. |
| data.sale_date | string (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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | |
| data.canceled_date | string (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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | |
| data.appointment_type | "consult" | "measure" | "service" | yes | |
| data.scheduled_for | string (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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | |
| data.install_date | string (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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | |
| data.completed_date | string (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.
| Field | Type | Required | Notes |
|---|---|---|---|
| data.external_id | string | yes | |
| data.paid_date | string (date) | yes | |
| data.amount_cents | integer | no |
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.