# Webhooks

> Signed contact-center and dialer webhooks - call.answered, call.completed, disposition.submitted, recording.ready - with retries, delivery logs and replay.

Source: https://edesy.in/docs/contact-center/api/webhooks

Edesy sends signed HTTPS webhooks so your CRM can record contact-center outcomes. For agent-dialer calls you get `call.answered`, `call.completed`, `disposition.submitted` and `recording.ready`, each carrying your own `external_id`. Every request is signed with HMAC-SHA256 and retried up to 6 attempts with backoff, and each delivery is logged with replay.

## Facts

| Item | Detail |
|---|---|
| Subscribe | `POST /v1/webhooks/subscriptions` |
| Events | `call.answered`, `call.completed`, `disposition.submitted`, `recording.ready`, `call.missed` |
| Filter | `event_types` on the subscription; empty means all events |
| Signature | `X-Edesy-Signature: sha256=<hex HMAC-SHA256 of the raw body>` |
| Timeout | 5 seconds per attempt |
| Retries | 6 attempts; backoff 30 s, 1 m, 2 m, 4 m, 8 m |
| Success | Any 2xx response |
| Tools | Test event, delivery list, replay one or many deliveries |
| Contact-center log | *Settings > Webhooks* in ccaas.edesy.in, or `/v1/ccaas/webhook-logs` |

## Envelope

```json
{
  "event": "disposition.submitted",
  "timestamp": "2026-10-28T08:20:31Z",
  "data": {
    "call_sid": "c0a8...",
    "external_id": "crm-lead-1042",
    "campaign_id": "5d2e...",
    "contact_id": "91ab...",
    "to": "+919876543210",
    "direction": "outbound",
    "agent": { "id": "8e1f...", "number": "1001", "name": "Agent name" },
    "disposition": { "id": "3c7d...", "name": "Not interested", "is_terminal": true, "notes": "Already bought elsewhere" },
    "custom_fields": { "plan": "gold" }
  }
}
```

All event types use the same `data` shape. Fields an event does not use are left out. `call.completed` adds `status` (`completed`, `busy`, `no-answer`, `failed`), `duration_sec`, `sip_status`, `sip_reason` and `answered_by` (when AMD ran). `recording.ready` adds `recording_url`.

## Events

| Event | When |
|---|---|
| `call.answered` | A dialer lead picks up |
| `call.completed` | A dialer call ends, whatever the outcome |
| `disposition.submitted` | An agent submits a wrap-up code on a dialer call |
| `recording.ready` | A dialer call's recording is stored and playable |
| `call.missed` | A number set to the missed-call target declines a call |

## How to receive webhooks

1. **Create a subscription** with your HTTPS URL and a secret.
2. **Send a test** with `POST /v1/webhooks/subscriptions/{id}/test`; you receive a `webhook.test` event.
3. **Verify the signature** on every request (below).
4. **Return 2xx quickly.** Do heavy work after responding.
5. **Use `external_id`** to update your own record without storing Edesy IDs.
6. **Check deliveries** at `GET /v1/webhooks/subscriptions/{id}/deliveries` and replay failures with `POST .../deliveries/replay` or `.../deliveries/{delivery_id}/replay`.

```bash
curl -X POST https://voice-api.edesy.in/v1/webhooks/subscriptions \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://crm.example.com/edesy", "secret": "your_secret", "event_types": ["disposition.submitted", "call.completed"]}'
```

## Verify the signature (Node.js)

```js
import crypto from 'crypto';

function isValid(rawBody, header, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return header && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
```

Compute the HMAC over the raw request body, before any JSON parsing.

## FAQ

### Do inbound queue calls send webhooks?
The events above are for agent-dialer calls (and `call.missed` for missed-call numbers). For inbound queue history, use [reports](https://edesy.in/docs/contact-center/supervisors/reports) and the agent call APIs.

### What if my endpoint is down?
Each delivery is retried up to 6 attempts with growing gaps. After that it is marked exhausted; replay it from the delivery log.

### Can I receive only some events?
Yes. Set `event_types` on the subscription.

### Are webhooks sent to private IP addresses?
No. Targets must be public HTTPS endpoints.

### Can I have more than one subscription?
Yes. Each subscription gets its own events and deliveries.

### Why does the signature not match?
Make sure you hash the raw bytes of the body with the same secret, and compare the full `sha256=...` string.
