Webhooks
Webhooks push call events to your server as they happen — no polling required.
You register an endpoint, Rindee POSTs a signed JSON payload to it for each matching
event, and you verify the signature before trusting it.
Subscribe
Section titled “Subscribe”Create a webhook endpoint (requires the webhooks capability):
curl -X POST https://api.rindee.dev/v1/webhook-endpoints \ -H "Authorization: Bearer rk_live_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "Call events", "url": "https://example.com/hooks/rindee", "secret": "whsec_choose_a_long_random_string", "outcomes": [] }'url— your HTTPS endpoint. Must start withhttp://orhttps://.secret— a secret you choose; used to sign every delivery so you can verify authenticity. Store it on your server. The API never returns it back (responses expose onlyhas_secret: true).outcomes— filter which call outcomes deliver. An empty list means all.
Event payload
Section titled “Event payload”Each delivery is a JSON body with a stable envelope plus call fields:
{ "event": "call.completed", "call_id": "0b9b…", "org_id": "7c1a…", "status": "completed", "sequence": 7, "occurred_at": "2026-06-29T12:34:56Z", "phone_number": "+15551234567", "direction": "outbound", "outcome": "positive", "end_reason": "completed", "duration_seconds": 142, "started_at": "2026-06-29T12:32:34Z", "ended_at": "2026-06-29T12:34:56Z", "agent_id": "a1b2…", "contact_id": "c3d4…", "campaign_id": null}event ranges over the call lifecycle (e.g. call.queued … call.completed). Process
events in sequence order — it carries causal ordering and never disagrees with the
event timeline. Deliveries can arrive out of order or more than once, so make your handler
idempotent (key on call_id + sequence).
Delivery headers
Section titled “Delivery headers”Every request includes these reserved headers (they can’t be overridden by custom headers):
| Header | Value |
|---|---|
Content-Type |
application/json |
X-Rindee-Delivery-Id |
A UUID unique to this delivery attempt. |
X-Rindee-Signature |
sha256=<hex> — HMAC-SHA256 of the raw body, keyed by your secret. |
Verifying the signature
Section titled “Verifying the signature”Compute HMAC-SHA256 over the raw request body (the exact bytes received, before any
JSON parsing) using your endpoint’s secret, then compare it to the hex digest after
sha256= using a constant-time comparison.
import hashlib, hmac
def verify(raw_body: bytes, header: str, secret: str) -> bool: expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() received = header.removeprefix("sha256=") return hmac.compare_digest(expected, received)import crypto from "node:crypto";
function verify(rawBody, header, secret) { const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); const received = header.replace(/^sha256=/, ""); const a = Buffer.from(expected), b = Buffer.from(received); return a.length === b.length && crypto.timingSafeEqual(a, b);}Reject any request whose signature doesn’t match. Respond 2xx once you’ve accepted the
event; non-2xx responses are retried.
Customizing the payload
Section titled “Customizing the payload”Per endpoint you can reshape what’s sent (each capped at 20 entries):
field_aliases— rename payload keys, e.g.{ "phone_number": "caller_phone" }.extra_payload— add static keys to every delivery.custom_headers— add HTTP headers (reserved headers above always win).
The signature is computed over the final, customized body — so verify against the raw bytes you actually receive.
Deliveries & retries
Section titled “Deliveries & retries”Inspect delivery history and retry status:
curl https://api.rindee.dev/v1/webhook-deliveries \ -H "Authorization: Bearer rk_live_your_key_here"Failed deliveries are retried automatically. See the API reference for the full delivery schema.