# Setting up WhatsApp

Choose a connection mode first. Official API means entering your own Meta phone number ID, WhatsApp Business account ID, access token and app secret, then pointing Meta's webhook at Connect. QR-linked means accepting Meta's coexistence warning and running Embedded Signup, which is not switched on in production yet. Either way, set the channel's autonomy mode and then message the number from a phone you hold.

- **Status:** Available
- **Audience:** both
- **Channels:** whatsapp
- **In the app:** #/whatsapp-setup, #/whatsapp, #/autonomy
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/whatsapp/channel-setup/

## Decide which mode you are connecting

Official API is a number registered on the WhatsApp Business Platform under the business's own Meta app. QR-linked is Meta's coexistence route: the business keeps using the WhatsApp Business app on the phone, scans a QR code inside Meta's Embedded Signup, and the same number is then also served by the Cloud API. Both end up in the same tables and behave the same afterwards, so the choice is about who owns the Meta app and how much automatic sending you need.

|  | Official API | QR-linked (coexistence) |
|---|---|---|
| Who owns the Meta app | The business | Connect's own Meta app, which must be a Tech Provider / Solution Partner app with Embedded Signup |
| The phone keeps its WhatsApp Business app | No | Yes — and its replies arrive in Connect as echoes |
| Automatic sends per number per hour | 120 | 20 |
| Extra gate before setup can start | None | The warning at `#/whatsapp-setup` must be ticked and is audit-logged |
| Usable in production today | Yes | No — the button reports that QR linking is not switched on |

One number belongs to one workspace. If a phone number ID is already connected somewhere else on the deployment, `whatsapp.connect` refuses it with `number_taken` rather than handing the second workspace the first one's conversations. A workspace that already has an Official API number connected must disconnect it before the QR route will accept consent.

## Connecting an Official API number

1. Open `#/whatsapp-setup` and enter the phone number ID, the WhatsApp Business account ID, the access token and the app secret.
   - Result: The token and the secret are encrypted at rest and never displayed again. A screen showing a masked value is telling you something is set, not what it is. The account is left at `pending`.
2. Copy the webhook URL the screen shows — one address, `/api/whatsapp/webhook`, shared by every workspace — and the verify token Connect generated for you into Meta's webhook configuration for the WhatsApp Business account.
   - Result: Meta calls the URL with `hub.mode=subscribe`; `verify_handshake` returns the challenge only for a verify token that matches, and stamps `webhook_verified_at`. Until this is done the channel is configured and deaf.
3. Press Verify.
   - Result: `verify_connection` asks Meta about the number itself and stores the display number, the verified name and Meta's quality rating. Only then does the account become `connected`; a rejected credential leaves it at `error` with what Meta said, minus anything sensitive.
4. Sync templates, and set the autonomy mode for `whatsapp` on [What Connect may do](/docs/autonomy/).
   - Result: Templates are read from Meta with their approved names, languages and categories; a name Meta has not approved cannot be sent later. A channel left `off` receives messages and answers nothing.
5. Send one message to the business number from your own phone.
   - Result: Within seconds the conversation appears on `#/whatsapp` attached to a Person, the 24-hour window opens, and a reply is drafted, held or sent according to the policy. That is the whole chain proved.

> **Note** Test from a number that is not already a contact in the workspace if you also want to watch person resolution create a record. A number Connect already knows proves the webhook and tells you nothing about matching.

## The QR route, and where it stops today

The QR flow starts with a warning that has to be accepted word for word. `record_qr_consent` compares what was submitted against the stored text, refuses anything else with `consent_required`, and writes an audit record holding a hash of the warning, who accepted it and when. The number is then marked QR-linked, which is what puts the standing risk line on the status wherever the connection shows.

After consent the screen would hand over to Meta's Embedded Signup in the browser. In production it does not: `qr_availability` reports which of `META_APP_ID`, `META_ES_CONFIG_ID`, `META_APP_SECRET` and `META_WEBHOOK_VERIFY_TOKEN` are missing, and `complete_qr` refuses with `qr_unavailable`. Meta allows coexistence only through a Tech Provider or Solution Partner app with Embedded Signup, and that application has not been made. The warning, the consent and the audit entry work; the button after them says QR linking is not switched on.

