SigsenAI
Developer guides

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

EventMeaning
call.startedThe voice worker accepted session startup.
call.endedA call ended normally or through its configured policy.
call.failedA failed call reached final handling.
call.outputsConfigured, consented post-call analysis produced outputs.
campaign.completedA 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.

On this page