Skip to main content

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 (SID XE5d4de7ff487f3fd6efc37e970205f8f9) 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 legacy whatsapp_number_pool table and whatsapp_sender_whitelist table 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)​

ItemCost
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 verificationFree
Twilio accountFree (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-twilio and 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_SID and TWILIO_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.com and 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
  • 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 to h.work homepage 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.work from 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
    • 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
  • 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 (or h.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_NUMBER in 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_pool table 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 reads WHATSAPP_PHONE_NUMBER directly, 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 +18566992888 points here)
  • 6.3 Click Save.
  • 6.4 Verify webhook signature validation is enabled in the NestJS handler. The X-Twilio-Signature header is validated using TWILIO_AUTH_TOKEN (or TWILIO_SUBACCOUNT_TOKEN as fallback) before processing any message. See api/src/channels/channels.controller.ts.

Section 7: Environment Variables in Railway​

Set these in Railway's project settings for the api service. 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, with TWILIO_SUBACCOUNT_TOKEN as fallback for subaccount-owned senders.

  • 7.1 In the Railway dashboard, open the api service β†’ Variables.
  • 7.2 Add or update the following variables:
VariablePurposeWhere to find it
WHATSAPP_PHONE_NUMBERThe 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_SIDMain account SID β€” used for outbound REST API callsTwilio Console (main account) β†’ Account Info
TWILIO_API_KEYMain account API Key SID β€” used for outbound authTwilio Console (main account) β†’ API keys & tokens β†’ Create API key
TWILIO_API_SECRETMain account API Key Secret β€” paired with TWILIO_API_KEYShown once when the API key is created
TWILIO_AUTH_TOKENMain account Auth Token β€” used for inbound signature verification (preferred)Twilio Console (main account) β†’ Account Info
TWILIO_SUBACCOUNT_SIDPer-env subaccount SIDTwilio Console β†’ switch into staging/production subaccount
TWILIO_SUBACCOUNT_TOKENPer-env subaccount Auth Token β€” fallback for signature verification when sender is owned by the subaccountTwilio Console β†’ subaccount β†’ Account Info
TWILIO_WEBHOOK_BASE_URLPublic 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 messageSet 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_URLPublic 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 β†’ WhatsAppdev: https://dev.h852.work, staging: https://staging.h852.work, prod: https://app.h.work
  • 7.3 Set WHATSAPP_PHONE_NUMBER separately on staging and production β€” each env has its own number.
  • 7.4 Redeploy the api service 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-XXXX code 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, confirm WhatsApp 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-XXXX for 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-XXXX code, 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​

ProblemLikely causeFix
Webhook not receiving messagesWrong URL in Twilio Console, or API not deployedCheck Railway logs; re-verify webhook URL in Twilio Console
403 Forbidden on webhookTwilio signature validation failingConfirm 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" β€” expectedAll 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 loopThe env's own number ended up linked back to itselfAdd guard: skip messages where From == To; never insert WHATSAPP_PHONE_NUMBER into whatsapp_linked_numbers
WABA status "Pending" for more than 3 daysMeta verification delayContact Twilio support: https://help.twilio.com/
Message delivered but not received in APIRailway deploy not current; wrong webhook URLRedeploy API; re-check webhook URL in Twilio Console

Created: 2026-04-30 Author: Platform team Related: docs/features/channels.md (WhatsApp section), BLK-016