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.