Skip to main content

Knowledge > Integrations > Twilio

Twilio Integration

Twilio provides phone numbers and SMS capabilities for the ChurchWiseAI portfolio. Its primary role is supplying phone numbers that are forwarded via SIP trunk to LiveKit Cloud for the voice agent. It also handles inbound SMS webhooks for lead nurturing and opt-out management, and outbound SMS for WatchTower ops alerts.

Account Info

FieldValue
AccountChurchWiseAI (managed by founder)
CLINo CLI configured — use Twilio Console or REST API
API basehttps://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}/
AuthBasic auth: TWILIO_ACCOUNT_SID:TWILIO_AUTH_TOKEN

No Twilio SDK is installed in churchwiseai-web. All calls use the Twilio REST API directly via fetch. The pewsearch codebase imports the twilio npm package for webhook signature validation only.

How Used Per Product

ProductCodebasePurpose
Voice Agentchurchwiseai-webLegacy phone numbers forwarded via SIP trunk to LiveKit Cloud. New customers use Telnyx numbers.
WatchTower Ops Alertschurchwiseai-webP0 SMS alerts to founder phone (OPS_ALERT_PHONE) when system issues detected
Congregation Care Broadcastschurchwiseai-web + pewsearchOutbound SMS to opted-in care members (via care broadcast API)
Inbound SMSchurchwiseai-webLead nurture: SMS responses to church outreach campaigns. Handles STOP/opt-out, interest replies
Admin Notificationspewsearch/webOutbound SMS to admin phone for update request notifications (currently disabled — 10DLC pending)
Inbound SMSpewsearch/webHandles inbound SMS replies — routes to church info, claim URLs, or opt-out

Key Environment Variables

VariableScopeUsed By
TWILIO_ACCOUNT_SIDServerAll Twilio REST API calls — account identifier
TWILIO_AUTH_TOKENServerAll Twilio REST API calls — Basic auth password. Also used to validate inbound webhook signatures.
TWILIO_FROM_NUMBERServerchurchwiseai-web — source number for ops SMS alerts
TWILIO_SMS_FROMServerchurchwiseai-web (ops-sms.ts) — source number for WatchTower alerts
TWILIO_FROM_NUMBER_CAServerpewsearch/web — Canadian sending number
TWILIO_FROM_NUMBER_USServerpewsearch/web — US sending number
OPS_ALERT_PHONEServerchurchwiseai-web — founder's phone number for P0 alerts
ADMIN_PHONEServerpewsearch/web — admin phone for notifications

Note: Some variable names differ between codebases (TWILIO_FROM_NUMBER vs TWILIO_SMS_FROM). This is a known inconsistency — both exist and serve slightly different purposes.

CLI Patterns & Gotchas

No CLI Configured

There is no Twilio CLI installed. All Twilio operations are done via:

  1. The Twilio Console (dashboard.twilio.com)
  2. Direct REST API calls from code
  3. The /api/admin/provision-number endpoint (founder-auth-gated)

Provisioning a New Church Phone Number

New voice plan customers automatically get a church_voice_agents stub row created by the Stripe webhook. The Stripe webhook also sends the founder an alert email. The founder then:

  1. Buys a new local number in Twilio Console
  2. Calls the provision API to register it:
# POST to /api/admin/provision-number
curl -X POST https://churchwiseai.com/api/admin/provision-number \
-H "Content-Type: application/json" \
-d '{"token": "FOUNDER_TOKEN", "churchId": "uuid", "phoneNumber": "+14695551234"}'
  1. In Twilio Console, configures the number to forward voice calls via SIP trunk to LiveKit Cloud.

The provision API validates E.164 format (+1XXXXXXXXXX), checks for duplicate assignments, and returns the webhook URLs to configure in Twilio.

Webhook URL Configuration

After provisioning a number in Twilio, configure these URLs:

EventURL
Voice (inbound call)Forward via SIP trunk to LiveKit Cloud (see LiveKit docs)
SMS (inbound message)https://churchwiseai.com/api/sms/webhook

