Signed call webhooks
Receive lifecycle and output events, verify the raw-body signature and process duplicate delivery safely.
Use Advanced → Organization call webhooks in the assistant editor to add a public HTTPS receiver. These subscriptions belong to the organisation. Save the signing secret when shown; keep it in your receiver's server configuration.
Events and envelope
| Event | Meaning |
|---|---|
call.started | The voice worker accepted session startup. |
call.ended | A call ended normally or through its configured policy. |
call.failed | A failed call reached final handling. |
call.outputs | Configured, consented post-call analysis produced outputs. |
campaign.completed | A campaign reached completion. |
{
"id": "call.started:CALL_UUID",
"event": "call.started",
"data": {
"callId": "CALL_UUID",
"assistantId": "ASSISTANT_UUID",
"versionId": "VERSION_UUID",
"source": "api",
"createdAt": "2026-10-11T12:00:00.000Z"
}
}The data object varies by event. Lifecycle payloads omit transcripts and audio. Output delivery depends on consent, analysis success and retention; it can arrive after the end event.
Verify before parsing
Read these headers: Sigsen-Timestamp, Sigsen-Signature and Sigsen-Event-Id. The signature is v1= followed by a hex HMAC-SHA256 of timestamp.rawRequestBody, using the signing secret exactly as displayed. It is not a hex-decoded key.
This Node.js helper accepts a raw body Buffer and uses a five-minute freshness window:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySigsenWebhook(
rawBody: Buffer,
timestamp: string,
signature: string,
secret: string,
): boolean {
if (!/^\d+$/.test(timestamp) || !/^v1=[a-f0-9]{64}$/.test(signature)) {
return false;
}
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const expected = createHmac("sha256", secret)
.update(timestamp + ".")
.update(rawBody)
.digest();
const received = Buffer.from(signature.slice(3), "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}Verify the original bytes before JSON parsing. Re-serializing parsed JSON can change those bytes and invalidate the signature.
Handle retries and retention
After verification, atomically persist the event ID and a processing job. Deduplicate by Sigsen-Event-Id, then acknowledge a durably accepted event with a successful HTTP response. Do not send a second CRM update or email for a duplicate event.
Deliveries retry up to eight times with the same event ID. Review attempts in Observe → Webhook logs; failed deliveries can be retried manually. The receiver needs public HTTPS and must not rely on redirects.
Output deliveries expire with artifact retention; other deliveries expire after 30 days. Deleting a call's artifacts cancels pending output deliveries, but cannot erase data already stored by your receiver.