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.
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.
GET /healthLiveness probe. Responds before method and signature checks, so it accepts any HTTP method.
Response — 200, 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 /voiceIncoming-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.
Response — 200, 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=…&name=…&caller=…&from=…" method="POST">
<Number url="{PUBLIC_URL}/voice/whisper?fam=…&name=…&caller=…&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/whisperPlayed 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 |
Response — 200, 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>
Errors — 403 on signature failure.
POST /voice/afterDial-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 response — 200, 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>
Errors — 403 on signature failure.
POST /voice/recordedRecording-complete action. Thanks the caller and hangs up; consumes no parameters beyond signature validation.
Response — 200, text/xml containing a <Say> thank-you and <Hangup/>.
Errors — 403 on signature failure.
POST /voice/transcriptionTranscription-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.
Response — 200, text/plain ok.
Errors — 403 on signature failure.
POST /voice/recordingRecording-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.
Response — 200, text/plain ok. Errors — 403 on signature failure.
| 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 |
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.