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 includescall_id,agent_id,from_number,to_number,direction.call_ended— fired when either side hangs up. Addsduration_seconds,end_reason, andcost_cents.call_analyzed— fired 5–30 seconds after a call ends, once the transcript and summary are ready. Addstranscript,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-
2xxresponse 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
}
}