# Webhooks

> Receive message.received and message.status events for your Edesy WhatsApp numbers on your own HTTPS endpoint, signed with HMAC-SHA256 and retried with backoff.

Source: https://edesy.in/docs/whatsapp-number/api/webhooks

WhatsApp webhooks send events for your Edesy numbers to your own HTTPS endpoint. Register a URL, choose `message.received`, `message.status` or both, and set a signing secret. Each POST carries an `X-Edesy-Signature` HMAC-SHA256 header, failed deliveries are retried with backoff, and every attempt is shown in a delivery log.

## Facts

| Item | Detail |
|---|---|
| Where | wa-connect.edesy.in > **Webhooks**, or the API below |
| Events | `message.received` (a customer sent a message), `message.status` (sent, delivered, read, failed) |
| Filter | Pick event types per webhook; an empty list means all events |
| Multiple endpoints | Yes, register as many as you need |
| Signature | `X-Edesy-Signature: sha256=<hex>`, HMAC-SHA256 of the raw body with your secret |
| Success | Any `2xx` response within 5 seconds |
| Retries | Up to 6 attempts, backoff of about 30 s, 1 min, 2 min, 4 min, 8 min |
| Delivery log | Event, status, attempts, HTTP code, error, next retry, payload |
| Test | Send a `webhook.test` event from the portal or API |

## How it works

1. **Add a webhook** on the **Webhooks** page: endpoint URL, optional signing secret, and events.
2. **Click Test** to send a `webhook.test` event and check that your endpoint returns `2xx`.
3. **Verify the signature** on every request (below).
4. **Return `2xx` quickly.** Do slow work after you respond.
5. **Check the delivery log** if events are missing. Failed attempts show the HTTP code and error, and pending ones show the next retry time.

## Payload

Every event uses the same envelope:

```json
{
  "event": "message.received",
  "timestamp": "2026-10-28T10:15:12Z",
  "data": { }
}
```

### message.received

`data` is the stored inbound message:

```json
{
  "event": "message.received",
  "timestamp": "2026-10-28T10:15:12Z",
  "data": {
    "id": "5b1c2d3e-0000-4000-8000-000000000000",
    "conversation_id": "7d8e9f00-0000-4000-8000-000000000000",
    "wamid": "wamid.HBgM...",
    "direction": "inbound",
    "type": "text",
    "status": "delivered",
    "from": "+919876543210",
    "to": "+911234567890",
    "content": "Is my order shipped?",
    "created_at": "2026-10-28T10:15:12Z"
  }
}
```

Media messages also carry `media_id`, `media_mime_type` and, for documents, `file_name`. Replies carry `context_message_id`.

### message.status

`data` is the WhatsApp status update:

```json
{
  "event": "message.status",
  "timestamp": "2026-10-28T10:15:20Z",
  "data": {
    "id": "wamid.HBgM...",
    "recipient_id": "919876543210",
    "status": "delivered",
    "timestamp": "1761646520"
  }
}
```

`status` is `sent`, `delivered`, `read` or `failed`. Failed updates include an `errors` array. Match `data.id` to the `wamid` you got when sending.

## Verify the signature

```javascript
import crypto from 'crypto';

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

Compute the HMAC over the raw request body, before any JSON parsing. If you did not set a secret, no signature header is sent.

## Webhooks API

Base URL: `https://voice-api.edesy.in/v1/whatsapp`. Auth: `Authorization: Bearer sk_...`.

| Method | Path | Description |
|---|---|---|
| `POST` | `/webhooks` | Register. Body: `url` (required), `secret`, `event_types` |
| `GET` | `/webhooks` | List (the secret is never returned; `has_secret` shows whether one is set) |
| `DELETE` | `/webhooks/{id}` | Remove |
| `POST` | `/webhooks/{id}/test` | Send a `webhook.test` event; returns `ok` and `status_code` |
| `GET` | `/webhooks/{id}/deliveries` | Recent delivery attempts (`limit`, default 50) |

```bash
curl -X POST https://voice-api.edesy.in/v1/whatsapp/webhooks \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/whatsapp",
    "secret": "a-long-random-string",
    "event_types": ["message.received", "message.status"]
  }'
```

## FAQ

### Which events can I subscribe to?
`message.received` for inbound messages and `message.status` for sent, delivered, read and failed updates.

### How do I check a webhook is genuine?
Compute HMAC-SHA256 of the raw body with your secret and compare it to `X-Edesy-Signature` (`sha256=<hex>`).

### What happens if my server is down?
The delivery is retried up to 6 attempts with growing gaps (about 30 s, 1 min, 2 min, 4 min, 8 min). After the last attempt it is marked failed in the delivery log.

### How fast must my endpoint respond?
Within 5 seconds, with a `2xx` status. Anything else counts as a failed attempt.

### Can I send events to more than one URL?
Yes. Register several webhooks, each with its own events and secret.

### Can I test my endpoint before going live?
Yes. Use **Test** in the portal or `POST /v1/whatsapp/webhooks/{id}/test`. It sends a `webhook.test` event.

### Do I get template approval events?
No. Template status is updated in the portal and API; use **Sync with Meta** or `GET /v1/whatsapp/templates` to check it.
