WhatsApp Business App Coexistence (Phone + CRM) β
Runbook for using one WhatsApp Business number on your Android phone and in the Aldilaijan CRM (Flutter / web Comms inbox) via Metaβs official Coexistence onboarding path.
Supabase project:
jrsgosnnyjonxaesqtln
Webhook:https://jrsgosnnyjonxaesqtln.supabase.co/functions/v1/whatsapp-webhook
Overview β
| Surface | How it connects |
|---|---|
| WhatsApp Business app (Android) | Meta Coexistence β number stays on the phone |
CRM inbox (/comms, WhatsApp tab) | Meta Cloud API β whatsapp-webhook + send-whatsapp-message |
| Phone-sent replies in CRM | Meta smb_message_echoes webhook β whatsappSmbEchoes.ts |
You cannot link the phone app to the CRM without Cloud API. Coexistence is the supported way to use both.
π‘ Interactive Sequence Diagram: See
docs/architecture/whatsapp-coexistence.html(Spec) for the verified interactive sequence diagram with dark/light mode and trace animation.
flowchart LR
subgraph phone [Android phone]
BizApp[WhatsApp Business app]
end
subgraph meta [Meta Cloud API]
Graph[Graph API + Webhooks]
end
subgraph backend [Supabase]
Webhook[whatsapp-webhook]
Send[send-whatsapp-message]
DB[(whatsapp_* tables)]
end
subgraph crm [Flutter / Web CRM]
Inbox[Comms WhatsApp inbox]
end
Customer[Customer] --> BizApp
Customer --> Graph
BizApp --> Graph
Graph --> Webhook
Webhook --> DB
Inbox --> Send
Send --> Graph
DB --> InboxPath A vs Path B (pick one per number) β
| Standard Cloud API | Coexistence (this doc) | |
|---|---|---|
| Phone app | Number must be off WhatsApp / Business app (βcleanβ) | Keep WhatsApp Business app active |
| Onboarding | Add number in Meta Business Manager only | Embedded Signup β Connect your existing WhatsApp Business app + QR scan |
| CRM outbound | Yes | Yes |
| Phone outbound in CRM history | N/A | Yes β requires smb_message_echoes subscription (implemented) |
| Groups / broadcast lists | N/A on phone after migrate | Not synced; broadcast lists become read-only |
See also WHATSAPP_HERMES_ADMIN.md for the exec Hermes admin line (standard or coexistence).
1. Meta onboarding (Coexistence) β
Prerequisites β
- WhatsApp Business app (not consumer WhatsApp), version 2.24.17+.
- Number used on the app for at least 7 days (30β60 days recommended).
- Meta Business portfolio + WhatsApp Business Account (WABA) with Embedded Signup access.
- Kuwait (+965): eligibility is decided in Metaβs signup UI at onboarding time.
Steps β
- Open Meta Embedded Signup (via your Business Manager / BSP flow).
- On the WABA step, choose Connect your existing WhatsApp Business app (Coexistence).
- Enter the phone number currently registered in the Business app.
- Scan the QR code with WhatsApp Business on Android.
- Optionally sync contacts and up to 6 months chat history.
- Copy the Meta
phone_number_idfor the number. - Generate a permanent System User access token with
whatsapp_business_messaging(and related) permissions.
Supabase secrets β
Set per entity (aldilaijan, khobara, aldilaijan_exec, personal):
supabase secrets set WHATSAPP_PHONE_NUMBER_ID_<ENTITY>="<phone_number_id>" --project-ref jrsgosnnyjonxaesqtln
supabase secrets set WHATSAPP_ACCESS_TOKEN_<ENTITY>="<permanent_token>" --project-ref jrsgosnnyjonxaesqtlnFor the solo personal line (+96562224000), use WHATSAPP_PHONE_NUMBER_ID_PERSONAL / WHATSAPP_ACCESS_TOKEN_PERSONAL. Keep WHATSAPP_PHONE_NUMBER_ID_ALDILAIJAN on the office line (+96522466861) only.
Line β entity map β
| Number | Entity | Secret slot | Business |
|---|---|---|---|
+96522466861 | aldilaijan | WHATSAPP_PHONE_NUMBER_ID_ALDILAIJAN | Brokerage β buyer requests, owner listings |
+96522476375 | khobara | WHATSAPP_PHONE_NUMBER_ID_KHOBARA | Valuation β appraisal requests |
+96562224000 | personal | WHATSAPP_PHONE_NUMBER_ID_PERSONAL | Owner's personal line β never a business intake |
The entity a message gets is derived from the receiving phone_number_id, and it decides the inbox tab, the whatsapp_ai_config row, and which WhatsApp Flow may be offered (see docs/WHATSAPP_FLOWS_SETUP.md). The two businesses are therefore not interchangeable: a valuation enquiry that arrives on the brokerage number is tagged aldilaijan and never reaches the valuation flow.
Until 2026-08-13 the Flutter contact screen pointed Khobara's "WhatsApp us" at 96562224000 β the personal line β so every valuation enquiry from the app landed in the personal inbox under entity personal. It now uses 96522476375.
Or use Dashboard β Edge Functions β Secrets.
Webhook subscription (Meta App β WhatsApp β Configuration) β
| Setting | Value |
|---|---|
| Callback URL | https://jrsgosnnyjonxaesqtln.supabase.co/functions/v1/whatsapp-webhook |
| Verify token | WHATSAPP_WEBHOOK_VERIFY_TOKEN (existing secret) |
| Subscribed fields | messages, smb_message_echoes, message status fields you already use |
Deploy the updated webhook after code changes:
supabase functions deploy whatsapp-webhook --project-ref jrsgosnnyjonxaesqtlnVerify coexistence mode (Graph API) β
curl -G "https://graph.facebook.com/v21.0/<PHONE_NUMBER_ID>" \
-d "fields=is_on_biz_app,platform_type" \
-d "access_token=<ACCESS_TOKEN>"Expected:
{ "is_on_biz_app": true, "platform_type": "CLOUD_API", "id": "<PHONE_NUMBER_ID>" }Ongoing phone hygiene β
- Open WhatsApp Business on the phone at least every 13β14 days.
- Do not uninstall the Business app after linking.
- Companion devices are unlinked during onboarding; re-link only supported companions (avoid Windows/WearOS for business messaging).
2. Solo manager access (CRM only you) β
CRM access is controlled by Supabase user_roles, separate from who holds the phone.
Grant WhatsApp to one user β
Replace placeholders with your user_id and run in Supabase SQL editor:
-- Grant whatsapp_agent to a single manager (idempotent)
insert into user_roles (user_id, role)
values ('<YOUR_USER_UUID>', 'whatsapp_agent')
on conflict do nothing;
-- Optional: admin for full Comms / settings
-- insert into user_roles (user_id, role)
-- values ('<YOUR_USER_UUID>', 'admin')
-- on conflict do nothing;Revoke others (optional lock-down) β
-- Remove whatsapp_agent from everyone except you
delete from user_roles
where role = 'whatsapp_agent'
and user_id <> '<YOUR_USER_UUID>';Verify RLS / access β
- Sign in as your user β open Comms β WhatsApp β inbox loads, send works.
- Sign in as a staff user without
whatsapp_agentβ should not see send/inbox actions (RLS + route guards onsend-whatsapp-message). - Assignable users list uses
get_whatsapp_assignable_users(_entity); with solo role, only you appear.
Phone access: whoever has the Android device can reply from WhatsApp Business. Use device lock and avoid extra linked devices.
3. Smoke test (dual surface) β
Run after secrets, webhook subscription, and function deploy.
| Step | Action | Expected |
|---|---|---|
| A | Customer sends WhatsApp to your business number | Row in CRM inbox (direction = inbound); appears on phone |
| B | Reply from CRM thread | Customer receives message; appears on phone |
| C | Reply from Android Business app | Customer receives message; CRM thread shows outbound (echo handler) |
| D | Graph API is_on_biz_app check | true + CLOUD_API |
SQL checks β
-- Recent webhook traffic for your phone_number_id
select entity, phone_number_id, message_count, message_types, sender_phones, created_at
from whatsapp_webhook_events
where phone_number_id = '<PHONE_NUMBER_ID>'
order by created_at desc
limit 10;
-- Last messages on a conversation (mix of CRM + phone outbound)
select direction, content, message_type, sent_by, whatsapp_message_id, created_at
from whatsapp_messages
where conversation_id = '<CONVERSATION_UUID>'
order by created_at desc
limit 20;
-- Phone-originated outbound: sent_by is null, direction outbound, has whatsapp_message_id
select id, content, whatsapp_message_id, created_at
from whatsapp_messages
where conversation_id = '<CONVERSATION_UUID>'
and direction = 'outbound'
and sent_by is null
order by created_at desc;If step C fails but A/B work β Meta webhook missing smb_message_echoes subscription or whatsapp-webhook not deployed with echo handler.
6. Personal vs office line mixed in one inbox tab β
Symptom: Your personal Business number (+96562224000) and the Aldilaijan office line (+96522466861) appear under the same Aldilaijan WhatsApp tab.
Root cause (most common): Both numbers share the same CRM entity (aldilaijan). That happens when:
- The personal Meta
phone_number_idwas saved intoWHATSAPP_PHONE_NUMBER_ID_ALDILAIJAN(office slot), or - The personal id was not mapped at all and the webhook defaulted inbound traffic to
aldilaijan(fixed in code β unmapped ids are now skipped), or - The public contact page pointed βWhatsApp usβ at
62224000while the office Cloud API line is22466861(Flutter contact screen now uses22466861for Aldilaijan).
Fix checklist
| Step | Action |
|---|---|
| 1 | Meta Business Manager β WhatsApp β API Setup β confirm two numbers with two different phone_number_id values |
| 2 | Supabase secrets: WHATSAPP_PHONE_NUMBER_ID_ALDILAIJAN = office 22466861 id; WHATSAPP_PHONE_NUMBER_ID_PERSONAL + WHATSAPP_ACCESS_TOKEN_PERSONAL = personal 62224000 id + token |
| 3 | Deploy webhook: supabase functions deploy whatsapp-webhook --project-ref jrsgosnnyjonxaesqtln |
| 4 | CRM inbox β filter chip Personal line (or All) β office tab (Brand / Aldilaijan) should no longer receive new personal traffic |
| 5 | Retag old rows: tool/ops/whatsapp_retag_personal_entity.sql |
Quick SQL β which line is which in webhook traffic:
select phone_number_id, entity, count(*) as events, max(created_at) as last_seen
from whatsapp_webhook_events
group by 1, 2
order by last_seen desc;You should see one row per business number after secrets are correct. If personal traffic still shows entity = aldilaijan, the personal id is in the wrong secret slot.
Solo access for personal only: use can_manage_whatsapp_entity / entity-scoped roles in Supabase (see architecture note in team chat) so staff keep aldilaijan / khobara / exec without seeing personal.
4. Limitations (Meta-side) β
- 1:1 chats only in sync; groups not mirrored to Cloud API.
- Broadcast lists disabled for new sends after coexistence onboarding.
- 20 messages/second throughput cap on coexistence numbers.
- Chat sync can be imperfect; run parallel testing before relying on CRM as legal record.
5. Code references β
| File | Purpose |
|---|---|
supabase/functions/whatsapp-webhook/index.ts | Routes messages + smb_message_echoes |
supabase/functions/_shared/whatsappSmbEchoes.ts | Inserts phone-sent outbound rows |
supabase/functions/_shared/whatsappMessageContent.ts | Shared message body parsing |
lib/data/repositories/whatsapp_repository.dart | CRM send via send-whatsapp-message |
References β
- Meta β Onboard WhatsApp Business app users (Coexistence)
WHATSAPP_HERMES_ADMIN.mdβ Hermes admin channel on exec numberWHATSAPP_MEDIA_INGEST.mdβ inbound media storage
