Twilio Voice Handler

Inbound gateway for Twilio telephony events — voice calls, SMS inbound/status, recording completions, and transcriptions. Handles URL-encoded POST bodies (Twilio standard), verifies HMAC-SHA1 signatures with dual-host URL candidates, and writes to Supabase CCP + local JSONL call logs.

Endpoint

PathProviderStatus
https://webhook.reri.co/smsTwilio SMS prefix✅ approved
https://webhook.reri.co/sms/webhookTwilio SMS inbound✅ approved
https://webhook.reri.co/sms/statusTwilio SMS status callback✅ approved
https://webhook.reri.co/voiceTwilio voice prefix✅ approved
https://webhook.reri.co/voice/webhookTwilio voice inbound✅ approved
https://webhook.reri.co/voice/statusTwilio voice status callback✅ approved
https://webhook.reri.co/voice/recording-completeTwilio recording done✅ approved
https://webhook.reri.co/voice/transcriptionTwilio transcription done✅ approved

Port: 18797 (systemd unit: twilio-webhook.service) Tunnel: Cloudflare Tunnel reri-apiwebhook.reri.co (canonical); Tailscale Funnel as fallback (untested for path-strip; HMAC URL candidate must match)

Auth method

HMAC-SHA1 (Twilio uses SHA1, not SHA256). Twilio computes HMAC-SHA1(AUTH_TOKEN, full_url + sorted_post_params) and sends it in x-twilio-signature. The handler tries two URL candidates per request:

  1. https://srv1347501.tailb025a7.ts.net + path (Tailscale)
  2. https://webhook.reri.co + path (Cloudflare canonical)

This dual-host approach handles rollback scenarios where Twilio is still configured against the Tailscale URL. Secret from TWILIO_AUTH_TOKEN env var.

Body format: application/x-www-form-urlencoded (Twilio standard) — handler uses express.urlencoded({ extended: false }).

Payload shape

FieldTypeDescription
CallSidstringUnique Twilio Call identifier
SmsSidstringUnique Twilio SMS identifier
BodystringMessage content (SMS) or transcription text
Directionstringinbound or outbound-api
FromstringCaller/sender E.164 phone number
TostringRecipient E.164 phone number
CallStatusstringringing, in-progress, completed, etc.
RecordingUrlstring(recording events) URL of MP3 recording
TranscriptionTextstring(transcription events) Full transcript text

Downstream dispatch

twilio-voice-handler.js
 ├─ /voice/webhook (inbound call) → generate TwiML response
 ├─ /voice/recording-complete → write calls.jsonl + Supabase
 ├─ /voice/transcription → Supabase CCP (svueekfvfrvhylxygktb)
 ├─ /sms/webhook (inbound SMS) → Supabase message write
 └─ /sms/status → Supabase delivery status update

Call log path: voice-calls/calls.jsonl (local append, NDJSON format) Supabase project: svueekfvfrvhylxygktb (CCP production)

Dedup strategy

Uses checkDedupRaw() from scripts/lib/webhook-dedup.js — raw variant that works without Express middleware chain. Keyed on CallSid or SmsSid. Failed/unprocessable payloads written to data/dead-letters/.

Audit trail

  • webhook_audit_log — via auditLogRaw() from scripts/lib/webhook-audit.js. Raw variant used because handler parses URL-encoded bodies outside normal middleware.
  • Call logs: voice-calls/calls.jsonl — local NDJSON for per-call audit trail.
  • Dead letter queue: data/dead-letters/ for unprocessable events.
  • Fatal error handler: uncaughtException + unhandledRejection log structured JSON before process exit.
  • webhook-architecture — Cross-handler governance; note: Twilio uses HMAC-SHA1 (not SHA256), dual-host URL candidates required
  • twilio — Twilio API reference, account SID, auth token rotation, TwiML spec, recording/transcription endpoints
  • _summary — Aurora may consume transcription results dispatched from this handler
  • cron-timer-registrytwilio-webhook.service systemd unit, restart policy
  • backend-model-map — Gateway routing for voice event processing