Skip to Content
APIWebhooks

Webhooks

Akol sends HTTP POST notifications to your endpoint when call events happen. Use webhooks to sync calls into your CRM, trigger automations, or update your own analytics.

Configuring a webhook

Go to Dashboard → Integrations → Webhooks and:

  1. Click Add Webhook
  2. Paste your HTTPS endpoint URL
  3. Pick the events you want to subscribe to
  4. Save — Akol generates a signing secret. Copy it now (it’s hashed server-side after this).

Your endpoint must:

  • Accept POST with Content-Type: application/json
  • Return 2xx within 30 seconds (we don’t wait longer)
  • Use HTTPS — plain http:// URLs are rejected at save time
  • Not resolve to a private/internal IP (SSRF protection)

Event types

EventWhen it fires
call.startedA call connects (inbound or outbound)
call.endedA call ends. Includes transcript, summary, sentiment.
call.transferredThe agent transferred the call to a human
call.voicemailOutbound call hit voicemail
appointment.scheduledAgent booked an appointment via a function call
message.receivedInbound SMS to one of your numbers
lead.createdNew lead captured during a call

Payload shape

{ "event": "call.ended", "timestamp": "2026-04-18T14:23:11.000Z", "businessId": "biz_xxxxxxxx", "webhookId": "whk_xxxxxxxx", "data": { "callId": "call_xxxxxxxx", "campaignId": "cmp_xxxxxxxx", "direction": "inbound", "fromNumber": "+15551234567", "toNumber": "+15557654321", "agentId": "agt_xxxxxxxx", "startedAt": "2026-04-18T14:21:42.000Z", "endedAt": "2026-04-18T14:23:10.000Z", "duration": 88, "outcome": "completed", "summary": "Caller asked about hours and booked a Saturday appointment.", "sentiment": "positive", "transcript": [ { "speaker": "agent", "text": "Thanks for calling — how can I help?", "timestamp": 0 }, { "speaker": "caller", "text": "What time do you close today?", "timestamp": 2400 }, ... ] } }

The data shape varies by event. See Endpoints → Webhooks for the exact schema per event type.

Verifying signatures

Every request includes:

X-Webhook-Signature: t=1745000000,v1=<hex-signature> X-Webhook-Event: call.ended X-Webhook-Delivery: del_xxxxxxxx User-Agent: Akol-Voice-Webhooks/1.0

Verify with HMAC-SHA256 using your signing secret:

import crypto from "node:crypto"; function verifyAkolWebhook(rawBody, header, secret) { // header = "t=<timestamp>,v1=<signature>" const parts = Object.fromEntries( header.split(",").map(p => p.trim().split("=")) ); const expected = crypto .createHmac("sha256", secret) .update(`${parts.t}.${rawBody}`) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"), ); }

Important: verify against the raw request body (the bytes you received), NOT a re-serialized JSON object. JSON whitespace differences will break the signature check.

Also reject anything with a timestamp more than 5 minutes from the current time — that prevents replay attacks if a request is intercepted.

Retries

If your endpoint returns non-2xx (or times out):

  1. Burst retries — 3 attempts with 1s, 5s, 15s backoff, total ~21s.
  2. Persistent retries — if those exhaust, the delivery moves to a Redis- backed queue with another 3 attempts, 5s/10s/20s backoff up to 1 hour.

After ~1 hour of total retries, the delivery is dropped and a failure record is logged. You can replay it from Dashboard → Webhooks → Logs.

Best practices

  • Respond fast. Acknowledge with 200 immediately, then enqueue the payload for async processing in your own infrastructure. Don’t do heavy work in the webhook handler.
  • Be idempotent. We may retry, and network gremlins can occasionally cause duplicate deliveries. Use the delivery ID (or data.callId for call events) as your idempotency key.
  • Whitelist our IPs if your firewall requires it — see the IP allowlist in the dashboard footer (subject to change with notice).
  • Test locally with ngrok or similar — the dashboard’s “Send test event” button lets you fire any event type at your endpoint with a synthetic payload.
Last updated on