> **Careful** QR-linked is Meta's own coexistence route, not a scraped WhatsApp Web session, and Connect contains no web automation or device emulation. The warning is kept anyway: the number is somebody's phone as well, and Meta can still restrict or block it.

## Choosing the first autonomy mode

Most workspaces should start WhatsApp on `ask_before_send` rather than `autonomous`, for a reason particular to this channel: WhatsApp replies are read within minutes and are hard to contextualise afterwards. Reading the first two dozen drafts in Needs You tells you what your knowledge and memory are missing far faster than reading them after they have gone out.

Autonomy resolves at four scopes — contact, endpoint, channel, workspace — and the narrowest wins. A workspace running WhatsApp autonomously can still hold every reply to one particular customer by setting a rule against that contact.

## When setup looks finished and is not

**Credentials saved, no messages arriving** — The webhook was never registered with Meta, or was registered against a different environment, or `webhook_verified` is still false. [WhatsApp messages are not arriving](/docs/troubleshooting/whatsapp-not-receiving/) works through this in order.
**Verify passes, sends fail** — The access token is valid for reading the number and not for messaging it, or it has since been rotated. Inbound keeps arriving either way, because the webhook does not use the token.
**Messages arrive, no reply is drafted** — The channel is `off`, the conversation is paused or taken over, or the workspace runtime is switched off. All three are deliberate states, and all three look like a fault.
**A draft appears and never sends** — `draft_only`, or the 24-hour window has closed and only an approved template may go out. The second reason is reported as a refused send, not a hold.
**Everything works for you and not for a colleague** — Their session is a different workspace, or they lack permission on the channel. Nothing about a WhatsApp number is global.

Setup is finished when an inbound message produces a canonical conversation and an outbound message reaches your phone. Those two observations are the acceptance test; anything short of both leaves half the chain unproven.

## Questions

### Can one workspace run two WhatsApp numbers?

Not at once. `account_for_workspace` resolves one WhatsApp account per workspace, and connecting a second number replaces the first. The number itself is the endpoint key autonomy rules are written against.

### Do I have to re-enter credentials when Meta rotates a token?

Yes. Connect holds the value you gave it and has no way to learn a new one on its own. A rotated token shows up as sends failing while inbound messages keep arriving, because the webhook is authenticated by the app secret rather than by the token.

### Can I switch a connected number from Official API to QR-linked?

Only by disconnecting first. `record_qr_consent` refuses with `official_connected` while Official API credentials are present, so that one number is never half one mode and half the other. In production the QR route then stops at Embedded Signup regardless.

### Does the test message count against my allowance?

An outbound reply is metered like any other on a customer plan — see [Plan and usage](/docs/account/). The Owner workspace has no plan and no allowance, which is one reason a test that passes for the Owner is not proof the channel works for a tenant.

## Related

- [WhatsApp in Connect](https://connectbyjbrh.com/docs/whatsapp/)
- [Receiving a WhatsApp message](https://connectbyjbrh.com/docs/whatsapp/inbound-webhook/)
- [Connecting WhatsApp, end to end](https://connectbyjbrh.com/docs/workflows/whatsapp-setup-workflow/)
- [WhatsApp messages are not arriving](https://connectbyjbrh.com/docs/troubleshooting/whatsapp-not-receiving/)
- [What Connect may do](https://connectbyjbrh.com/docs/autonomy/)
- [Configuring a channel, end to end](https://connectbyjbrh.com/docs/workflows/channel-configuration/)

## What this page is based on

- `backend/app/whatsapp.py` — `connect`, `verify_connection`, `verify_handshake`, `record_qr_consent`, `qr_availability`, `complete_qr`
- `backend/app/whatsapp_api.py` — `WEBHOOK_PATH`, the customer and Owner routers
- Connect capability registry (docs-source/facts.py) — `WHATSAPP`
- `docs-source/sources/GENERAL.md` §5 — autonomy modes and scopes
