Handoff: WhatsApp via Twilio — Railway + Twilio Console Setup
Configure Railway env vars and Twilio webhook so Vitto can send/receive WhatsApp messages
Handoff: WhatsApp via Twilio — Railway + Twilio Console Setup
From: Session 127 (Cursor Agent) To: Browser/Cowork Agent Priority: HIGH — This is the #1 blocker for "Vitto always reachable" vision Estimated Scope: 3 browser tasks, ~10 minutes total
Context
Session 127 verified that the full WhatsApp round-trip code path is complete in backend-vitto-api:
WhatsApp User → Twilio → POST /vitto-api/engine/integrations/whatsapp/webhook
→ ChannelGatewayService.handleTwilioWhatsAppInbound()
→ VittoEngineService.submitTask({ replyTo: 'whatsapp' })
→ 4-phase AI workflow
→ routeTaskResponse() → TwilioService.sendWhatsAppMessage()
→ Twilio API → WhatsApp User
The code is deployed to vitto-api.gograb.space. What's missing: environment variables in Railway and webhook URL in Twilio console.
Task 1: Add Twilio Env Vars to Railway
Steps
- Open Railway dashboard: https://railway.com/project/3990549d-4313-47e4-9682-d17b382bdba2
- Click on the backend-vitto-api service (service ID:
072352ed-2e34-468e-bec9-03f6d299f867) - Go to Variables tab
- Add these variables (click "New Variable" for each):
| Variable | Value |
|---|---|
TWILIO_ACCOUNT_SID |
AC2aa9b6d002861a191a830d2cc25892a8 |
TWILIO_AUTH_TOKEN |
22a240bad17be449642397a127b1fc4b |
TWILIO_NUMBER |
+16073056767 |
TWILIO_WHATSAPP_MODE |
sandbox |
TWILIO_SANDBOX_CODE |
enemy-president |
- Click Deploy (or wait for auto-deploy) to apply the changes
- Wait for the deployment to complete and verify health:
https://vitto-api.gograb.space/vitto-api/health
Verification
After deploy, the health endpoint should return { "status": "ok" }. The Twilio service will log "TwilioService configured" on startup instead of the previous warning about missing credentials.
Task 2: Configure Twilio Sandbox Webhook
Steps
- Open Twilio Console: https://console.twilio.com
- Log in with the account for
AC2aa9b6d002861a191a830d2cc25892a8 - Navigate to: Messaging → Try it out → Send a WhatsApp message
- In the Sandbox Settings section, set:
| Field | Value |
|---|---|
| When a message comes in | https://vitto-api.gograb.space/vitto-api/engine/integrations/whatsapp/webhook |
| Method | POST |
| Status callback URL | (leave empty for now) |
- Click Save
Important Notes
- The webhook URL MUST use HTTPS (Railway provides this automatically via
vitto-api.gograb.space) - The endpoint is
@Public()(no JWT required) — Twilio can't send auth headers - The endpoint accepts form-encoded POST data (Twilio's default format)
Task 3: Test the Round-Trip
Steps
- From your phone, open WhatsApp
- Send a message to +14155238886 (Twilio sandbox number) with the text:
join enemy-president - Wait for the confirmation message from Twilio (should say "You are now connected to the sandbox")
- Send any message, e.g.:
Hey Vitto, what can you do? - Wait for Vitto's response (should arrive within 10-30 seconds depending on LLM processing time)
Expected Behavior
- Twilio receives the WhatsApp message
- Twilio POSTs form-encoded data to
vitto-api.gograb.space/vitto-api/engine/integrations/whatsapp/webhook - Vitto processes it as a
channel-messagetask through the 4-phase workflow - Vitto sends the response back via
TwilioService.sendWhatsAppMessage() - User sees the response in WhatsApp
Troubleshooting
If no response arrives:
- Check Railway logs for the backend-vitto-api service — look for
[ChannelGateway]or[VittoEngine]log entries - Check Twilio Debugger at https://console.twilio.com/us1/develop/sms/errors — shows webhook delivery failures
- Common issues:
- Webhook URL typo — must be exactly
https://vitto-api.gograb.space/vitto-api/engine/integrations/whatsapp/webhook - Env vars not deployed — check the Railway deployment completed successfully
- Sandbox not joined — must send "join enemy-president" first
- 24-hour session window — Twilio sandbox requires interaction within 24h to send messages back
- Webhook URL typo — must be exactly
Railway Service Reference
| Field | Value |
|---|---|
| Project ID | 3990549d-4313-47e4-9682-d17b382bdba2 |
| Environment ID | 1d438b60-52d5-4c89-ba8f-45fff0df308f |
| Service ID (backend-vitto-api) | 072352ed-2e34-468e-bec9-03f6d299f867 |
| Domain | vitto-api.gograb.space |
| Health endpoint | https://vitto-api.gograb.space/vitto-api/health |
| WhatsApp webhook | https://vitto-api.gograb.space/vitto-api/engine/integrations/whatsapp/webhook |
What Was Done in Session 127 (For Context)
Code changes that enable this (already deployed or ready to deploy):
- DLQ retry for channel sends — if the WhatsApp send fails, it's queued for exponential backoff retry via
DeadLetterQueueService - userId propagation — WhatsApp messages now carry the phone number as userId through to memory creation
- Channel response formatting — WhatsApp responses are formatted with 3900-char limit and status emoji
No code changes needed. This handoff is purely configuration.