WhatsApp is the reason a lot of teams self-host Chatwoot: one shared inbox where several agents answer the business number, with no per-seat fee. The official way to connect it is Meta's WhatsApp Cloud API, which Chatwoot supports natively.
Good news if you're on Railway's Hobby plan: unlike email, WhatsApp works there. Railway blocks outbound SMTP below Pro, but the Cloud API is plain HTTPS in both directions — Chatwoot calls Meta's Graph API, and Meta calls a webhook on your Chatwoot domain.
This guide covers the setup and, more importantly, three self-hosting details that let Chatwoot show "connected" while incoming messages never appear.
Before you start
You need:
- A self-hosted Chatwoot reachable over public HTTPS (the default
*.up.railway.appdomain is fine). - A Meta Business portfolio and a Meta developer app with the WhatsApp use case.
- A phone number that is not currently active in the WhatsApp or WhatsApp Business phone app. Chatwoot's manual setup doesn't support coexistence with the app.
- Admin access in Chatwoot.
Two things to know about the official API:
- The 24-hour window. You can reply freely within 24 hours of the customer's last message. After that, Meta only lets you start the conversation again with an approved message template. Chatwoot syncs approved templates from your WhatsApp Business Account automatically.
- Unofficial bridges are a different thing. Tools that drive WhatsApp Web (WAHA, Baileys-based APIs) skip Meta's approval process, but they're unofficial and numbers can get banned. This guide uses the official API only.
The three self-hosting details that break WhatsApp
1. FRONTEND_URL becomes your webhook URL
Chatwoot builds the WhatsApp webhook from FRONTEND_URL:
<FRONTEND_URL>/webhooks/whatsapp/<phone-number>
and registers that URL with Meta when you create the inbox. If FRONTEND_URL is wrong, empty, or still the Railway domain after you moved to a custom domain, Meta delivers messages to the wrong place and nothing arrives.
Set FRONTEND_URL to the public HTTPS URL agents actually use before creating the inbox. If you change domains later, update FRONTEND_URL, redeploy, and re-run the webhook setup from the inbox (or update the callback URL in your Meta app).
2. Sidekiq must be running
When Meta calls the webhook, Chatwoot's web server accepts the request and hands it to a background job (Webhooks::WhatsappEventsJob, on the low queue). Sidekiq is what turns that job into a conversation.
So if Sidekiq isn't running, Meta sees a successful delivery, Chatwoot looks healthy, and no messages appear — they're sitting in Redis. Before debugging Meta settings, check that Sidekiq is alive: super-admins can open /monitoring/sidekiq and look for a growing low queue.
3. Media needs persistent storage
Photos, voice notes and documents customers send are downloaded and stored through Rails Active Storage. With the default local storage and no volume, they vanish on the next redeploy and old conversations fill with broken images. Mount a volume at /app/storage, or configure S3/R2, before you go live.
Step-by-step: manual Cloud API setup
This is Chatwoot's "manual flow", which works on every self-hosted install.
1. Start the inbox in Chatwoot. Go to Settings → Inboxes → Add Inbox → WhatsApp and choose WhatsApp Cloud. If a Quick setup screen appears, use the manual setup flow link at the bottom.
2. Prepare the Meta app. In Meta for Developers, create (or open) an app, choose the use case for connecting with customers through WhatsApp, and select the business portfolio that owns the number.
3. Copy two IDs. In the app's WhatsApp API Setup page, register your number if needed, then copy the Phone Number ID and the WhatsApp Business Account ID. They're different numbers — mixing them up is the most common error. Paste both into Chatwoot and continue.
4. Create a permanent token. Temporary tokens from the API Setup page expire within a day; use a system user instead:
- Meta Business Settings → Users → System users → create an admin system user (or reuse one).
- Assign it your Meta app and your WhatsApp Business Account with full control.
-
Generate token → select the app → expiry Never → permissions
whatsapp_business_managementandwhatsapp_business_messaging. - Copy it immediately (Meta shows it once) and paste it into Chatwoot as the permanent access token.
5. Verify and create. Chatwoot checks number access, template access, the webhook callback and the webhook subscription, and configures the webhook in Meta for you. When all four show Verified, create the inbox and add agents.
If the webhook steps fail, use Retry webhook setup. If they keep failing, the system user probably lacks full control of the WhatsApp Business Account. As a fallback, copy the Webhook URL and Verification Token from Chatwoot, paste them under WhatsApp → Configuration in your Meta app, verify, and subscribe to the messages field.
If template access shows as not verified, the token is missing whatsapp_business_management: messages still work, templates won't sync.
Optional: Quick setup (embedded signup)
Chatwoot Cloud customers get a one-click Connect with WhatsApp Business button. Self-hosted installs can enable it too, but only after the instance admin configures their own Meta app for it. In Super Admin → App Config → WhatsApp Embedded set:
-
WHATSAPP_APP_ID— your Meta app ID -
WHATSAPP_CONFIGURATION_ID— from Facebook Login for Business → Configurations, using the WhatsApp Embedded Signup variation -
WHATSAPP_APP_SECRET— App Settings → Basic
The button stays hidden until all three are set. It's worth it if you'll onboard several numbers or clients; for one number, the manual flow is quicker.
Testing it
- From a personal phone, message the business number.
- The conversation should appear in Chatwoot within seconds.
- Reply from Chatwoot; the message arrives on the phone.
- Send a photo, then redeploy the Chatwoot service and check the photo still loads.
Nothing arrived? Check in this order: Sidekiq is running → FRONTEND_URL matches your public domain → the Meta app's webhook is subscribed to messages → the token hasn't been revoked (update it in the inbox's Configuration tab if you replaced it).
Running Chatwoot on Railway for this
If you don't have a Chatwoot instance yet, I maintain a Railway template that's set up for exactly this: Chatwoot with Sidekiq supervised in the same container, Postgres with pgvector, Redis, and a volume at /app/storage so WhatsApp media survives redeploys. WhatsApp, live chat and the other social channels work on the Hobby plan; email notifications need Pro.
(Disclosure: I earn a share of Railway usage from deployments of this template. Everything above applies to any self-hosted Chatwoot.)
For the rest of the setup — email, storage, upgrades, backups — see Self-Hosting Chatwoot on Railway: What Actually Breaks.
Checklist
- [ ]
FRONTEND_URLis your final public HTTPS domain before creating the inbox - [ ] Sidekiq running (check
/monitoring/sidekiq) - [ ] Volume at
/app/storageor S3/R2 for media - [ ] Number not active in the WhatsApp phone app
- [ ] Phone Number ID and WhatsApp Business Account ID not swapped
- [ ] Permanent system-user token, expiry Never, both WhatsApp permissions
- [ ] All four checks Verified; webhook subscribed to
messages - [ ] Templates approved for messaging customers after 24 hours
Questions or something out of date? Say so in the comments — I'll update it.
Top comments (0)