← Callora home
Docs

Webhooks

Real-time notifications for call events, credit updates, and agent state. Signed HMAC-SHA256 · timestamp-bound · replay-protected.

Configuring

Dashboard → Settings → Webhooks → Add endpoint. We POST JSON to your URL with two headers: X-Callora-Signature (hex HMAC-SHA256 of the raw body using your webhook secret) and X-Callora-Timestamp (Unix seconds when the event was signed). The shared secret is shown exactly once at creation — save it.

Signature algorithm

signature = hex( HMAC_SHA256( key = webhook_secret, message = timestamp + "." + raw_body ) ) The timestamp is inside the signed message, so an old signature can never be replayed at a new time.

Verifying — Node.js

import crypto from 'crypto' function verifyCallora (rawBody, headers, secret) { const sig = headers['x-callora-signature'] const ts = headers['x-callora-timestamp'] if (!sig || !ts) return false // Reject events older than 5 minutes if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return false const expected = crypto.createHmac('sha256', secret) .update(ts + '.' + rawBody).digest('hex') const a = Buffer.from(sig, 'hex') const b = Buffer.from(expected, 'hex') return a.length === b.length && crypto.timingSafeEqual(a, b) }

Verifying — Python

import hmac, hashlib, time def verify_callora(raw_body: bytes, headers: dict, secret: str) -> bool: sig = headers.get('x-callora-signature') ts = headers.get('x-callora-timestamp') if not sig or not ts: return False if abs(time.time() - int(ts)) > 300: # 5 min window return False msg = (ts + '.').encode() + raw_body expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest() return hmac.compare_digest(sig, expected) # constant-time

Event: call.started

Fires when an outbound call connects OR an inbound call is answered. Payload: { id, type: "call.started", ts, data: { call_id, agent_id, direction, from, to } }.

Event: call.completed

Fires when a call ends. Includes duration_sec, disposition (booked / handled / voicemail / declined), transcript_url, recording_url (if enabled), analysis (structured fields from your speech-analysis prompt).

Event: credit.low

Fires when the account drops below 20% of the monthly allowance.

Event: agent.paused

Fires when an agent hits its per-day cost cap and is auto-paused.

Retries

Non-2xx responses are retried with exponential backoff: 30s, 2m, 10m, 1h, 6h. After 5 attempts (~24h) the event is moved to your dashboard's dead-letter queue. Return 2xx within 15 seconds — queue long work on your side.

Replay protection

Every event has a unique id. Store it and reject duplicates — retries can deliver the same event more than once. Combined with the 5-minute timestamp window this gives you full replay protection.
Explore more: API · Webhooks · Integrations