Skip to content

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 ​

SurfaceHow 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 CRMMeta 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.

mermaid
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 --> Inbox

Path A vs Path B (pick one per number) ​

Standard Cloud APICoexistence (this doc)
Phone appNumber must be off WhatsApp / Business app (β€œclean”)Keep WhatsApp Business app active
OnboardingAdd number in Meta Business Manager onlyEmbedded Signup β†’ Connect your existing WhatsApp Business app + QR scan
CRM outboundYesYes
Phone outbound in CRM historyN/AYes β€” requires smb_message_echoes subscription (implemented)
Groups / broadcast listsN/A on phone after migrateNot 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 ​

  1. Open Meta Embedded Signup (via your Business Manager / BSP flow).
  2. On the WABA step, choose Connect your existing WhatsApp Business app (Coexistence).
  3. Enter the phone number currently registered in the Business app.
  4. Scan the QR code with WhatsApp Business on Android.
  5. Optionally sync contacts and up to 6 months chat history.
  6. Copy the Meta phone_number_id for the number.
  7. Generate a permanent System User access token with whatsapp_business_messaging (and related) permissions.

Supabase secrets ​

Set per entity (aldilaijan, khobara, aldilaijan_exec, personal):

bash
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 jrsgosnnyjonxaesqtln

For 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 ​

NumberEntitySecret slotBusiness
+96522466861aldilaijanWHATSAPP_PHONE_NUMBER_ID_ALDILAIJANBrokerage β€” buyer requests, owner listings
+96522476375khobaraWHATSAPP_PHONE_NUMBER_ID_KHOBARAValuation β€” appraisal requests
+96562224000personalWHATSAPP_PHONE_NUMBER_ID_PERSONALOwner'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) ​

SettingValue
Callback URLhttps://jrsgosnnyjonxaesqtln.supabase.co/functions/v1/whatsapp-webhook
Verify tokenWHATSAPP_WEBHOOK_VERIFY_TOKEN (existing secret)
Subscribed fieldsmessages, smb_message_echoes, message status fields you already use

Deploy the updated webhook after code changes:

bash
supabase functions deploy whatsapp-webhook --project-ref jrsgosnnyjonxaesqtln

Verify coexistence mode (Graph API) ​

bash
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:

json
{ "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:

sql
-- 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) ​

sql
-- Remove whatsapp_agent from everyone except you
delete from user_roles
where role = 'whatsapp_agent'
  and user_id <> '<YOUR_USER_UUID>';

Verify RLS / access ​

  1. Sign in as your user β†’ open Comms β†’ WhatsApp β†’ inbox loads, send works.
  2. Sign in as a staff user without whatsapp_agent β†’ should not see send/inbox actions (RLS + route guards on send-whatsapp-message).
  3. 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.

StepActionExpected
ACustomer sends WhatsApp to your business numberRow in CRM inbox (direction = inbound); appears on phone
BReply from CRM threadCustomer receives message; appears on phone
CReply from Android Business appCustomer receives message; CRM thread shows outbound (echo handler)
DGraph API is_on_biz_app checktrue + CLOUD_API

SQL checks ​

sql
-- 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:

  1. The personal Meta phone_number_id was saved into WHATSAPP_PHONE_NUMBER_ID_ALDILAIJAN (office slot), or
  2. 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
  3. The public contact page pointed β€œWhatsApp us” at 62224000 while the office Cloud API line is 22466861 (Flutter contact screen now uses 22466861 for Aldilaijan).

Fix checklist

StepAction
1Meta Business Manager β†’ WhatsApp β†’ API Setup β†’ confirm two numbers with two different phone_number_id values
2Supabase secrets: WHATSAPP_PHONE_NUMBER_ID_ALDILAIJAN = office 22466861 id; WHATSAPP_PHONE_NUMBER_ID_PERSONAL + WHATSAPP_ACCESS_TOKEN_PERSONAL = personal 62224000 id + token
3Deploy webhook: supabase functions deploy whatsapp-webhook --project-ref jrsgosnnyjonxaesqtln
4CRM inbox β†’ filter chip Personal line (or All) β€” office tab (Brand / Aldilaijan) should no longer receive new personal traffic
5Retag old rows: tool/ops/whatsapp_retag_personal_entity.sql

Quick SQL β€” which line is which in webhook traffic:

sql
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 ​

FilePurpose
supabase/functions/whatsapp-webhook/index.tsRoutes messages + smb_message_echoes
supabase/functions/_shared/whatsappSmbEchoes.tsInserts phone-sent outbound rows
supabase/functions/_shared/whatsappMessageContent.tsShared message body parsing
lib/data/repositories/whatsapp_repository.dartCRM send via send-whatsapp-message

References ​

Aldilaijan & Khobara Real Estate Platform