API Reference
Five endpoints. Everything a business system needs to reach its customers by voice.
POST /api/events
The product. Fire one event; callhook handles all phone communication for it.
| Field | Type | Notes |
|---|---|---|
id | string · required | Unique event id — also the idempotency key (unless idempotency_key is set). Duplicate ids never call twice. |
type | string · required | Event type — see Events and Campaigns. |
customer_id | string · required | Resolves the customer record from the business store; binds the whole session to one customer. |
phone | string | E.164 override. Defaults to the customer's stored number. |
tz | string | IANA timezone override (e.g. Asia/Kolkata) for the calling-hours check. Defaults by region. |
payload | object | Event-type-specific data (invoice refs, warning reasons, offer details). |
callback_url | string | Where the terminal outcome is POSTed. Strongly recommended — this closes the loop. |
not_before | string · RFC3339 | Park the call until this time. Window rules still apply after it. |
Responses: 202 {"status":"call_placed"} · 202 {"status":"scheduled"} (not_before) · 202 {"status":"deferred"} (outside calling hours) · 200 {"status":"duplicate"}.
POST /api/events/batch
Fire many events at once. Each is processed independently — one bad event never sinks the batch.
{ "events": [ { "id": "evt_1", "type": "invoice.due", "customer_id": "cus_1002" }, ... ] }
# → {"results":[{"session_id":"evt_1","status":"call_placed",...}, ...]}
POST /callhook/webhook
CALL-E's terminal results land here (set CALLHOOK_PUBLIC_URL so CALL-E can reach you). Requires the X-Callhook-Secret header when CALLHOOK_WEBHOOK_SECRET is configured — forged terminal results are rejected. You normally never call this yourself.
POST /mcp
The MCP server — callhook as agent tools (Streamable HTTP, JSON-RPC 2.0). Tools: fire_event, launch_campaign, get_campaign, list_sessions, list_event_types, run_demo. Point any MCP client here.
GET /api/stream
Server-Sent Events: session and campaign events pushed on every mutation. The war room's live updates ride this stream; polling is the fallback. With auth on, pass the token as ?token= (EventSource cannot send headers).
GET /api/sessions
All sessions with their state, audit trail, outcome and transcript. This is what the dashboard renders.
GET /api/metrics
{
"sessions": 42,
"by_status": { "completed": 35, "failed": 2, "intake": 5 },
"by_outcome": { "payment_promised": 18, "no_answer": 7, ... },
"retries_armed": 3
}
GET /api/health
Uptime probe — also the quickest way to check your configuration at a glance.
{
"ok": true,
"dry_run": true, // no CALLHOOK_API_KEY
"windows_enforced": true,
"auth_intake": false, // CALLHOOK_INTAKE_TOKEN unset
"auth_webhook": false
}
Authentication
When CALLHOOK_INTAKE_TOKEN is set, both intake endpoints require it:
Authorization: Bearer <CALLHOOK_INTAKE_TOKEN>
Point callhook at your real business data by implementing the business.Store interface (customers, invoices, promises, escalations) — see Architecture.