Skip to Content
Living documentation — last reviewed 2026-05-28
RunbooksMeta & WhatsApp integration setup (Lead Ads + Cloud API)

Meta & WhatsApp integration setup (Lead Ads + Cloud API)

How to configure the one central Taikan Meta app that powers both lead-ingestion channels:

  • Meta Lead Ads — Facebook/Instagram lead forms → leads (Facebook Login for Business).
  • WhatsApp Cloud — Click-to-WhatsApp ads → leads (Embedded Signup + Cloud API).

For what the pipeline does (flows, data model, code), see the feature doc docs/features/leads-crm/lead-ingestion.md. This runbook is the dashboard + env side: the Meta App Dashboard config, the review/verification gates, and the env vars the API reads.

Status: WhatsApp is pending Meta Business Verification + App Review — the connect flow is built but a real number can’t go live until that clears. Lead Ads is live. Steps below that depend on review are marked ⏳ pending review.

The stable contract (what the code expects)

Everything the API needs is read from env (apps/api/src/lead-ingestion/meta-config.service.ts) and these two webhook endpoints. One app, one app secret, both products — the X-Hub-Signature-256 HMAC uses META_APP_SECRET for Lead Ads and WhatsApp.

EndpointObject / fieldVerify tokenHandler
GET/POST /webhooks/meta/leadgenpage / leadgenMETA_VERIFY_TOKENmeta-lead-webhook.controller.ts
GET/POST /webhooks/whatsappwhatsapp_business_account / messagesWHATSAPP_VERIFY_TOKEN (→ META_VERIFY_TOKEN)whatsapp-webhook.controller.ts

