Webhooks
Receive real-time event notifications via webhooks.
Webhooks let your application receive real-time HTTP notifications when events happen in ErzyCall — such as a call ending, a contact being created, or a call failing.
How It Works
- You register a webhook endpoint URL in ErzyCall (via the API or dashboard).
- When a subscribed event occurs, ErzyCall sends a
POSTrequest to your URL with the event payload. - Your server verifies the signature and processes the event.
Available Events
| Event | Trigger |
|---|---|
call.started | An outbound call was initiated |
call.ended | A call completed (status: "ended") |
call.failed | A call ended on a provider or pipeline error (status: "error") |
call.scheduled | A call was scheduled for later |
call.cancelled | A scheduled call was cancelled |
contact.created | A new contact was created |
contact.updated | A contact was modified |
contact.deleted | A contact was deleted |
Every call fires exactly one of call.ended or call.failed when it finishes,
never both — subscribe to both events if you want to hear about every outcome.
Test calls placed from the dashboard fire neither.
Webhook Payload
Each delivery is a POST request with a JSON body:
{
"event": "call.ended",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"id": "abc123",
"status": "ended",
"direction": "inbound",
"callTo": "+14155551234",
"callFrom": "+14155559999",
"endedReason": "customer-ended-call",
"durationSeconds": 45,
"recordingUrl": "https://...",
"createdAt": "2025-01-15T10:29:15.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
}data is the same call object returned by GET /api/v1/calls/:id, minus the
transcript field — see Calls for every field, and
fetch that endpoint when you need the conversation.
When event is call.failed because the LiveKit agent could not load its
configuration before audio, data.endedReason is
livekit-agent-bootstrap-failed and data.bootstrapFailure contains only safe,
structured diagnostics. Raw provider logs, response bodies, credentials, and
internal correlation IDs are not included.
Signature Verification
Every webhook delivery is signed with your endpoint's secret using HMAC-SHA256. Always verify the signature before processing the payload.
The signature is sent in the X-Webhook-Signature header, alongside the unix
timestamp it was computed with:
X-Webhook-Signature: t=1736937000,v1=<hex-encoded HMAC>The signed string is <timestamp>.<raw request body>.
Verification Example (Node.js)
import crypto from "crypto";
function verifyWebhookSignature(payload, signature, secret) {
const [t, v1] = signature.split(",").map((part) => part.split("=")[1]);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${payload}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
// In your webhook handler:
app.post("/webhook", (req, res) => {
const signature = req.headers["x-webhook-signature"];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process the event
const { event, data } = req.body;
console.log(`Received ${event}:`, data);
res.status(200).send("OK");
});Managing Endpoints
Via the API
# Create a webhook endpoint
curl -X POST "https://app.erzycall.com/api/v1/webhooks" \
-H "X-API-Key: ek_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhook",
"events": ["call.ended", "contact.created"],
"description": "My production webhook"
}'The response includes a secret field — save this for signature verification:
{
"data": {
"id": "wh_abc123",
"url": "https://your-app.com/webhook",
"events": ["call.ended", "contact.created"],
"secret": "whsec_...",
"isActive": true,
"description": "My production webhook"
}
}Via the Dashboard
Navigate to Settings > API Keys > Webhooks tab to create and manage webhook endpoints through the UI.
Retry Behavior
If your endpoint returns a non-2xx status code or times out, ErzyCall retries the delivery up to 3 times with exponential backoff (delays of approximately 10s, 60s, and 300s).
Auto-Disable
If an endpoint fails 10 consecutive deliveries, it is automatically disabled. You can re-enable it from the dashboard or by updating the endpoint via the API:
curl -X PATCH "https://app.erzycall.com/api/v1/webhooks/{id}" \
-H "X-API-Key: ek_live_abc123" \
-H "Content-Type: application/json" \
-d '{"isActive": true}'Rotating Secrets
To rotate the signing secret for an endpoint:
curl -X POST "https://app.erzycall.com/api/v1/webhooks/{id}/rotate-secret" \
-H "X-API-Key: ek_live_abc123"The response contains the new secret. Update your verification code immediately — the old secret stops working right away.
Limits
- Maximum 5 webhook endpoints per organization.
- Maximum 10 events per endpoint.
- Webhook URLs must use HTTPS.
Best Practices
- Always verify signatures to ensure the payload came from ErzyCall.
- Respond quickly with a
200status. Process the event asynchronously if needed. - Handle duplicate deliveries — use the event data to deduplicate (e.g., by call ID).
- Monitor your endpoint — if deliveries start failing, check your logs before the endpoint gets auto-disabled.