TWILIO-SETUP-CHECKLIST.md β Provisioning WhatsApp via Twilio
Purpose: Step-by-step guide for the ops/engineering team to stand up Twilio WhatsApp for h.work. Complete these steps in order. Each section assumes the previous one is done.
Related: docs/features/channels.md (WhatsApp section), BLK-016
Current status (2026-05-19): Dev WhatsApp sender
+18566992888(SIDXE5d4de7ff487f3fd6efc37e970205f8f9) is ONLINE. Webhook points at dev API. Display name pending Meta review. Production sender not yet provisioned.
β οΈ One number per environment. As of 2026-05-06 the platform uses ONE WhatsApp number per environment (set via
WHATSAPP_PHONE_NUMBER). All orgs on the env share that number. Senderβorg identity is established by pairing codes generated in the client portal (Settings β WhatsApp); the legacywhatsapp_number_pooltable andwhatsapp_sender_whitelisttable are kept for back-compat but are NOT required for routing. See Sections 4 and 8 for the canonical flow.
Cost Overview (know before you start)β
| Item | Cost |
|---|---|
| Twilio phone number | ~$1.00/month per number (US local) β you only need ONE per environment |
| WhatsApp outbound message (business-initiated) | ~$0.005β0.015 per message (varies by country) |
| WhatsApp inbound message (user-initiated session, 24h window) | Free for first 1,000 conversations/month, then ~$0.005β0.015 |
| Meta Business verification | Free |
| Twilio account | Free (pay as you go); no monthly fee |
Estimate: 1 staging number + 1 production number + ~3,000 messages/month β budget ~$20β50/month.
Section 1: Twilio Account + Subaccount Setupβ
Why subaccounts? Twilio has one Auth Token per account. If staging and production shared one account, any developer with staging access would also have production access. Two subaccounts give you separate credentials β staging credentials can be shared with devs via Railway, production credentials live only in Railway prod and are never seen by developers.
Credential hierarchy:
- Main account (
ACb236...) β your account. Used only to create/manage subaccounts. Never goes in any codebase or Railway env.- Staging subaccount β separate SID + token. Safe to share with developers via Railway staging env.
- Production subaccount β separate SID + token. Goes only in Railway production env. Developers never see this.
- 1.1 Go to
https://www.twilio.com/try-twilioand create a new account using a Humanity Protocol company email (e.g.,ops@humanity.org). - 1.2 Verify your email address via the confirmation email Twilio sends.
- 1.3 Complete identity verification: Twilio will ask for a phone number to verify (use a real company number, not personal). This is required before purchasing phone numbers.
- 1.4 Log in to the Twilio Console at
https://console.twilio.com. - 1.5 Upgrade from the trial account: click "Upgrade Account" in the top banner. Add a payment method. Trial accounts cannot use WhatsApp or send to unverified numbers.
- 1.6 Note your main account SID (starts with
AC) and Auth Token from the Console dashboard. Store these securely β they stay with you only, never in any env file or repo.
Create Staging Subaccountβ
- 1.7 In Twilio Console, click the account name dropdown (top left) β Create Subaccount.
- 1.8 Name it
humanwork-staging. Click Create. - 1.9 Switch into the staging subaccount (click its name in the dropdown). Note the Subaccount SID and Auth Token β these are the staging credentials.
- 1.10 These staging credentials go into Railway staging environment as
TWILIO_SUBACCOUNT_SIDandTWILIO_SUBACCOUNT_TOKEN. Developers can see these.
Create Production Subaccountβ
- 1.11 Switch back to main account. Click dropdown β Create Subaccount.
- 1.12 Name it
humanwork-production. Click Create. - 1.13 Switch into the production subaccount. Note the Subaccount SID and Auth Token.
- 1.14 These production credentials go into Railway production environment only. Never share them with developers. Never commit them to any file.
Section 2: Meta Business Account Setupβ
Meta requires a verified Business Account before you can use the WhatsApp Business API. This is a one-time setup for h.work as a company.
- 2.1 Go to
https://business.facebook.comand sign in with a Facebook account designated for h.work (create one if needed β use a company email, not a personal account). - 2.2 Click "Create Account" β enter:
- Business name:
h.work - Your name: (ops contact name)
- Business email:
ops@humanity.org
- Business name:
- 2.3 Complete Meta Business verification:
- In Business Manager, go to Settings β Business Info β Verification
- Select "Verify with domain" or "Verify with official documents"
- Domain verification: add a
<meta>tag toh.workhomepage or upload a DNS TXT record - Document verification: upload business registration documents (company certificate, etc.)
- Verification typically takes 1β3 business days
- 2.4 Once verified, note the Meta Business Manager ID (found in Settings β Business Info). You will need this when applying for WhatsApp Business API access through Twilio.
Section 3: Apply for WhatsApp Business API via Twilioβ
Twilio acts as a Business Solution Provider (BSP). You apply through them rather than Meta directly.
- 3.1 In Twilio Console, navigate to Messaging β Try it out β Send a WhatsApp message (or go directly to
https://console.twilio.com/us1/develop/sms/whatsapp/senders). - 3.2 Click "Apply for WhatsApp Business API" (or "Request Access").
- 3.3 Twilio will launch the Meta Embedded Signup flow in a modal. This is a Facebook/Meta login flow. Use the same Facebook account connected to your Meta Business Account (Step 2.1).
- 3.4 In the embedded signup flow:
- Connect your Meta Business Account (select
h.workfrom the dropdown) - Create or select a WhatsApp Business Account (WABA) β create a new one named
h.work - Create or select a WhatsApp Business Profile β this is the brand profile users see:
- Display name:
h.work(the verified business; individual Specialist names are set per phone number, not here) - Business category: Choose appropriate category (e.g., "Business Services")
- Business description: Brief description of h.work
- Display name:
- Add a phone number (you can add a placeholder for now β see Section 4 for per-Specialist numbers)
- Complete the embedded signup and return to Twilio Console
- Connect your Meta Business Account (select
- 3.5 Twilio will now show the WhatsApp Sender as "Pending" while Meta processes the request. This typically takes minutes to a few hours for pre-verified businesses; up to 3 business days otherwise.
- 3.6 Once approved, the WABA status in Twilio Console will show "Active".
Section 4: Provision One Number Per Environmentβ
The platform now uses one WhatsApp number per environment. Buy ONE number for staging and ONE for production. Specialist personas (Bob, Nova, Atlas, etc.) all share the same number β they're distinguished at the conversation/Specialist layer, not by phone number.
Important: Buy staging numbers while switched into the staging subaccount. Buy production numbers while switched into the production subaccount.
- 4.0 In Twilio Console, switch to the correct subaccount (staging or production) via the top-left dropdown.
- 4.1 Go to Phone Numbers β Manage β Buy a number (
https://console.twilio.com/us1/develop/phone-numbers/search). - 4.2 Search for a US number. Filter by Voice and SMS capabilities. Buy ONE number per env (~$1/month). Note it in E.164 format (e.g.,
+18556992888). - 4.3 Register the number as a WhatsApp sender:
- Go to Messaging β Senders β WhatsApp Senders β Add Sender
- Select the number β link to your WABA
- Status will show "Pending" briefly, then "Active"
- 4.4 Set the WhatsApp Business Profile (the number is the brand surface for ALL Specialists on this env):
- In Twilio Console β Messaging β Senders β WhatsApp Senders β click the number β Profile
- Set Display name to
h.work(orh.work (staging)for staging) - Set About:
h.work provides expert advisory services. - Set Website:
https://h.work - Submit. Meta reviews display name changes within 24 hours.
- 4.5 Configure the webhook (see Section 6).
- 4.6 Set
WHATSAPP_PHONE_NUMBERin the env's Railway variables to the E.164 number you just bought (Section 7).
Section 5: Specialist Personas (No Per-Number Assignment)β
Specialist personas (Bob, Nova, Atlas, etc.) are platform-level identities β not WhatsApp numbers. All personas on an env share the env's single WHATSAPP_PHONE_NUMBER. Persona identity is carried in the conversation reply text, not by which number sent it.
There is no per-number naming, no pool maintenance schedule, and no recycling workflow. To add a new Specialist, just create the Specialist record in the platform β no Twilio Console action required.
Legacy note: the
whatsapp_number_pooltable and admin endpoints (/admin/whatsapp/pool/*) remain in production for back-compat with existing pool-routed orgs β they are not removed. New orgs do NOT need pool slots assigned because routing readsWHATSAPP_PHONE_NUMBERdirectly, but the pool table stays. The unrelated Baileys QR-pairing flow was the path that was deleted (#672, #1113); do not conflate the two.
Section 6: Configure the Webhookβ
The webhook tells Twilio where to forward inbound WhatsApp messages.
- 6.1 In Twilio Console, go to Phone Numbers β Manage β Active Numbers and click on the env's WhatsApp number.
- 6.2 Scroll to the Messaging section. Under "A Message Comes In":
- Set the method to HTTP POST
- Set the URL to:
https://api.h.work/channels/whatsapp/inbound(production) - (For staging:
https://api-staging-bd3f.up.railway.app/channels/whatsapp/inbound) - (For dev:
https://api-dev-b36f.up.railway.app/channels/whatsapp/inboundβ current dev sender+18566992888points here)
- 6.3 Click Save.
- 6.4 Verify webhook signature validation is enabled in the NestJS handler. The
X-Twilio-Signatureheader is validated usingTWILIO_AUTH_TOKEN(orTWILIO_SUBACCOUNT_TOKENas fallback) before processing any message. Seeapi/src/channels/channels.controller.ts.
Section 7: Environment Variables in Railwayβ
Set these in Railway's project settings for the
apiservice. Never commit these values to git.Credential split: WhatsApp senders are registered on the main account. Outbound sends therefore use the main account's API Key + Secret. Inbound webhook signatures are verified against whichever account owns the sender β
TWILIO_AUTH_TOKEN(main) is preferred, withTWILIO_SUBACCOUNT_TOKENas fallback for subaccount-owned senders.
- 7.1 In the Railway dashboard, open the
apiservice β Variables. - 7.2 Add or update the following variables:
| Variable | Purpose | Where to find it |
|---|---|---|
WHATSAPP_PHONE_NUMBER | The env's single inbound WhatsApp number, E.164 (e.g. +18556992888) | The number you bought + registered as a WhatsApp sender in Section 4 |
TWILIO_ACCOUNT_SID | Main account SID β used for outbound REST API calls | Twilio Console (main account) β Account Info |
TWILIO_API_KEY | Main account API Key SID β used for outbound auth | Twilio Console (main account) β API keys & tokens β Create API key |
TWILIO_API_SECRET | Main account API Key Secret β paired with TWILIO_API_KEY | Shown once when the API key is created |
TWILIO_AUTH_TOKEN | Main account Auth Token β used for inbound signature verification (preferred) | Twilio Console (main account) β Account Info |
TWILIO_SUBACCOUNT_SID | Per-env subaccount SID | Twilio Console β switch into staging/production subaccount |
TWILIO_SUBACCOUNT_TOKEN | Per-env subaccount Auth Token β fallback for signature verification when sender is owned by the subaccount | Twilio Console β subaccount β Account Info |
TWILIO_WEBHOOK_BASE_URL | Public base URL of the API for this env. MUST match the URL Twilio is calling. Used for HMAC signature verification β a mismatch yields 403 Forbidden on every inbound message | Set per environment (dev: https://api-dev-b36f.up.railway.app, staging: https://api-staging-bd3f.up.railway.app, prod: https://api-production-f95e.up.railway.app) |
APP_BASE_URL | Public base URL of the frontend app for this env. Used in outbound user-facing replies (e.g. WhatsApp pairing prompts) to link users back to Settings β WhatsApp | dev: https://dev.h852.work, staging: https://staging.h852.work, prod: https://app.h.work |
- 7.3 Set
WHATSAPP_PHONE_NUMBERseparately on staging and production β each env has its own number. - 7.4 Redeploy the
apiservice after adding variables.
Section 8: End-to-End Testβ
Test before announcing to any client. Use a real phone with WhatsApp installed. The pairing-code flow is now the canonical way to establish sender identity.
- 8.1 Sign in to the client portal as a member of a test Org, navigate to Settings β WhatsApp, and click Generate code. The dashboard displays a
HW-XXXXcode with a 15-minute countdown. - 8.2 Open WhatsApp on your phone, start a new chat with the env's WhatsApp number (
WHATSAPP_PHONE_NUMBER). - 8.3 Send the pairing code exactly as shown (e.g.,
HW-4829). Case is ignored. - 8.4 Within seconds you should receive a confirmation reply: "Connected to
{OrgName}. Reply here anytime." The Settings page should now list your phone under Linked numbers. - 8.5 Send a normal message (e.g.,
Test message from [your name]). In Railway logs, confirmWhatsApp inbound: from=+1... to=+1... sid=.... - 8.6 Confirm the message appears in the Expert Queue (or auto-replies if the org's confidence threshold is met).
- 8.7 From the Expert Workspace, approve a reply and send it. Confirm it arrives on your WhatsApp from the same pool number.
- 8.8 Test the unknown-sender path: send a normal (non-code) message from a phone that is NOT linked to any org. Confirm you receive the fallback reply pointing to the dashboard and
https://app.{HWORK_DOMAIN}/client/settings/profile. - 8.9 Test invalid/expired-code path: send
HW-XXXXfor a code that was never issued (or has expired). Confirm you receive the "not recognised, generate a new one from the dashboard" reply, and the original code (if any) is NOT consumed. - 8.10 Test re-pair guard: from a phone already linked to Org A, text a fresh code generated from Org B. Confirm you receive the "already connected, unlink at /client/settings/profile" reply and the new code is not burned.
- 8.11 Test media on the linked phone: send an image, a document, and a voice note. Confirm each arrives in the Expert Queue and the voice note is transcribed.
Section 9: Onboarding a New Specialist (No Twilio Action Required)β
Specialists no longer get their own WhatsApp number. All Specialists on an env share
WHATSAPP_PHONE_NUMBER. Twilio Console requires no per-Specialist action.
For each new Specialist:
- 9.1 In the AM dashboard, navigate to Specialists β Create Specialist.
- 9.2 Pick a persona name (Bob, Nova, Atlas, β¦). This is a platform-level identity used in conversation replies, not a phone number.
- 9.3 Do not configure a sender whitelist for the Org. The whitelist is deprecated. Instead, share the onboarding instructions with the client team: each user signs in to the portal, opens Settings β WhatsApp, generates an
HW-XXXXcode, and texts it to the env's WhatsApp number. Their phone is linked on first message; future messages route automatically. (See Section 8 for the end-to-end flow.)
Troubleshootingβ
| Problem | Likely cause | Fix |
|---|---|---|
| Webhook not receiving messages | Wrong URL in Twilio Console, or API not deployed | Check Railway logs; re-verify webhook URL in Twilio Console |
403 Forbidden on webhook | Twilio signature validation failing | Confirm TWILIO_AUTH_TOKEN (or TWILIO_SUBACCOUNT_TOKEN if the sender lives on the subaccount) is correct in Railway env vars, and that TWILIO_WEBHOOK_BASE_URL matches the public URL Twilio is calling |
| Display name shows "h.work" β expected | All Specialists share the env's number, so the WABA display name is h.work, not the persona name. Persona identity is carried in the conversation reply, not on the WhatsApp profile. Note: display name pending Meta review as of 2026-05-19. | |
| Auto-reply loop | The env's own number ended up linked back to itself | Add guard: skip messages where From == To; never insert WHATSAPP_PHONE_NUMBER into whatsapp_linked_numbers |
| WABA status "Pending" for more than 3 days | Meta verification delay | Contact Twilio support: https://help.twilio.com/ |
| Message delivered but not received in API | Railway deploy not current; wrong webhook URL | Redeploy API; re-check webhook URL in Twilio Console |
Created: 2026-04-30 Author: Platform team Related: docs/features/channels.md (WhatsApp section), BLK-016