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.
{
"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:
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));
}
Compute the HMAC over the raw body bytes, before any JSON parse/re-serialize — re-stringifying can change the bytes and break the check.
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.