WhatsApp Business Cloud API Setup

Hermes can connect to WhatsApp through Meta’sofficialWhatsApp Business Cloud API. This is the production-grade path: no Node.js bridge subprocess, no QR codes, no account-ban risk.

In exchange:

If those constraints don’t work for your use case, theBaileys bridge integrationis the alternative — personal account, no public URL needed, but unofficial and ban-prone.

Quick start​

hermes whatsapp-cloud

The wizard walks you through every credential, validates each one as you paste it (catches the #1 setup trap — pasting a phone number into the Phone Number ID field), and prints exact follow-up instructions for the parts that need to happen outside the wizard (starting cloudflared, configuring Meta’s webhook dashboard).

The rest of this page is the manual reference.

Prerequisites​

  1. A Meta Business account. Create one atbusiness.facebook.com.
  2. A Meta app with WhatsApp enabled. See “Creating the Meta app” below.
  3. A way to expose a local port to the public internetwith HTTPS. Cloudflare Tunnel (cloudflared) is recommended — free, no port forwarding, no domain required. ngrok, your own domain with a reverse proxy + TLS, or a VPS with the gateway directly bound to a public IP all work too.
  4. Optional but recommended: ffmpeg onPATHso outbound voice messages render as native WhatsApp voice-note bubbles (green waveform) instead of MP3 audio attachments. Hermes degrades gracefully if absent.

cloudflared PATH

Creating the Meta app​

  1. Go todevelopers.facebook.com/apps→Create App.
  2. Choose use case:”Connect with customers through WhatsApp”→Next.
  3. Pick or create a business portfolio. Review the publishing requirements. Confirm →Create app.
  4. After creation you’ll land onCustomize use case → Connect on WhatsApp → Quickstart. ClickStart using the API→ you’re now on theAPI Setuppage.
  5. Make sure a WhatsApp Business Account (WABA) is linked. If you created a new portfolio in step 3, one was auto-created. Verify in the API Setup page.

You’ll need these values from the dashboard — the wizard prompts for them in this order:

Value Where in dashboard Field shape Notes
Phone Number ID App Dashboard → WhatsApp → API Setup → below the “From” dropdown Numeric, 15-17 digits NOTthe phone number itself. The #1 setup mistake is pasting the actual phone number here.
Access Token App Dashboard → WhatsApp → API Setup → “Generate access token” Starts withEAA, 100+ chars Temp tokens last 24h — see “Permanent token” below for production.
App Secret App Dashboard → Settings → Basic → click “Show” next to App secret 32-character lowercase hex Used to verify incoming webhook signatures. Without it, inbound delivery is refused with 503.
App ID(optional) App Dashboard → Settings → Basic Numeric, 15-16 digits Not required for messaging, useful for analytics.
WABA ID(optional) App Dashboard → WhatsApp → API Setup → near the top Numeric, 15+ digits Not required for messaging, useful for analytics.

EAA

Permanent token (production)​

Temporary access tokens expire after24 hours, which means a token generated today stops working tomorrow. For production deployments use aSystem User permanent token:

  1. Go tobusiness.facebook.com/latest/settings→System users(left sidebar).
  2. Add→ name (e.g.hermes-bot) → role:Admin.
  3. Select the new user →Assign Assets:Select your app → toggleManage appunder Full control.Select your WhatsApp account → toggleManage WhatsApp Business Accountsunder Full control.ClickAssign assets.
  4. Generate tokenwith these permissions:business_managementwhatsapp_business_messagingwhatsapp_business_management
  5. Settoken expiration: Never.
  6. Copy the token → updateWHATSAPP_CLOUD_ACCESS_TOKENin~/.hermes/.env→ restart the gateway.

hermes-bot

business_management whatsapp_business_messaging whatsapp_business_management WHATSAPP_CLOUD_ACCESS_TOKEN ~/.hermes/.env

System User tokens don’t expire unless you explicitly revoke them.

Exposing Hermes to the internet​

The Cloud API delivers inbound messages by HTTPS POST to your webhook URL — that means the Hermes gateway has to be reachable from Meta’s servers. Three common ways:

Free, no port forwarding, works on Windows / macOS / Linux. Runs as a separate process alongside the gateway.

Install:

# Windowswinget install Cloudflare.cloudflared# macOSbrew install cloudflared# Linux# Download the binary from https://github.com/cloudflare/cloudflared/releases

Run a quick tunnel(no Cloudflare account needed — gives you ahttps://.trycloudflare.comURL):

https://<random>.trycloudflare.com

cloudflared tunnel --url http://localhost:8090

Note the printed URL — that’s what you’ll give Meta.

The free quick-tunnel URL changes every time you restartcloudflared. For a stable URL, log in withcloudflared tunnel loginand create a named tunnel. Free Cloudflare accounts get unlimited named tunnels — seeCloudflare’s docsfor the named-tunnel workflow.

cloudflared cloudflared tunnel login

ngrok​

ngrok http 8090

Free tier shows a different URL on each restart. Paid tier gives you a stable subdomain.

Your own domain + reverse proxy​

If you already have a server with a TLS cert (Caddy, nginx, etc.), point a route atlocalhost:8090. This is the most stable option for production but requires existing infrastructure.

localhost:8090

Configuring the webhook on Meta’s side​

Once your tunnel is running:

  1. Note the public URL printed by your tunnel — sayhttps://abc123.trycloudflare.com.
  2. Generate aVerify Token— the wizard does this for you withsecrets.token_urlsafe(32); if you’re configuring manually, run:python-c”import secrets; print(secrets.token_urlsafe(32))”Save it asWHATSAPP_CLOUD_VERIFY_TOKENin~/.hermes/.env.
  3. Start the Hermes gateway:hermes gateway.
  4. In the Meta App Dashboard →WhatsApp → Configuration(orUse cases → Customize → Configurationdepending on UI version) → clickEditon the Webhook section.
  5. Fill in:Callback URL:https://abc123.trycloudflare.com/whatsapp/webhookVerify Token: the string from step 2 (must match exactly)
  6. ClickVerify and save. Meta hits your URL with a GET request, the gateway echoes back the challenge, and Meta marks the webhook as verified.
  7. UnderWebhook fields, clickManage→ subscribe to themessagesfield. This is what tells Meta to actually deliver inbound messages to your webhook.

https://abc123.trycloudflare.com secrets.token_urlsafe(32)

python -c "import secrets; print(secrets.token_urlsafe(32))"

WHATSAPP_CLOUD_VERIFY_TOKEN ~/.hermes/.env hermes gateway

https://abc123.trycloudflare.com/whatsapp/webhook

To verify the loop manually(from a third terminal):

TUNNEL="https://abc123.trycloudflare.com"VERIFY="<your verify token>"# Should print HTTP 200 with body "hello"curl -i "$TUNNEL/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=$VERIFY&hub.challenge=hello"# Health endpoint — should show verify_token_configured: true and app_secret_configured: truecurl "$TUNNEL/health"

Recipient whitelist (Meta-side)​

In development mode (before your app goes through App Review), Meta restricts which numbers your bot can message:

  1. App Dashboard → WhatsApp → API Setup →Todropdown.
  2. ClickManage phone number list.
  3. Add the phone numbers you want to message (yours, your team’s, friendly testers). Meta sends each one a 6-digit verification code via SMS or WhatsApp.

Up to 5 numbers in dev mode. Going to App Review removes this limit.

Allowlist (Hermes-side)​

In addition to Meta’s recipient whitelist, Hermes has its own per-platform allowlist that controlswhich incoming messages the agent processes. Add to~/.hermes/.env:

~/.hermes/.env

# Comma-separated phone numbers, country code, no '+' / spaces / dashesWHATSAPP_CLOUD_ALLOWED_USERS=15551234567,15557654321# Or allow everyone (only safe in combination with Meta's recipient whitelist)# WHATSAPP_CLOUD_ALLOW_ALL_USERS=true

The wizard sets this in step 6. Without an allowlist,every inbound message is denied— this is intentional, so the bot can’t be invoked by random numbers if the recipient whitelist is ever loosened.

Polishing your bot’s WhatsApp profile​

WhatsApp displays aname and profile picturefor your bot in the chat header and contact list. These can’t be set via the Cloud API — they live in Meta’s Business Manager.

Once your bot is working, head tobusiness.facebook.com/wa/manage/phone-numbers, click your phone number, and you’ll find:

What Where Notes
Display name Top of the phone-number page Changes go through Meta’s name-review process (~24–48 hours).
Profile picture Top of the phone-number page Square image, ≥640×640px recommended. Updates immediately.
About / description / website / email / hours / category “Edit profile” button These appear in the info pane when a user taps the bot’s name. Cosmetic.
Verified badge(green checkmark) Business Manager → Security Center → Start Verification Requires Meta’s separate business verification process.

Thehermes whatsapp-cloudwizard prints these links at the end of setup. None of this is required for the bot to work — it’s pure polish for how your bot appears to users.

hermes whatsapp-cloud

Configuration reference​

All settings live in~/.hermes/.env. Required values are inbold.

~/.hermes/.env | Variable | Default | Description | | — | — | — | | WHATSAPP_CLOUD_PHONE_NUMBER_ID | — | The 15-17 digit ID from API Setup.Notthe phone number. | | WHATSAPP_CLOUD_ACCESS_TOKEN | — | Meta access token (starts withEAA). Temp 24h or System User permanent. | | WHATSAPP_CLOUD_APP_SECRET | — | 32-char hex from Settings → Basic. Without it, inbound is refused with 503. | | WHATSAPP_CLOUD_VERIFY_TOKEN | — | Shared secret for the GET handshake. Auto-generated by the wizard. | | WHATSAPP_CLOUD_ALLOWED_USERS | — | Comma-separated wa_ids allowed to message the bot. | | WHATSAPP_CLOUD_ALLOW_ALL_USERS | false | Set totrueto bypass the allowlist. | | WHATSAPP_CLOUD_APP_ID | — | Optional, for future analytics integration. | | WHATSAPP_CLOUD_WABA_ID | — | Optional, for future analytics integration. | | WHATSAPP_CLOUD_WEBHOOK_HOST | 0.0.0.0 | Interface the webhook server binds to. | | WHATSAPP_CLOUD_WEBHOOK_PORT | 8090 | Port the webhook server binds to. Must match the port your tunnel forwards. | | WHATSAPP_CLOUD_WEBHOOK_PATH | /whatsapp/webhook | URL path Meta posts to. | | WHATSAPP_CLOUD_API_VERSION | v20.0 | Meta Graph API version. Only override if a newer version is recommended in Meta’s docs. | | WHATSAPP_CLOUD_HOME_CHANNEL | — | wa_id to use as the bot’s home channel (for cron jobs etc). |

WHATSAPP_CLOUD_PHONE_NUMBER_ID WHATSAPP_CLOUD_ACCESS_TOKEN EAA WHATSAPP_CLOUD_APP_SECRET WHATSAPP_CLOUD_VERIFY_TOKEN WHATSAPP_CLOUD_ALLOWED_USERS WHATSAPP_CLOUD_ALLOW_ALL_USERS false true WHATSAPP_CLOUD_APP_ID WHATSAPP_CLOUD_WABA_ID WHATSAPP_CLOUD_WEBHOOK_HOST 0.0.0.0 WHATSAPP_CLOUD_WEBHOOK_PORT 8090 WHATSAPP_CLOUD_WEBHOOK_PATH /whatsapp/webhook WHATSAPP_CLOUD_API_VERSION v20.0 WHATSAPP_CLOUD_HOME_CHANNEL

You can haveboththe Baileys (whatsapp) and Cloud (whatsapp_cloud) adapters enabled simultaneously, targeting different phone numbers.

whatsapp whatsapp_cloud

Features​

Inbound​

.ogg .txt .md .json .py .csv

Outbound​

**bold** *bold* ~~strike~~ ~strike~ [link](url) link (url)

Interactive UX​

When the agent invokes any of these flows, Hermes uses WhatsApp’s native interactive messages — tap-to-answer buttons instead of “reply with the number” prompts:

clarify ✅ Approve ❌ Deny /approve /deny /reload-mcp ✅ Approve Once 🔒 Always ❌ Cancel

All interactive prompts gracefully degrade to plain text if the buttons fail to render (e.g. on legacy WhatsApp clients).

Read receipts and typing indicator​

Hermes acknowledges inbound messages immediately:

This makes it obvious when the bot has seen your message versus when it’s still working on a response.

Voice messages​

WhatsApp distinguishes between a “voice note” (the green waveform bubble) and a generic audio file attachment. The difference is purely codec: voice notes need to beaudio/oggwithopusencoding.

audio/ogg opus

Hermes TTS produces MP3. Two paths:

winget install Gyan.FFmpeg brew install ffmpeg

You can check whether the gateway found ffmpeg via the health endpoint:

curl http://localhost:8090/health# look for "ffmpeg_present": true

Known limitations​

24-hour conversation window​

Meta only allowsfree-form messageswithin a 24-hour window after the user’s last inbound message. Outside that window, the only thing Meta’s API accepts is a pre-approvedmessage template.

What this means in practice:

131047 delegate_task

Hermes warns the agent about this window in its system prompt, so the model knows to mention it when scheduling delayed messages.

Message-template support (the workaround for outside-window sends) is not yet implemented in Hermes. If you need it, pleaseopen an issue— it’s planned but waiting on a clear demand signal.

Group chats​

The Cloud API has limited group support (capability-tier gated by Meta). Hermes’swhatsapp_cloudadapter currently handlesdirect messages onlyin v1. If you need group chats, use the Baileys bridge.

whatsapp_cloud

Outbound rate limit​

Meta’s default throughput is80 messages/second per business phone number, with upgrades available. Hermes doesn’t currently enforce this client-side — extremely high-volume sends could hit Meta’s limit.

Troubleshooting​

Setup verification fails (“URL couldn’t be validated”) in Meta dashboard​

Almost always one of:

.env ~/.hermes/.env WHATSAPP_CLOUD_VERIFY_TOKEN hermes gateway

graph error 100: Object with ID ‘…’ does not exist​

graph error 100

You pasted your phone number (10-11 digits) intoWHATSAPP_CLOUD_PHONE_NUMBER_IDinstead of the Phone Number ID (Meta’s 15-17 digit internal ID). Re-check the API Setup page — the Phone Number ID is shownbelowthe “From” dropdown.

WHATSAPP_CLOUD_PHONE_NUMBER_ID

The wizard catches this with a validator now, but it’s worth knowing if you’re configuring manually.

graph error 190: Authentication Error​

graph error 190

Your access token is invalid. Subcodes:

subcode 463 subcode 467 business_management whatsapp_business_messaging whatsapp_business_management

graph error 131047: Re-engagement message​

graph error 131047

The 24-hour conversation window expired (see “Known limitations”). Either:

Inbound message:media metadata fetch failed (status=401)​

media metadata fetch failed (status=401)

Same 401 root causes as outbound (graph error 190) — the access token is invalid or expired. Fix the token.

graph error 190

Bot replies appear as raw JSON / tool-call leakage​

Common cause: the toolset configured forwhatsapp_cloudis missing the tools the agent wants to call. Checkhermes tools listand verify the platform is usinghermes-whatsapp(the default Cloud adapter toolset, same as Baileys).

whatsapp_cloud hermes tools list hermes-whatsapp

If the model emits tool-call-shaped text instead of a structured call, it usually means the toolset was effectively empty. Seehermes_cli/platforms.pyfor the platform → default toolset mapping.

hermes_cli/platforms.py

STT (voice note transcription) returns empty / “could not transcribe”​

The defaultstt.provider: localrequirespip install faster-whisper. If you’re a Nous subscriber, you can route STT through Meta’s managed audio gateway instead:

stt.provider: local pip install faster-whisper

hermes config set stt.provider openaihermes config set stt.use_gateway truehermes gateway restart

This uses your Nous Portal access token instead of needing a separate OpenAI key.

Security notes​

WHATSAPP_CLOUD_APP_SECRET /health

Comparison to the Baileys bridge​

  Baileys (hermes whatsapp) Cloud API (hermes whatsapp-cloud)
Account type Personal Business
Setup QR code scan Meta app + WABA + token
Dependencies Node.js + npm Pure Python (httpx + aiohttp)
Process Managed Node subprocess aiohttp webhook server
Public URL needed? No Yes
Account ban risk Yes (unofficial API) No (officially supported)
Inbound Polling Node bridge Webhook POST from Meta
Outbound Local bridge → Baileys HTTPS to graph.facebook.com
Groups Full support DMs only (v1)
24h window No restriction Hard rule — templates required after
Voice notes (out) Native Native with ffmpeg, MP3 fallback otherwise
Read receipts No Yes (blue double-checkmarks)
Typing indicator No Yes (auto-dismisses on response)
Interactive buttons Text fallback only Native (clarify, approval, slash-confirm)
Production use Risky (Meta can ban) Designed for it

hermes whatsapp hermes whatsapp-cloud

Most users running Hermes for personal projects prefer Baileys. Most users running customer-facing bots prefer Cloud API.

See also​