Skip to main content
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.
EventEmitted today
call.initiated✅ Yes
call.answered · call.ended · call.missed · call.timeout · call.cancelled · call.failedComing soon
call.transferred · call.held · call.resumedWith 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:
AttemptSent
1immediately
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.