The pipeline

Pipeline Flow Diagram
Webhook In
Router / Blueprint
Session Prefetch
Task Composition
Deterministic Template
Place Call
CALL-E & Customer
Outcome Webhook
Outcome Engine: Policy Gate (Certain → Write · Uncertain → Escalate) | Retries (no_answer → Redial) | Audit Log

Packages

PackageOwns
cmd/callhookEntrypoint, config, graceful shutdown
cmd/callhookctlCLI client: fire, batch, sessions, metrics
internal/apiHTTP: intake (+batch), CALL-E webhook, dashboard, metrics, rate limiting, auth
internal/eventsEvent schema + router: event type → blueprint (task composer + result schema)
internal/businessThe Store interface + mock — the surface you implement against your CRM
internal/callhookclientCALL-E Developer API client: calls + Goals + webhooks (their OpenAPI spec ships in docs/)
internal/sessionSession registry, audit log, retry/schedule triggers
internal/outcomeOutcome engine: policy-gated writes, escalation, retry decisions, callback delivery
internal/retryScheduler: redials, calling-window deferrals, scheduled starts
internal/callwindowPolite-hours gate: region → timezone, 9:00–20:00 weekdays
internal/storeCrash-safe JSONL journal persistence

Design decisions

Read/write separation

Prefetched context is given to the voice agent (customer data is baked into the task — the agent never asks for account numbers). Business writes happen only after the call, from the JSON-Schema-validated structured result — never from raw conversation. Every write is policy-gated and audit-logged.

No extra LLM — deliberately

CALL-E's voice agent is the conversational brain. Task composition is deterministic templates, because a hallucinated amount or date spoken on a phone call is a real failure mode, and orchestration policy ("certain → write, uncertain → escalate") should be auditable code, not model output. callhook doesn't reinvent the voice AI; it makes it pluggable into any business system.

Triggers, not timers

Three deferred-trigger kinds share one scheduler: retry (no answer), window (polite hours), scheduled (caller-requested). Window and scheduled triggers never consume retry attempts — deferring a call is not the customer's fault.

Goals API — the enterprise path

Besides free-text call tasks, CALL-E publishes Goals: reusable, versioned call workflows with typed input and result schemas. callhook supports both. A blueprint can pin GoalID (+ variables mapping) and that event type executes the pinned, schema-validated goal server-side. Free-text keeps callhook zero-setup and fully generic; Goals give enterprises versioned, governed workflows. The client implements ListGoals, CreateGoalRun, GetGoalRun.

What we deliberately did not build

Testing

Unit suites cover the correctness-critical paths: calling-window timezone math (including weekend and evening rollover), journal replay (including torn-line tolerance after a crash), and the outcome policy ladder (promise writes, refusal never redials, blocked numbers never retried, exhaustion stops). CI runs the suite with -race on every push.