# Webhook Subscriptions

> Register workspace webhook endpoints with POST /v1/webhooks/subscriptions, choose event types, set a signing secret, send a test event and read deliveries.

Source: https://edesy.in/docs/voice-api/webhooks/subscriptions

A subscription is an HTTPS endpoint registered on your workspace that receives Voice API events. You choose which `event_types` it gets (or all), set an optional signing secret, and every delivery is logged with its response code so you can debug and replay. Manage subscriptions on the Webhooks page of [voice-app.edesy.in](https://voice-app.edesy.in) or with the API below.

## Endpoints

| Method | Path | Description |
|---|---|---|
| `GET` | `/v1/webhooks/subscriptions` | List |
| `POST` | `/v1/webhooks/subscriptions` | Create |
| `PATCH` | `/v1/webhooks/subscriptions/{id}` | Update URL, secret, event types or active flag |
| `DELETE` | `/v1/webhooks/subscriptions/{id}` | Delete |
| `POST` | `/v1/webhooks/subscriptions/{id}/test` | Send a `webhook.test` event and return your endpoint's status code |
| `GET` | `/v1/webhooks/subscriptions/{id}/deliveries` | Delivery log (`limit`, default 50) |
| `POST` | `/v1/webhooks/subscriptions/{id}/deliveries/{delivery_id}/replay` | Re-send one delivery |
| `POST` | `/v1/webhooks/subscriptions/{id}/deliveries/replay` | Re-send many. See [Retries and Replay](https://edesy.in/docs/voice-api/webhooks/retries-and-replay) |

## Create

```bash
curl -X POST https://voice-api.edesy.in/v1/webhooks/subscriptions \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://crm.example.com/edesy/events",
    "secret": "a-long-random-string",
    "event_types": ["call.completed", "disposition.submitted", "recording.ready"]
  }'
```

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | Public HTTPS endpoint |
| `secret` | string | no | Signs every delivery with `X-Edesy-Signature`. Strongly recommended |
| `event_types` | array | no | Events to receive. Empty or omitted means all |

```json
{
  "data": {
    "id": "0b9f5d2c-0000-4000-8000-000000000000",
    "url": "https://crm.example.com/edesy/events",
    "event_types": ["call.completed", "disposition.submitted", "recording.ready"],
    "has_secret": true,
    "is_active": true,
    "created_at": "2026-10-28T09:00:00Z",
    "updated_at": "2026-10-28T09:00:00Z"
  }
}
```

The secret is never returned; `has_secret` tells you one is set. To rotate it, `PATCH` a new `secret` and accept both old and new signatures on your side for a short overlap.

Valid event types: `call.answered`, `call.completed`, `recording.ready`, `disposition.submitted`, `call.missed`. Unknown types are rejected so a subscription cannot wait for an event that never fires.

## Envelope

Every delivery is a JSON `POST`:

```json
{
  "event": "call.completed",
  "timestamp": "2026-10-28T11:19:02Z",
  "data": {
    "call_sid": "CA_6f1c2d3e-0000-4000-8000-000000000000",
    "external_id": "crm-lead-77812",
    "campaign_id": "...",
    "contact_id": "...",
    "to": "+9198XXXXXXXX",
    "from": "+9180XXXXXXXX",
    "direction": "outbound",
    "status": "completed",
    "duration_sec": 142,
    "sip_status": 200,
    "answered_by": "human",
    "custom_fields": { "city": "Pune" }
  }
}
```

`data` has one shape for every event; each event fills the fields that apply:

| Field | Description |
|---|---|
| `call_sid` | The call |
| `external_id`, `custom_fields` | Your CRM ID and fields from the lead |
| `campaign_id`, `contact_id` | Edesy campaign and contact |
| `to`, `from`, `direction` | Call details |
| `phone_number_id`, `phone_number_name` | Your number (`call.missed`) |
| `status` | `completed`, `busy`, `no-answer`, `failed` (`call.completed`), `answered`, or `missed` |
| `duration_sec`, `sip_status`, `sip_reason` | Call result |
| `answered_by` | `human`, `machine` or `unknown` when AMD ran |
| `agent` | `id`, `number`, `name` |
| `disposition` | `id`, `name`, `is_terminal`, `notes` |
| `recording_url` | Playable recording (`recording.ready`) |

## Responding

Reply with any `2xx` within 5 seconds. Do the slow work (CRM writes) after responding, from a queue. Anything else, including a timeout, is retried. See [Retries and Replay](https://edesy.in/docs/voice-api/webhooks/retries-and-replay).
