# Webhooks overview

> Get pushed signed HTTPS notifications when texts arrive, calls end, summaries are ready and IVR flows run — instead of polling.

Source: https://dev.ody.co/webhooks/overview

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](https://dev.ody.co/api/rate-limits).

Deliveries follow the [Standard Webhooks](https://www.standardwebhooks.com) 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**](https://dev.ody.co/webhooks/signatures) — A public `https://` route that accepts `POST` with a JSON body. Keep the **raw** body for signature verification.
- [**2. Register it**](https://dev.ody.co/api/webhook-endpoints#create-an-endpoint) — `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**](https://dev.ody.co/webhooks/signatures) — Check `webhook-signature` against the raw body. Reject anything that fails or is older than 5 minutes.
- [**4. Send a test**](https://dev.ody.co/api/webhook-endpoints#send-a-test-delivery) — `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](https://dev.ody.co/webhooks/events).
- `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`](https://dev.ody.co/api/conversations#retrieve-a-conversation) 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`](https://dev.ody.co/api/webhook-endpoints#list-deliveries) | Shows every attempt's status code or error |

## What's next

- [Event types](https://dev.ody.co/webhooks/events): every event and its `data` fields.
- [Verifying signatures](https://dev.ody.co/webhooks/signatures): Node, Python and the raw algorithm.
- [Retries and delivery](https://dev.ody.co/webhooks/retries): timeouts, the retry schedule and debugging.