The GET is Meta’s subscribe handshake (echoes hub.challenge when the token matches); the POST is the event delivery (HMAC-verified). Both must be publicly reachable over HTTPS before Meta will let you save the webhook — for local dev, tunnel the API (cloudflared tunnel --url http://localhost:3001 or ngrok) and use the tunnel URL.

1. The central Meta app

Meta App Dashboard → your Taikan app (one app serves every org).

  1. App ID / secret → env META_APP_ID, META_APP_SECRET. (Settings → Basic.)
  2. Add the two products / use cases:
    • Facebook Login for Business (Lead Ads connect).
    • WhatsApp (Cloud API).
  3. Business Verification (Settings → Basic → Verification) — required before either product leaves dev mode. Shared across both products; do it once.

2. Webhooks

App Dashboard → Webhooks (or each product’s Configuration → Webhooks).

FieldValue
Lead Ads — ObjectPage, subscribe field leadgen
Lead Ads — Callback URLhttps://<api-host>/webhooks/meta/leadgen
Lead Ads — Verify tokenthe value you set as META_VERIFY_TOKEN
WhatsApp — ObjectWhatsApp Business Account, subscribe field messages
WhatsApp — Callback URLhttps://<api-host>/webhooks/whatsapp
WhatsApp — Verify tokenWHATSAPP_VERIFY_TOKEN (or META_VERIFY_TOKEN if you didn’t set the WA-specific one)

Pick any high-entropy strings for the verify tokens (openssl rand -hex 32); they only have to match what the API echoes. Set the env vars first and redeploy — Meta calls the GET immediately on save and the handshake fails if the API doesn’t know the token yet.

Per-account subscription (which Page / which WABA actually sends events) is done by the connect flow at runtime, not here: subscribePageToLeadgen (Lead Ads) and subscribeWabaToWebhooks (WhatsApp). This step only registers the app-level callback.

3. Lead Ads — Facebook Login for Business

The coach-facing connect is an OAuth redirect (…/integrations/meta/connect-url → Facebook → …/integrations/meta/callback).

  1. Facebook Login for Business → Configurations → Create configuration.
    • Type: System User access token (so the stored token can call the Graph API server-to-server for leadgen retrieval).
    • Assets/permissions the config requests (see §5).
    • Copy the Configuration ID → env META_LOGIN_CONFIG_ID.
  2. Valid OAuth redirect URI (Login settings) = your META_OAUTH_REDIRECT_URI, e.g. https://<api-host>/integrations/meta/callback. Must match env exactly.
  3. The connect flow then lists the coach’s Pages and, on finalize, subscribes each chosen Page to leadgen and stores one active integration_connections row per Page.

4. WhatsApp — Embedded Signup + Cloud API

The coach-facing connect is the Embedded Signup popup (FB JS SDK), not a redirect.

  1. WhatsApp → Embedded Signup → create/!configure a signup flow → copy its Configuration ID → env WHATSAPP_EMBEDDED_SIGNUP_CONFIG_ID. This is the only value the frontend popup needs (served via …/integrations/whatsapp/config).
  2. Test number (WhatsApp → API Setup): Meta gives every app a free test number. Use it to exercise /webhooks/whatsapp end-to-end before review clears. Inbound is free.
  3. Per-org go-live ⏳ pending review: when a coach connects, the finalize step (whatsapp-connect.service.ts) exchanges the code, subscribes the WABA to messages, and registers the number (registerWhatsAppNumber, idempotent). The coach’s number must be a real number that can receive the verification code (no VoIP), and its display name needs Meta approval before it can send.

5. App Review + Business Verification ⏳

Both products run in dev mode (only app admins/testers/the test number work) until App Review approves the advanced-access permissions. Submit once Business Verification is green.

ProductPermissions to requestWhy
Lead Adsleads_retrieval, pages_show_list, pages_read_engagement, pages_manage_metadata, business_managementRead a Page’s lead forms + retrieve submitted leads + subscribe the Page webhook.
WhatsAppwhatsapp_business_management, whatsapp_business_messagingManage the WABA/number + receive inbound messages.

The Lead Ads permission set is bundled into the Login for Business configuration (§3) — review it there. App Review needs a screencast of the real connect flow working, which is why the frontend connect cards (Settings → Integrations) had to ship first. Record: coach clicks Connect → authorizes → the connection appears active.

Restriction emails on a fresh test WABA (“breach of acceptable use”) are a common false positive on unverified test accounts — scoped to that test WABA, not your portfolio, and they don’t affect the live path. Appeal via Business Support Home and complete Business Verification.

6. Env summary

API env (apps/api/.env locally; Railway → @taikan/api in prod — the agent cannot write prod env). All optional: when unset the pipeline fails safe (no connect, no webhook routing) rather than erroring.

VarUsed for
META_APP_IDApp identity (OAuth + Embedded Signup).
META_APP_SECRETShared webhook HMAC (both channels) + token exchange.
META_VERIFY_TOKENLead Ads webhook handshake.
META_LOGIN_CONFIG_IDFacebook Login for Business configuration.
META_OAUTH_REDIRECT_URILead Ads OAuth callback (must match the app’s allowed URI).
WHATSAPP_VERIFY_TOKENWhatsApp webhook handshake (falls back to META_VERIFY_TOKEN).
WHATSAPP_EMBEDDED_SIGNUP_CONFIG_IDThe Embedded Signup popup configuration.
FRONTEND_URLWhere the Lead Ads OAuth callback redirects back to.
PAYMENT_CREDENTIALS_ENCRYPTION_KEYEncrypts stored access tokens at rest (reused).

isConfigured (Lead Ads webhook) needs app id + secret + a verify token; isConnectConfigured also needs the login config id + redirect uri. See meta-config.service.ts.

7. Prove it

  1. Webhook handshake — in the App Dashboard, hit Verify and Save on each webhook. A green save means the GET handshake passed (token + reachable HTTPS).
  2. Lead Ads — Meta’s Lead Ads Testing Tool (developers.facebook.com/tools/lead-ads-testing) → pick the connected Page + form → Create lead → a lead appears in Taikan within seconds.
  3. WhatsApp — from the test number’s API Setup page, send yourself a message, or message the test number from a CTWA test ad. Only messages with a CTWA referral create a lead (organic inbound is dropped by design).

8. Troubleshooting

SymptomCause / fix
Webhook won’t save (“URL couldn’t be validated”)The GET handshake failed — API not publicly reachable over HTTPS, or the verify token env ≠ what you typed. Set env + redeploy, then retry.
Webhook saves but no leads arriveApp-level callback is set but the Page/WABA isn’t subscribed — that happens in the connect flow (finalize). Confirm an active integration_connections row exists for the page_id/phone_number_id.
401/403 on the POSTHMAC mismatch — the app sending events isn’t signing with META_APP_SECRET, or the secret rotated.
Lead Ads connect returns “no pages”The coach granted the app no Pages, or the Login config doesn’t request pages_show_list.
WhatsApp connect popup does nothingWHATSAPP_EMBEDDED_SIGNUP_CONFIG_ID unset → …/integrations/whatsapp/config returns nothing actionable; the card shows “not configured”.
Number registered but can’t sendDisplay name not yet approved, or it’s a VoIP number (rejected).
Everything 403s after going liveApp still in dev mode — App Review not approved for the advanced permissions yet.