Webhooks

Webhooks are HTTP POST requests Xenith sends to your server the moment something happens on a call. Use them to write transcripts into your CRM, charge customers, or trigger downstream automation.

Events

  • call_started— fired as soon as the agent picks up. Payload includes call_id, agent_id, from_number, to_number, direction.
  • call_ended— fired when either side hangs up. Adds duration_seconds, end_reason, and cost_cents.
  • call_analyzed— fired 5–30 seconds after a call ends, once the transcript and summary are ready. Adds transcript, summary, sentiment, recording_url.

Configuring a webhook endpoint

1In the dashboard open Settings → Webhooks and click New endpoint.

2Paste an HTTPS URL (http:// is rejected), pick the events to subscribe to, and click Save. Xenith generates a signing secret — copy it now; it is shown only once.

3Send a Test event from the same screen to verify your endpoint responds with 2xx.

Verifying signatures

Every request includes an X-Xenith-Signature header containing an HMAC-SHA256 of the raw request body, keyed with your signing secret. Always verify this header before trusting the payload.

// Next.js / Node example
import crypto from "node:crypto";

export async function POST(req: Request) {
  const raw = await req.text();
  const signature = req.headers.get("x-xenith-signature") ?? "";
  const expected = crypto
    .createHmac("sha256", process.env.XENITH_WEBHOOK_SECRET!)
    .update(raw)
    .digest("hex");

  const a = Buffer.from(signature, "hex");
  const b = Buffer.from(expected, "hex");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = JSON.parse(raw);
  // ... handle event.type
  return new Response("ok");
}
Always use the raw body for HMAC verification. JSON parsers re-order keys and break the hash.

Retries and ordering

  • Any non-2xx response is retried with exponential backoff for up to 24 hours.
  • Each event carries a unique event_id. Store it and de-duplicate on your side — retries can deliver the same event more than once.
  • Events for the same call are delivered in causal order on a best-effort basis, but don’t rely on strict ordering across calls.

Example payload

{
  "event_id": "evt_01HXYZ...",
  "type": "call_ended",
  "created_at": "2026-04-19T10:14:22Z",
  "data": {
    "call_id": "call_01HXYZ...",
    "agent_id": "agt_01HXYZ...",
    "from_number": "+447700900000",
    "to_number": "+442030000000",
    "direction": "inbound",
    "duration_seconds": 94,
    "end_reason": "caller_hangup",
    "cost_cents": 23
  }
}

Next