> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wave.sa/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive call events at your endpoint, and verify their signatures.

Webhooks notify your server when call events happen. You register an endpoint, Wave POSTs signed JSON to it, and retries on failure.

## Register an endpoint

Webhook endpoints are managed in the **Wave dashboard** under **Webhooks**: add a URL, choose the events, and copy the **signing secret** (shown once).

## Events

You can subscribe to any event below (max **5 endpoints** per project). Today only `call.initiated` is **emitted** — the rest are accepted in the subscription allowlist but won't fire until the real-time event stream lands.

| Event                                                                                              | Emitted today                |
| -------------------------------------------------------------------------------------------------- | ---------------------------- |
| `call.initiated`                                                                                   | ✅ Yes                        |
| `call.answered` · `call.ended` · `call.missed` · `call.timeout` · `call.cancelled` · `call.failed` | Coming soon                  |
| `call.transferred` · `call.held` · `call.resumed`                                                  | With the Calling SDK (later) |

## Payload

Each delivery is a JSON body with the `event` type and a `data` object. The **`X-Wave-Event-Id`** header carries a unique id for idempotency.

```json theme={null}
{
  "event": "call.initiated",
  "data": {
    "call_id": "call_abc123",
    "status": "initiated",
    "direction": "outbound",
    "from": "+96651XXXXXXX",
    "customer_number_masked": "+96650••••17",
    "queue_id": null,
    "vitalpbx_channel_id": "…",
    "started_at": "2026-06-17T10:00:00.000Z",
    "metadata": {}
  }
}
```

## Verifying the signature

Every delivery is signed. The **`X-Wave-Signature`** header is `sha256=<hex>` — an HMAC-SHA256 of the **raw request body** using your endpoint's signing secret (`whsec_…`). Verify it with a constant-time compare before trusting the payload:

```javascript theme={null}
import crypto from "node:crypto";

function verifyWaveSignature(rawBody, header, secret) {
  if (!header?.startsWith("sha256=")) return false;
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
```

<Warning>
  Compute the HMAC over the **raw** body bytes, before any JSON parse/re-serialize — re-stringifying can change the bytes and break the check.
</Warning>

## Retries & dead-letter

Each attempt has a **10-second** timeout. Failed deliveries retry up to **5 attempts** on a backoff ladder, then dead-letter:

| Attempt | Sent        |
| ------- | ----------- |
| 1       | immediately |
| 2       | +30s        |
| 3       | +5m         |
| 4       | +30m        |
| 5       | +2h         |

Exhausted deliveries are **dead-lettered** (retained 72h) and appear — with a replay action — in the dashboard's delivery logs.
