The war room won't connect

SymptomCause & fix
"could not reach server"Server not running, or wrong URL. Start it: cd backend && go run ./cmd/callhook (or the released binary) and connect to http://localhost:8080. → Quickstart
"invalid or missing bearer token"The server has CALLHOOK_INTAKE_TOKEN set; paste the same token on the Connect screen. → Production → Lock it down
Connects, but "DRY-RUN" badge everywhereNot an error — no CALLHOOK_API_KEY means fabricated calls, zero cost, full pipeline. → Going live

Platform webhook returns 401

Every adapter verifies the platform's own signature. A 401 body looks like {"error":"STRIPE_WEBHOOK_SECRET not configured","docs":"https://callhook.github.io/..."} — the docs field is the fix.

ErrorFix
<ENV_VAR> not configuredSet the adapter's secret (e.g. STRIPE_WEBHOOK_SECRET=whsec_...) and restart. Per-platform steps: Native adapters + integrations/<platform>/README.md in the repo.
invalid ... signatureThe secret doesn't match what the platform sends — re-copy it from the platform's webhook settings, not the API key. Watch hex vs base64: Shopify/WooCommerce/Xero/Typeform are base64; GitHub/Stripe/Calendly/Paddle are hex.
HubSpot: correct secret, still invalidBehind a tunnel the signed URI is the public one. Set HUBSPOT_EXTERNAL_BASE_URL=https://your-tunnel.example.com and restart. → Production
Stripe/Slack worked before, now 401Replay window: signatures older than 5 minutes are rejected. Sync your clock (NTP). Secret rotation: during the overlap both signatures are accepted only if both secrets are valid — v1 lists support this; otherwise wait out the window.
SNS: "cert url host must be *.amazonaws.com"SigningCertURL failed validation — message tampered or misconfigured. Also confirm the subscription (SNS_AUTO_SUBSCRIBE=true or click the SubscribeURL in the delivery logs).
Chargebee/Pipedrive: 401 with correct credentialsThey use HTTP Basic auth, not HMAC. The username/password must be the ones configured in their webhook settings screens — not API keys.

POST /api/events errors

ErrorFix
type is required / customer_id is required / id is requiredAll three are mandatory fields. → API → POST /api/events
unsupported event typeThe response lists supported_types — use one of the 8 blueprints. → Event Types
customer ... not foundDemo store knows cus_1001–cus_1003 and cus_2001–cus_2030. For real data implement business.Store. → Store adapters
phone must be E.164 formatNumbers start with + and country code, e.g. +14155550002. → API
{"status":"duplicate"}Not an error — the event id was seen before (idempotency). New call → new id.
{"status":"deferred"}Not an error — outside calling hours (9:00–20:00 recipient-local, weekdays); dials automatically at the next window.
prefetch invoice: no open invoiceThe blueprint's Compose step needs data the store doesn't have — an invoice.due needs an open invoice for that customer. → Blueprint contract
not_before must be RFC3339Use ISO-8601/RFC3339 like 2026-09-08T09:00:00Z. → API

Events fire, but no calls happen

CheckHow
Dry-run mode?GET /api/health → dry_run: true means calls are fabricated on purpose (no balance spent) — sessions still complete. → Going live
Outside calling hours?Sessions show call_deferred in the audit trail; they dial at the next window. For testing: CALLHOOK_ENFORCE_WINDOWS=false.
Real mode + call_failed?CALL-E rejected the call — check CALLHOOK_API_KEY and CALLHOOK_PUBLIC_URL (CALL-E must reach /callhook/webhook). → Production

Campaign problems

SymptomCause & fix
audience is emptyall_overdue found no customers with open invoices. Pass an explicit audience list, or fix store data. → Campaigns
goal.target must be > 0Count goals need a target. → Create a campaign
Running, but no waves launchWave delay hasn't elapsed (Go duration: "15m"). The pump ticks every CALLHOOK_RETRY_TICK (default 15s).
Everyone marked skippedThe goal was met — early-stop is the feature. → Lifecycle & early-stop

Install / startup problems

SymptomFix
port :8080 already in useCALLHOOK_ADDR=:9090; connect the war room to the new port.
Journal replay errors on bootdata/sessions.jsonl is append-only; corrupt tail lines are skipped. Delete only if history doesn't matter. → Persistence
go: go.mod file not foundRun from backend/ (module root), not the repo root.
Dashboard 404 / blank pageBinary built without the web app — make full in backend/ embeds it, or use the release binary. → Install

Where everything lives

QuestionDoc
How do I start?Live demo · Quickstart · Install
What can I fire at it?Event Types · API Reference
How do platform webhooks plug in?Integrations → Native adapters
How do campaigns work?Campaigns
How do I go live / secure it?Production Guide
How is it built inside?Architecture