Receive & verify webhooks
Guide: Receive & verify webhooks
This guide shows how to build a secure webhook receiver — verifying signatures, de‑duplicating retries, and responding correctly. For the catalog of events and payloads, see the events reference.
1. Create an endpoint
Register your HTTPS URL and the events you want via the dashboard Webhooks page or the management API. Save the signing_secret from the create response — it's shown only once.
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/webhooks \
-H "x-api-key: vai_live_…" -H "Content-Type: application/json" \
-d '{ "url": "https://example.com/telenow/webhooks", "events": ["call.ended","recording.ready"], "includeTranscript": true }'
If an automation platform (Zapier, n8n, Make) is managing the subscription, it uses the REST-hooks API (
/api/v1/hooks) instead — those deliveries are still signed, but the platform handles verification.
2. Verify the signature
Every delivery includes X-VoiceAI-Signature: sha256=<hex>, the HMAC‑SHA256 of the raw request body keyed with your signing secret. Always verify against the raw bytes — not a re‑serialized object.
Node (Express)
import express from 'express';
import crypto from 'crypto';
const app = express();
const SECRET = process.env.TELENOW_WEBHOOK_SECRET;
// Capture the raw body for signature verification.
app.use('/telenow/webhooks', express.raw({ type: 'application/json' }));
app.post('/telenow/webhooks', (req, res) => {
const sig = req.get('X-VoiceAI-Signature') || '';
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
const ok =
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString());
const deliveryId = req.get('X-VoiceAI-Delivery');
if (alreadyProcessed(deliveryId)) return res.status(200).end(); // de-dupe retries
handle(event); // your logic
markProcessed(deliveryId);
res.status(200).end(); // ack quickly
});
Python (Flask)
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["TELENOW_WEBHOOK_SECRET"].encode()
@app.post("/telenow/webhooks")
def webhooks():
body = request.get_data() # raw bytes
expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
sig = request.headers.get("X-VoiceAI-Signature", "")
if not hmac.compare_digest(sig, expected):
abort(401)
delivery_id = request.headers.get("X-VoiceAI-Delivery")
if already_processed(delivery_id):
return "", 200
handle(request.get_json())
mark_processed(delivery_id)
return "", 200
3. De-duplicate with the delivery id
Retries reuse the same X-VoiceAI-Delivery id. Record processed ids (in Redis/DB with a TTL) and skip duplicates so a retried call.ended doesn't double‑post to your CRM.
4. Respond fast, work async
Acknowledge with a 2xx immediately, then do heavy work (CRM writes, downloading recordings) in a background job. Slow responses look like failures and get retried.
5. Understand retries
Failed deliveries (non‑2xx, timeout, connection error) retry with exponential backoff (5s × 2^attempt, capped at 1 hour) for up to 8 attempts; after repeated permanent failures the endpoint is disabled. Inspect attempts at GET /api/orgs/{orgId}/webhooks/{id}/deliveries.
6. Handle the events you care about
function handle(event) {
switch (event.event) {
case 'call.started':
// event.from, event.variables, event.startTime
break;
case 'call.ended':
// event.durationSecs, event.transcript (if enabled), event.recording (if enabled)
break;
case 'recording.ready':
// download event.recording.url before event.recording.expiresAt
break;
case 'tool.invoked':
// event.name, event.arguments, event.result, event.status
break;
case 'call.analyzed':
// event.analysis.summary, .sentiment, .disposition, .actionItems
break;
}
}
Recording URLs are signed and short‑lived (~1 hour) — download promptly inside your handler rather than storing the URL for later.
See the events reference for every payload.
Related
- Webhook events reference — the full payload catalog.
- Webhook management API — both registration surfaces.
- Automation platforms — no-code receivers via Zapier / n8n / Make.