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
| Field | Value |
|---|---|
| Account | ChurchWiseAI (managed by founder) |
| CLI | No CLI configured — use Twilio Console or REST API |
| API base | https://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}/ |
| Auth | Basic 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
| Product | Codebase | Purpose |
|---|---|---|
| Voice Agent | churchwiseai-web | Legacy phone numbers forwarded via SIP trunk to LiveKit Cloud. New customers use Telnyx numbers. |
| WatchTower Ops Alerts | churchwiseai-web | P0 SMS alerts to founder phone (OPS_ALERT_PHONE) when system issues detected |
| Congregation Care Broadcasts | churchwiseai-web + pewsearch | Outbound SMS to opted-in care members (via care broadcast API) |
| Inbound SMS | churchwiseai-web | Lead nurture: SMS responses to church outreach campaigns. Handles STOP/opt-out, interest replies |
| Admin Notifications | pewsearch/web | Outbound SMS to admin phone for update request notifications (currently disabled — 10DLC pending) |
| Inbound SMS | pewsearch/web | Handles inbound SMS replies — routes to church info, claim URLs, or opt-out |
Key Environment Variables
| Variable | Scope | Used By |
|---|---|---|
TWILIO_ACCOUNT_SID | Server | All Twilio REST API calls — account identifier |
TWILIO_AUTH_TOKEN | Server | All Twilio REST API calls — Basic auth password. Also used to validate inbound webhook signatures. |
TWILIO_FROM_NUMBER | Server | churchwiseai-web — source number for ops SMS alerts |
TWILIO_SMS_FROM | Server | churchwiseai-web (ops-sms.ts) — source number for WatchTower alerts |
TWILIO_FROM_NUMBER_CA | Server | pewsearch/web — Canadian sending number |
TWILIO_FROM_NUMBER_US | Server | pewsearch/web — US sending number |
OPS_ALERT_PHONE | Server | churchwiseai-web — founder's phone number for P0 alerts |
ADMIN_PHONE | Server | pewsearch/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:
- The Twilio Console (dashboard.twilio.com)
- Direct REST API calls from code
- The
/api/admin/provision-numberendpoint (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:
- Buys a new local number in Twilio Console
- 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"}'
- 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:
| Event | URL |
|---|---|
| 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 thetwilionpm package'stwilio.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:
- Validates Twilio signature
- Parses
FromandBodyfrom the inbound message - Matches sender to a church via phone number digits (last 10 digits, flexible format matching)
- Routes based on message content:
- STOP / opt-out words → writes
email_opt_out=truetopremium_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
- STOP / opt-out words → writes
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
| Failure | Symptom | Recovery |
|---|---|---|
Missing TWILIO_AUTH_TOKEN | SMS webhook returns 401, ops alerts silently skip | Add env var via `echo "val" |
| Invalid webhook signature | SMS webhook returns 403 | Verify the webhook URL in Twilio matches exactly (protocol, domain, path). URL must match what Twilio sends to. |
| 10DLC not registered | Outbound SMS fails with Twilio error 30007 | Complete 10DLC registration in Twilio Console. US A2P SMS requires this. |
| Phone number not forwarded | Church receives calls but voice agent not reached | Check 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 provisioned | 409 error from /api/admin/provision-number | Check church_voice_agents table for existing assignment. Use the DELETE endpoint to remove the old assignment first. |
| Ops SMS not received | WatchTower alerts silent | Check 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/collectcron (every 15 min) checks Twilio balance as part of WatchTower monitoring.