Webhooks overview
Get pushed signed HTTPS notifications when texts arrive, calls end, summaries are ready and IVR flows run — instead of polling.
Webhooks let Ody push events to your server the moment they happen: a customer texts in, a call ends, an AI summary is ready. Polling is slower, and every poll counts against your rate limit.
Deliveries follow the Standard Webhooks specification (the same scheme Svix uses), so off-the-shelf verification libraries work.
#How it works
Customer texts your number
│
▼
Ody records the message ──► event message.received
│
▼
POST https://your-server.com/ody/webhooks
webhook-id / webhook-timestamp / webhook-signature
{ "id": "evt_…", "type": "message.received", "data": { "conversationId": "…", "activityId": "…" } }
│
▼
You verify the signature, return 2xx fast, then fetch the details:
GET /v1/api/conversations/:conversationId#Set up an endpoint
- 1. Expose an HTTPS URL — A public
https://route that acceptsPOSTwith a JSON body. Keep the raw body for signature verification. - 2. Register it —
POST /v1/api/webhookswith the URL and the events you want. Save thewhsec_…secret: it's returned only once. - 3. Verify every delivery — Check
webhook-signatureagainst the raw body. Reject anything that fails or is older than 5 minutes. - 4. Send a test —
POST /v1/api/webhooks/:id/testsends a signedpingright away so you can confirm the whole path.
#The event payload
Every delivery is a JSON object with the same envelope:
{
"id": "evt_4f9b2c0d8e1a4b7c9d2e3f4a5b6c7d8e",
"apiVersion": "2026-06-01",
"createdAt": "2026-10-14T18:05:42.311Z",
"type": "message.received",
"data": {
"numberE164": "+15125550100",
"conversationId": "c4f1a2b3-5d6e-4f70-8a9b-0c1d2e3f4a5b",
"inboxId": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"activityId": "a1b2c3d4-e5f6-4789-8abc-def012345678",
"contactId": "8b1d6f3e-2c4a-4e1b-9f0d-7a5c3e2b1a90"
}
}idstring — Unique event ID. Same value as thewebhook-idheader. Deduplicate on it: a retry re-sends the same ID.apiVersionstring — The payload contract version (currently2026-06-01).createdAttimestamp — When the event happened.typestring — One of the event types.dataobject — References, not content: IDs such asconversationIdandactivityId, plus a few event-specific fields. See each type for its fields.
Payloads carry references, so message text, transcripts and recordings never sit in a third party's logs, and a stale retry can never overwrite fresher data. Fetch the current state with GET /conversations/:id when you need it. It's one request, and it always returns the latest.
#Best practices
| Do | Why |
|---|---|
Return 2xx within 10 seconds, then process asynchronously | Slower responses count as failures and are retried |
Deduplicate on id | Retries and network blips can deliver the same event twice |
| Don't depend on order | Events are delivered concurrently. Use createdAt and fetch current state |
| Verify signatures and timestamps | Anyone can POST to a public URL |
| Subscribe only to what you handle | Less noise, fewer wasted requests. ["*"] also covers future event types |
Watch GET /webhooks/:id/deliveries | Shows every attempt's status code or error |
#What's next
- Event types: every event and its
datafields. - Verifying signatures: Node, Python and the raw algorithm.
- Retries and delivery: timeouts, the retry schedule and debugging.