Webhook Signature Validation

Inbound Twilio webhooks include an X-Twilio-Signature header. Both codebases validate this:

  • churchwiseai-web (/api/sms/webhook): Uses the twilio npm package's twilio.validateRequest().
  • pewsearch/web (/api/sms/webhook): Same pattern.

The signature is validated against TWILIO_AUTH_TOKEN and the full webhook URL. If validation fails, the endpoint returns 403. This prevents spoofed requests.

Outbound SMS Pattern (No SDK)

Both codebases make direct REST calls without the Twilio SDK:

const credentials = Buffer.from(`${accountSid}:${authToken}`).toString('base64');
const params = new URLSearchParams({ To: to, From: from, Body: message });

const res = await fetch(
`https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`,
{
method: 'POST',
headers: {
Authorization: `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
}
);

PewSearch Outbound SMS — 10DLC Pending

The notifyNewUpdateRequest() function in pewsearch/web/src/lib/notifications.ts has outbound SMS commented out:

// SMS disabled — US 10DLC registration required before Twilio can send outbound SMS.
// Once registered, uncomment the line below:
// await sendAdminSMS(smsBody);

Twilio requires 10DLC registration for US A2P messaging before sending SMS to US numbers. This must be completed in the Twilio Console before enabling outbound SMS in pewsearch.

Inbound SMS Flow (churchwiseai-web)

The /api/sms/webhook handler in churchwiseai-web:

  1. Validates Twilio signature
  2. Parses From and Body from the inbound message
  3. Matches sender to a church via phone number digits (last 10 digits, flexible format matching)
  4. Routes based on message content:
    • STOP / opt-out words → writes email_opt_out=true to premium_churches, also opts out from congregation care; returns TwiML "unsubscribed" reply
    • Interest words (yes, info, sign up, etc.) → returns TwiML with preview URL and claim URL
    • Anything else → returns TwiML with helpful info and contact

TwiML Responses

Both SMS webhooks return TwiML XML responses (Twilio Markup Language), not JSON:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Message>Your reply text here</Message>
</Response>

The Content-Type must be text/xml.

Failure Modes & Recovery

FailureSymptomRecovery
Missing TWILIO_AUTH_TOKENSMS webhook returns 401, ops alerts silently skipAdd env var via `echo "val"
Invalid webhook signatureSMS webhook returns 403Verify the webhook URL in Twilio matches exactly (protocol, domain, path). URL must match what Twilio sends to.
10DLC not registeredOutbound SMS fails with Twilio error 30007Complete 10DLC registration in Twilio Console. US A2P SMS requires this.
Phone number not forwardedChurch receives calls but voice agent not reachedCheck Twilio Console — the number's voice webhook should forward via SIP trunk to LiveKit Cloud. It's a forwarding config, not a TwiML webhook.
Duplicate phone number provisioned409 error from /api/admin/provision-numberCheck church_voice_agents table for existing assignment. Use the DELETE endpoint to remove the old assignment first.
Ops SMS not receivedWatchTower alerts silentCheck TWILIO_SMS_FROM and OPS_ALERT_PHONE env vars. Also verify the Twilio number has SMS capability.

Cost Model / Usage Limits

  • Twilio charges per phone number rented (monthly fee per number) plus per-message and per-minute.
  • Each church on a voice plan requires one dedicated Twilio number.
  • Inbound SMS receiving is typically free; outbound SMS is billed per message.
  • Voice calls: Twilio is a pass-through here — calls are immediately forwarded via SIP trunk to LiveKit Cloud. Twilio billing is per-minute of call duration at standard rates.
  • For US A2P SMS (outbound marketing), Twilio requires 10DLC registration and charges a monthly brand/campaign fee.
  • The ops/collect cron (every 15 min) checks Twilio balance as part of WatchTower monitoring.

See Also

  • LiveKit — receives forwarded calls from Twilio via SIP trunk
  • Vercel — hosts the SMS webhook endpoints
  • Supabase — opt-out state and care member SMS preferences stored here