mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
Closes the silent-failure bug a user reported: when multiple phone
numbers are connected to the same WABA, only the first one actually
receives webhook events from Meta. The wacrm UI shows "Connected"
because the metadata probe succeeds, but Meta's activity log is
empty for every subsequent number.
Root cause
----------
The POST /api/whatsapp/config handler verified credentials with a
GET /{phone_number_id} probe and saved them to Supabase — but
never called Meta's two registration endpoints that actually wire
a number for inbound events:
1. POST /{phone_number_id}/register
Subscribes the number to THIS app's webhook. Requires a
6-digit 2FA PIN the user previously set in Meta WhatsApp
Manager → Two-step verification. Without it, Meta routes
events for that number to whichever app last claimed it.
2. POST /{waba_id}/subscribed_apps
Subscribes the WABA itself to this app. Required once per
WABA, but idempotent on Meta's side so we call on every
save without harm.
The first number "worked" because someone (the user, Embedded
Signup, or Meta dashboard) had already done both calls for it
out-of-band. Every subsequent number silently failed.
What this PR does
-----------------
* New helpers in meta-api.ts: registerPhoneNumber (treats Meta's
"already registered" 400 as success), subscribeWabaToApp
(idempotent), getSubscribedApps (for the diagnostic).
* POST /api/whatsapp/config now:
- Accepts an optional `pin` (6-digit, validated).
- Calls subscribeWabaToApp on every save (idempotent).
- Calls registerPhoneNumber on first save and whenever a PIN
is supplied. Skipped when the same number is already
registered locally and no fresh PIN is provided — re-running
/register with a stale PIN would actually undo the active
subscription.
- Persists registered_at / subscribed_apps_at timestamps and
last_registration_error for diagnostic surfacing.
- Returns {success, registered, registration_error} so the UI
can distinguish "creds valid" from "actually live".
- Saves the row even when /register fails so the user can
retry without re-entering everything.
* New diagnostic GET /api/whatsapp/config/verify-registration —
hits Meta to confirm the saved number is reachable + the WABA
is subscribed. Returns a per-check breakdown the UI renders.
Solves the misleading-green-banner UX failure that caused the
bug to go unnoticed in the first place.
* Migration 015 adds registered_at, subscribed_apps_at,
last_registration_error to whatsapp_config. All nullable so
existing rows survive — the UI labels them "registration status
unknown" until the user clicks Verify or re-saves.
* whatsapp-config.tsx:
- New PIN input with help text pointing to the exact Meta
Manager setting and explaining why it's needed.
- Two-tier status: existing "Credentials valid" alert (relabeled
from "Connected" since it was misleadingly broad) PLUS a new
Registration Status alert that flips emerald/amber based on
the registered_at timestamp.
- "Verify with Meta" button on the registration alert pings
the diagnostic endpoint and renders the per-check result
inline. Surfaces last_registration_error verbatim with a
pointer to retry.
- PIN clears from state after a successful save so it can't
accidentally retrigger /register on the next save.
Tests + checks
--------------
* 9 new fetch-mock tests cover registerPhoneNumber (happy path,
already-registered passthrough, PIN-required error verbatim,
generic Meta error), subscribeWabaToApp (URL + bearer + error),
getSubscribedApps (data, empty, error).
* 265/265 tests pass. Lint 0 errors. Typecheck clean. Build
registers both /api/whatsapp/config and
/api/whatsapp/config/verify-registration.
Out of scope
------------
* Multi-number-per-CRM-user (current schema is UNIQUE(user_id)
so one CRM user = one number). The reported bug was
multi-number-per-WABA across multiple CRM users, which this
fully resolves.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
2.1 KiB
SQL
46 lines
2.1 KiB
SQL
-- ============================================================
|
|
-- whatsapp_config: track Meta Cloud API registration state
|
|
--
|
|
-- Why this exists:
|
|
-- Saving a row to whatsapp_config does NOT make a phone number
|
|
-- actually receive webhook events from Meta. Two extra Cloud API
|
|
-- calls are required:
|
|
--
|
|
-- POST /{phone_number_id}/register — subscribes the number
|
|
-- with a 2FA PIN, makes
|
|
-- it routable to OUR app
|
|
-- POST /{waba_id}/subscribed_apps — subscribes the WABA
|
|
-- (one-time per app, but
|
|
-- idempotent so we can
|
|
-- call on every save)
|
|
--
|
|
-- Until those two complete successfully, Meta routes inbound
|
|
-- events to whichever app last registered the number (often the
|
|
-- one that did Embedded Signup originally). Symptom: a second
|
|
-- wacrm user adds a second number under the same WABA, the UI
|
|
-- reports "Connected" because metadata verification succeeds,
|
|
-- but Meta's activity log shows zero events for that number.
|
|
--
|
|
-- These columns let the UI distinguish "credentials saved" from
|
|
-- "actually live" and let users retry registration without
|
|
-- re-entering everything.
|
|
--
|
|
-- Backfill: every column is nullable. Existing rows survive with
|
|
-- NULL values; the UI shows them as "registration status unknown —
|
|
-- click Verify Registration" and the diagnostic endpoint fills the
|
|
-- timestamps on the next probe.
|
|
--
|
|
-- Idempotent — safe to re-run.
|
|
-- ============================================================
|
|
|
|
ALTER TABLE whatsapp_config
|
|
ADD COLUMN IF NOT EXISTS registered_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS subscribed_apps_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS last_registration_error TEXT;
|
|
|
|
-- Index supports the "find all numbers awaiting registration"
|
|
-- query a future admin dashboard might want; cheap to maintain.
|
|
CREATE INDEX IF NOT EXISTS idx_whatsapp_config_registered_at
|
|
ON whatsapp_config (registered_at)
|
|
WHERE registered_at IS NULL;
|