Post-call webhooks: events, triggers, payload, signatures and retries
Receive every call's recording, transcript and outcome in your own system: the call.completed and call.classified events, the trigger filter, every payload field including hangupReason, how to verify X-ElevateBox-Signature, and the retry schedule.
Updated 5 October 2026
On the caller's Outcomes tab, Post-call webhooks send each call's recording, transcript and outcome to your system as soon as the call ends. Up to five endpoints per caller. Webhooks are not versioned: a saved change applies to the next call.
Add an endpoint
- Click Add webhook and enter a public
https://URL (port 443, no credentials, no IP addresses or local hosts). - Choose When this webhook fires (see triggers below) and Save webhook.
- Copy the signing secret (
whsec_…) when it is shown. It is shown once. - Use the test button: we send a signed sample
call.completedwith"test": trueand report “Test delivery sent and accepted by your server.”, or the reason it failed (unreachable, no answer within 10 seconds, or the HTTP status).
Events
call.completed: sent when the call ends. The transcript and timings are there;outcome,summary,intent,sentimentandcapturedValuesare stillnullandclassification.statusispending.call.classified: sent once the outcome, summary and captured values are known, usually within a minute or two of the call ending. Treat this as the one to act on.
Triggers
- Every call (default): fires for every attempt, answered or not.
- Answered calls only: only calls someone actually answered. Skips voicemail, busy, no answer and failures.
- Voicemail only: only calls that reached voicemail, so you can follow up another way. Since the outcome is unknown at
call.completed, such an endpoint receives onlycall.classified.
Payload
{
"event": "call.classified",
"deliveryId": "dlv_…",
"sentAt": "2026-10-05T09:31:12.000Z",
"call": {
"id": "…", "agentId": "…", "direction": "outbound", "status": "completed",
"startedAt": "…", "answeredAt": "…", "endedAt": "…", "durationSeconds": 84,
"contact": { "name": "Asha Rao", "phone": "+919876543210" },
"hangupReason": "completed",
"outcome": "qualified", "summary": "Asked for a site visit on Saturday…",
"intent": "booking", "intentConfidence": 0.9, "intentReason": "…",
"sentiment": "positive", "sentimentConfidence": 0.8, "sentimentReason": "…",
"capturedValues": { "budget": "50L", "visit_day": "Saturday" },
"transcript": [ { "speaker": "agent", "text": "…" }, { "speaker": "user", "text": "…" } ],
"recordingUrl": "https://…",
"recording": { "status": "ready", "url": "https://…", "expiresAt": "…", "mimeType": "audio/mpeg", "readyAt": "…", "deletedAt": null },
"classification": { "at": "…", "status": "done" }
}
}hangupReasoniscompleted,failedorcancelledfor a connected call, andvoicemail,no_answer,busy,call_failedorno_responsefor one that never connected.outcomeis one ofqualified,interested,callback,not_interested,no_response,no_answer,voicemail,busy,call_failed.sentimentispositive,neutralornegative.transcriptholds up to 400 turns;speakerisagentoruser.recording.urlis a signed link and isnulluntil the recording is ready.capturedValuesis keyed by your field keys from the Outcomes tab. There is no cost field; credits are in the Billing statement.
Verify the signature
Every delivery carries X-ElevateBox-Delivery, X-ElevateBox-Event, X-ElevateBox-Timestamp (Unix seconds) and X-ElevateBox-Signature (v1=<hex>). The signature is HMAC-SHA256 of "<timestamp>.<raw body>" with your whsec_ secret. The same four headers are also sent as X-Vikku-* for receivers built on the old name; those stop on 4 January 2027, so switch to the X-ElevateBox-* names.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, headers, rawBody) {
const expected = createHmac("sha256", secret)
.update(`${headers["x-elevatebox-timestamp"]}.${rawBody}`)
.digest("hex");
const given = String(headers["x-elevatebox-signature"]).replace(/^v1=/, "");
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Retries
Any 2xx answer counts as delivered; answer quickly and do the work afterwards, because the request times out after 10 seconds and redirects are not followed. A failure is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 8 hours, six attempts in all, then marked dead. Each endpoint shows “Active/Paused · N delivered · N pending · N failed”; Pause stops deliveries without deleting the endpoint. Use deliveryId to ignore a repeat.