callhook mark

The voice channel, as an API.

Your system fires a webhook. callhook places an intelligent phone call via CALL-E, handles the conversation with full customer context, and POSTs a structured outcome back.

Go single binary 0 dependencies self-hosted + goal-driven campaigns 8 event types MCP server MIT
$ curl -fsSL https://callhook.github.io/install.sh | bash
$ ./callhook/callhook_linux_amd64/callhook
→ the war room opens at http://localhost:8080

How it works

Pipeline Architecture
Business App
POST /api/events
Webhook In
callhook
Prefetch & Policy Gate
CALL-E API
Customer
Interactive Voice
▲ ── Structured Outcome Webhook: {outcome, promise_date, actions, transcript, confidence}

Not a robocall. Not another LLM.

Context before dialing

The customer's plan, invoice, and payment history are prefetched and baked into the call — the agent never wastes seconds asking for account details.

Thinks during the call

When the customer says "I already paid," the agent acknowledges and flags the account for review instead of arguing. CALL-E's voice brain handles it.

Structured outcomes only

Results arrive as JSON-Schema-validated objects — payment_promised, promise_date, confidence — never free text you have to parse.

Policy-gated actions

Unambiguous outcomes write to your business store; uncertain ones escalate to a human. Every action is audit-logged. Deterministic code, not model vibes.

Operational guarantees

GuaranteeHow
Never double-dialPer-attempt idempotency keys + event dedup persisted across restarts
Crash-safeAppend-only JSONL journal; sessions, armed retries and schedules replay on boot
Polite hoursCalls deferred to 9:00–20:00 recipient-local, weekdays, per-region timezone
Courtesy retriesno_answer redials up to 2×; refusals and blocked numbers never redialed
Scheduled callsnot_before on any event parks it until the requested time
Not forgeableBearer-token intake + shared-secret webhook (set both on a public tunnel)

Campaigns: declare a goal, not a dial list

Fire one webhook per call — or declare a goal and let callhook run the waves: "collect 5 payment promises from the overdue list." Waves launch, unanswered customers are requeued, and the campaign stops the moment the goal is met — everyone not yet called is skipped, the budget stays unspent. See campaigns →

Integrate anything that can POST

Stripe, Shopify, n8n, Zapier, cron + curl — one webhook endpoint with idempotency built in. Structured outcomes POST back; AI agents drive it over MCP; your CRM plugs in via one interface. Integrations →

Fire your first event

# start callhook (dry-run: no API key, no real calls, no balance)
$ go run ./cmd/callhook
callhook listening on :8080 (dashboard: http://localhost:8080/)

# any business system:
$ curl -X POST localhost:8080/api/events -H 'Content-Type: application/json' -d '{
    "id": "evt_001",
    "type": "invoice.due",
    "customer_id": "cus_1002",
    "callback_url": "https://your-app.example.com/hooks/callhook"
  }'

# → {"status":"call_placed","call_id":"call_..."}
# → outcome lands on your callback URL when the call finishes
Why "callhook"? Because it's the whole architecture in one word: a webhook goes in, a phone call comes out. If you've ever built a webhook, you already know how to use it.