# Webhook Subscriptions

> Create, update, test and delete call.missed webhook subscriptions, read the delivery log and replay failed deliveries.

Source: https://edesy.in/docs/missed-call/api-reference/webhook-subscriptions

Manage where `call.missed` events are sent. For the payload and what your server must do, see [Webhook Setup](https://edesy.in/docs/missed-call/guides/webhook-setup).

| Method | Path | What it does |
|---|---|---|
| `GET` | `/v1/webhooks/subscriptions` | List subscriptions |
| `POST` | `/v1/webhooks/subscriptions` | Create a subscription |
| `PATCH` | `/v1/webhooks/subscriptions/:id` | Update URL, secret, events or active state |
| `DELETE` | `/v1/webhooks/subscriptions/:id` | Delete a subscription (returns `204`) |
| `POST` | `/v1/webhooks/subscriptions/:id/test` | Send a test event |
| `GET` | `/v1/webhooks/subscriptions/:id/deliveries` | Delivery log |
| `POST` | `/v1/webhooks/subscriptions/:id/deliveries/:delivery_id/replay` | Replay one delivery |
| `POST` | `/v1/webhooks/subscriptions/:id/deliveries/replay` | Replay failed deliveries in bulk |

## Create a subscription

| Body field | Type | Description |
|---|---|---|
| `url` | string, required | HTTPS URL that receives the POST. Private addresses are refused |
| `secret` | string | When set, each request carries `X-Edesy-Signature: sha256=<HMAC-SHA256 of the raw body>` |
| `event_types` | string[], required | Use `["call.missed"]` |

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

`201 Created`:

```json
{
  "data": {
    "id": "05c54484-f740-4103-8d98-0a0709569c9b",
    "url": "https://crm.example.com/webhooks/missed-call",
    "event_types": ["call.missed"],
    "has_secret": true,
    "is_active": true,
    "created_at": "2026-10-18T07:00:00Z",
    "updated_at": "2026-10-18T07:00:00Z"
  }
}
```

A URL that is not HTTPS or points to a private address returns `400`, for example `{ "error": "webhook url must use https" }`.

## Update a subscription

`PATCH` with any of `url`, `secret`, `event_types` and `is_active`. Set `is_active` to `false` to pause deliveries without deleting the subscription.

## Send a test

`POST /v1/webhooks/subscriptions/:id/test` posts a `webhook.test` event to the URL and reports what your server answered. A failed test still returns `200`, with the details:

```json
{ "data": { "success": false, "status_code": 500, "error": "webhook returned status 500" } }
```

## Delivery log

`GET /v1/webhooks/subscriptions/:id/deliveries?limit=50` returns recent deliveries, newest first (`limit` up to 200, default 50), including the exact JSON we sent:

```json
{
  "data": [
    {
      "id": "7b1e2c3d-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
      "subscription_id": "05c54484-f740-4103-8d98-0a0709569c9b",
      "event_type": "call.missed",
      "call_sid": "CA_e5353a30-5f34-4582-bb9c-830ab7b513ef",
      "status": "failed",
      "attempts": 6,
      "max_attempts": 6,
      "response_code": 0,
      "error": "resolve webhook host: no such host",
      "created_at": "2026-10-18T07:50:33Z"
    }
  ]
}
```

## Replay

- **One delivery:** `POST /v1/webhooks/subscriptions/:id/deliveries/:delivery_id/replay` returns `{ "data": { "replayed": 1 } }`.
- **In bulk:** `POST /v1/webhooks/subscriptions/:id/deliveries/replay` resends every failed delivery in a window, up to 10,000 per call.

| Body field | Description |
|---|---|
| `status` | Only `"failed"` is supported |
| `since` | RFC 3339; only deliveries created after this |
| `until` | RFC 3339; only deliveries created before this |

Replays go to the subscription's current URL and are not charged. See [Retries and Replay](https://edesy.in/docs/missed-call/guides/retries-and-replay).
