OdyDocs

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

Text
 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 accepts POST with a JSON body. Keep the raw body for signature verification.
  • 2. Register it — POST /v1/api/webhooks with the URL and the events you want. Save the whsec_… secret: it's returned only once.
  • 3. Verify every delivery — Check webhook-signature against the raw body. Reject anything that fails or is older than 5 minutes.
  • 4. Send a test — POST /v1/api/webhooks/:id/test sends a signed ping right away so you can confirm the whole path.

#The event payload

Every delivery is a JSON object with the same envelope:

JSON
{
  "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"
  }
}
  • id string — Unique event ID. Same value as the webhook-id header. Deduplicate on it: a retry re-sends the same ID.
  • apiVersion string — The payload contract version (currently 2026-06-01).
  • createdAt timestamp — When the event happened.
  • type string — One of the event types.
  • data object — References, not content: IDs such as conversationId and activityId, plus a few event-specific fields. See each type for its fields.
Why IDs instead of the full message?

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

DoWhy
Return 2xx within 10 seconds, then process asynchronouslySlower responses count as failures and are retried
Deduplicate on idRetries and network blips can deliver the same event twice
Don't depend on orderEvents are delivered concurrently. Use createdAt and fetch current state
Verify signatures and timestampsAnyone can POST to a public URL
Subscribe only to what you handleLess noise, fewer wasted requests. ["*"] also covers future event types
Watch GET /webhooks/:id/deliveriesShows every attempt's status code or error

#What's next

Last updated Wed Oct 14 2026 00:00:00 GMT+0000 (Coordinated Universal Time)Something wrong on this page? Tell us