← 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.