← Thomas R. Christian — portfolio

Resbit Telephony Worker — Webhook API Reference

API REFERENCEv1.0reviewedOwner: Thomas ChristianUpdated 2026-07-30Verified 2026-07-30

Twilio-facing webhook endpoints for Resbit's forward-first family phone number, with request signing, TwiML responses, and database side effects.

Overview

The Resbit Telephony Worker is a Cloudflare Worker that receives Twilio voice webhooks for a family's Resbit phone number. On an incoming call it forwards to the family caregiver's phone with a whisper announcement identifying the caller; unanswered calls fall through to a transcribed voicemail. Each call produces a row in resbit_calls and a digest entry in resbit_digest_items; voicemail transcripts containing an escalation keyword trigger an SMS to the caregiver. Calls to numbers not registered in resbit_numbers are rejected.

All database access uses the Supabase service-role key and bypasses row-level security by design — the worker is a trusted server-side component.

Authentication

Every POST endpoint validates the X-Twilio-Signature header before doing any work. The expected signature is Base64(HMAC-SHA1(authToken, url + concat)), where concat is the request's POST parameters sorted alphabetically by key and concatenated as key1value1key2value2…. The URL used for validation is PUBLIC_URL + path + query string, so PUBLIC_URL must exactly match the deployed URL Twilio calls — a mismatch fails every request.

Signatures are compared with a constant-time-style XOR accumulation. A request with a missing or invalid signature receives 403 invalid signature. If TWILIO_AUTH_TOKEN is not configured, all signed requests are rejected — there is no bypass mode.

Endpoints

GET /health

Liveness probe. Responds before method and signature checks, so it accepts any HTTP method.

Response200, text/plain:

ok resbit-telephony

Requests to any other path with a method other than POST receive 405 method not allowed. Unknown POST paths receive 404 not found.

POST /voice

Incoming-call entry point (the number's Voice webhook). Looks up the called number in resbit_numbers; if a family is found, identifies the caller against resbit_providers by matching the last 10 digits of the phone number, then either forwards the call or routes to voicemail.

Parameters consumed

Name Type Source Description
To string (E.164) POST body Called Resbit number, used for the family lookup
From string (E.164) POST body Caller's number, used for provider matching and logging

Twilio sends additional standard parameters (including CallSid); they participate in signature validation but are not read by this handler.

Response200, text/xml. With a forwarding number configured (resbit_families.founder_phone):

<?xml version="1.0" encoding="UTF-8"?><Response>
  <Dial timeout="15" action="{PUBLIC_URL}/voice/after?fam=…&amp;name=…&amp;caller=…&amp;from=…" method="POST">
    <Number url="{PUBLIC_URL}/voice/whisper?fam=…&amp;name=…&amp;caller=…&amp;from=…">{founder_phone}</Number>
  </Dial>
</Response>

With no forwarding number, the response is the voicemail TwiML (see /voice/after). If the called number has no resbit_numbers row:

<?xml version="1.0" encoding="UTF-8"?><Response><Reject/></Response>

Errors

Status Meaning
403 Missing or invalid Twilio signature
405 Non-POST method

POST /voice/whisper

Played to the caregiver before the forwarded call connects.

Parameters consumed

Name Type Source Description
name string query Family name; defaults to the family
caller string query Caller label; defaults to an unknown caller

Response200, text/xml:

<?xml version="1.0" encoding="UTF-8"?><Response>
  <Say voice="Polly.Joanna">Resbit call for {name} from {caller}. Connecting you now.</Say>
</Response>

Errors403 on signature failure.

POST /voice/after

Dial-result callback. An answered call is logged and ended; anything else falls to voicemail.

Parameters consumed

Name Type Source Description
DialCallStatus string POST body completed means the caregiver answered
CallSid string POST body Call identifier, used as the idempotency key
From string (E.164) POST body Fallback caller label and number
fam string (UUID) query Family ID
name, caller, from string query Context carried from /voice

Behavior and response200, text/xml. When DialCallStatus is completed: upserts a resbit_calls row (summary: "Call forwarded to you — answered live", triage: handled), inserts a resbit_digest_items row, and returns <Hangup/>. Otherwise returns voicemail TwiML:

<?xml version="1.0" encoding="UTF-8"?><Response>
  <Say voice="Polly.Joanna">You’ve reached the family’s Resbit line. …</Say>
  <Record maxLength="180" playBeep="true" transcribe="true"
    transcribeCallback="{PUBLIC_URL}/voice/transcription?…"
    recordingStatusCallback="{PUBLIC_URL}/voice/recording?…"
    action="{PUBLIC_URL}/voice/recorded" method="POST"/>
</Response>

Errors403 on signature failure.

POST /voice/recorded

Recording-complete action. Thanks the caller and hangs up; consumes no parameters beyond signature validation.

Response200, text/xml containing a <Say> thank-you and <Hangup/>.

Errors403 on signature failure.

POST /voice/transcription

Transcription-ready callback — the endpoint that produces the call log entry for voicemails.

Parameters consumed

Name Type Source Description
TranscriptionText string POST body Voicemail transcript
CallSid string POST body Idempotency key for the call row
From string (E.164) POST body Fallback caller number
To, Called string (E.164) POST body Used, in that order, as the SMS sender on escalation
fam string (UUID) query Family ID; if missing or unknown, responds 200 with no database writes
caller, from string query Context carried from the call flow

Behavior — classifies the transcript against the family's escalation_keywords (default: discharge, hospital, emergency, urgent; matching is case-insensitive substring). Upserts the resbit_calls row keyed on call_sid (Prefer: resolution=merge-duplicates, so Twilio retries cannot duplicate entries), inserts a digest row, and — when the triage result is escalated and the family has founder_phone set — sends one SMS via the Twilio Messages API:

RESBIT ESCALATION — {family}: voicemail from {caller} matched an urgent keyword. "{transcript, truncated to 200 chars}" Call log has the full recording.

The SMS send result is not checked; a failed send is silently ignored.

Response200, text/plain ok.

Errors403 on signature failure.

POST /voice/recording

Recording-URL callback. Attaches the recording to the existing call row.

Parameters consumed

Name Type Source Description
CallSid string POST body Row selector (resbit_calls.call_sid)
RecordingUrl string (URL) POST body Stored with .mp3 appended
RecordingDuration string (integer) POST body Stored as duration_seconds; null when absent

If CallSid or RecordingUrl is missing, the endpoint responds 200 without writing.

Response200, text/plain ok. Errors403 on signature failure.

Configuration

Name Kind Purpose
TWILIO_AUTH_TOKEN secret Signature validation and SMS auth; unset means every signed request is rejected
SUPABASE_SERVICE_KEY secret Service-role database access (RLS bypassed by design)
TWILIO_ACCOUNT_SID var Twilio REST auth for outbound SMS
SUPABASE_URL var Supabase project REST base
PUBLIC_URL var Exact public URL Twilio calls; used to reconstruct the signed URL

Caveats

The worker writes to resbit_calls and resbit_digest_items and reads resbit_numbers, resbit_families, and resbit_providers. Call rows are idempotent on call_sid; digest rows are plain inserts. Timestamps are stored twice: at as a display string in America/New_York and at_ts as ISO-8601. All text interpolated into TwiML is XML-escaped, so caller-supplied values cannot inject TwiML verbs.