Documentation menu

Run calls

Webhooks

One URL, one secret, per-event toggles — every event is a signed POST so your backend can trust it actually came from Voicelo.

Setup

In Settings → Webhooks, set a URL, turn it on, and choose which events to receive. A secret is generated for you — regenerate it any time from POST /api/settings/webhooks/regenerate-secret, and use POST /api/settings/webhooks/test to fire a sample event at your endpoint before going live.

Events

Event types
inboundCall
A call reached one of your numbers.
outboundCall
A call was placed on your behalf.
callCompleted
A call reached a terminal status — completed, failed, no-answer, busy or canceled.
callbackPlaced
A scheduled callback was dialled.
voicemailLeft
The call ended in voicemail.
leadCreated
A new lead was added, however it arrived — dashboard, import, or /api/calls/dial.
leadQualified
An agent marked a lead qualified mid-call.
appointmentBooked
An appointment was booked, by an agent or the dashboard.
appointmentCanceled
An appointment was canceled.
campaignCompleted
Every lead in a campaign reached a terminal outcome.

Payload shape

Every event, whatever its type, arrives as the same envelope:

callCompleted
{
  "event": "callCompleted",
  "timestamp": "2026-09-12T14:03:21.000Z",
  "data": {
    "callSid": "CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "leadId": "6511a...",
    "campaignId": "6511b...",
    "direction": "outbound",
    "status": "completed",
    "duration": 47,
    "provider": "twilio"
  }
}

Verifying a signature

Every request carries an X-Voicelo-Signature header — an HMAC-SHA256 of the exact request body, hex-encoded, using your webhook secret as the key:

verify.js
const crypto = require("crypto");

function isValid(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody) // the exact request body bytes, not a re-serialised object
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
Careful
Delivery is a single POST with an 8-second timeout and no automatic retry — a failed or slow response is logged on the account but not retried. Design the receiving endpoint to respond quickly and treat webhooks as a convenience alongside the REST API, not the only record of what happened.