For the complete documentation index, see llms.txt. This page is also available as Markdown.

Validation guide

Audience: Developers integrating WhatsApp via 360dialog Status: Pre-GA. Use this guide to certify your integration before usernames reach GA. Companion page: [alpha] Usernames


Why this guide exists

There is no live "username-only" user you can create on demand — Meta enables username adoption per country, on its own schedule, and phone numbers still appear in every webhook until GA.

You don't need to. The breaking change is a deterministic contract, not new runtime behavior: at GA, certain phone fields are simply omitted from webhooks you already receive. You can validate every part of that contract today by combining:

  1. Live production traffic — BSUIDs and BSUID-sending are already live, so most of the integration is testable against real data now.

  2. Replayed fixtures — for the one thing you can't reproduce live (a webhook with the phone field absent), replay the golden payloads in this guide through your own staging pipeline.

Work through the stages in order. Each has a clear Pass criteria. At the end, complete the self-certification checklist (§10) and you're GA-ready.

Throughout, the test consumer is RS.1502576394655843 (BSUID) / 310601600963 (phone). Replace with your own values.


Before you start

You'll need:

  • A WhatsApp number connected via 360dialog (https://waba-v2.360dialog.io, D360-API-KEY header).

  • A staging webhook endpoint you can POST test payloads to, isolated from production data.

  • At least one recent real inbound webhook captured from your number (it already contains a BSUID — see Stage 1).

  • A way to replay JSON to your webhook endpoint (e.g. curl, Postman, or your test harness).

⚠️ Replay fixtures only into staging. The payloads below use example IDs and must not create real records in production.

1

Confirm you are capturing the BSUID (live)

The BSUID is already in every production webhook. Verify you persist it.

Steps

  1. Send a message to your business number from any WhatsApp account.

  2. Inspect the inbound webhook. Confirm these fields are present and stored:

    • contacts[0].user_id

    • messages[0].from_user_id

  3. Confirm you store user_id keyed per business portfolio, not globally.

Pass criteria: the BSUID from a real webhook lands in your datastore, associated with the correct portfolio, on the same record as the phone number.

2

Send a message to a BSUID (early July)

Sending to a BSUID works in production today — no allowlist.

Steps

  1. Take a user_id from a recent webhook (the user's 24h window must be open, or use a template).

  2. Send using the recipient field (not to):

  1. Confirm your code parses the BSUID-only send response shape:

  1. Store the returned wamid.

Pass criteria: the message sends; your parser reads messages[0].id and contacts[0].user_id (it does not expect wa_id); the wamid is persisted for status correlation.

3

Handle phone-withheld webhooks (fixtures — the breaking case)

This is what changes at GA. Replay each fixture below into your staging endpoint and confirm your pipeline routes on the BSUID and never crashes on a missing phone.

3a. Inbound message — BSUID only (no wa_id, no from)

Pass criteria: message is ingested and routed to the right conversation using from_user_id; no null-pointer/validation error from the missing from / wa_id; username is stored as a display string (no @) and not used as a key.

3b. Status webhook — BSUID only

Pass criteria: the status is matched to your sent message by wamid (id), not by recipient phone; recipient_user_id is used when recipient_id is absent.

3c. Failed status — no contacts array

Pass criteria: your handler tolerates a failed status that has no contacts array; the error is logged against the correct message via wamid.

For comparison, keep a phone-present copy of 3a/3b on hand (same payloads with wa_id / from / recipient_id added back) and confirm both variants flow through the same code path. See §11.

4

Force the BSUID-only path in your own pipeline

Fixtures prove your webhook parser; this proves the rest of your system (routing, CRM lookup, replies) degrades gracefully when the phone is gone.

Pick one:

  • Strip-and-replay: take a copy of real production traffic and remove wa_id, from, and recipient_id before it reaches your ingestion.

  • Feature flag: add a staging-only switch that nulls the phone on ingestion.

Then run a normal end-to-end flow (receive → identify → reply).

Pass criteria: the conversation still routes, the customer is still identified (by BSUID), and an outbound reply is sent via recipient — with no phone number anywhere in the flow.

5

Error-path drills

Trigger each deliberately and confirm graceful handling.

Drill
How to trigger
Pass criteria

131062 — BSUID not supported

Send a one-tap / zero-tap / copy-code authentication template to a recipient (BSUID).

Error caught; you fall back to recovering the phone (Stage 6) before retrying auth.

131047 — window closed

Send a free-form (non-template) message to a BSUID whose 24h window is closed.

Error caught; you switch to a template instead of retrying free-form.

Failed w/o contacts

Replay fixture §3c.

No crash on missing contacts; error logged by wamid.

6

Stage the phone-recovery flow (REQUEST_CONTACT_INFO)

⚠️ Build, but don't depend on it yet. Meta now lists this as starting early July 2026.

Steps

  1. Create the utility template (the text field is optional and the button label cannot be customized; if you include it, any value other than Share Contact Info is rejected with subcode 2388153):

  1. Validate your handler for the on-tap response by replaying this fixture:

Pass criteria: you read the shared number from messages[0].contacts[0].phones[0], normalize on phones[0].wa_id (digits only, no +/spaces), and map it to the existing BSUID record. Handle both origin: "contact_request" (button tap, no vCard) and origin: "other" (manual share, vCard included).

7

Username & business username plumbing

User username (display only): confirm you store contacts[0].profile.username as a changeable display string and never use it as a key (see §3a).

Business username (your number's handle):

  1. List suggestions: GET /username_suggestions

  2. Claim: POST /username with { "username": "yourbrand" }

  3. Verify: GET /username{ "username": "...", "status": "..." }

  4. Subscribe each app to the business_username_update webhook and confirm you receive approved / reserved / deleted events.

Pass criteria: you can claim/verify a reserved handle and react to business_username_update. Remember: a business username does not hide your phone number.

8

Data-model & migration checks

Review (code review, not runtime) that:

  • The consumer phone number is nullable everywhere — schema, validation, UI.

  • A BSUID ↔ phone mapping table exists; phone is treated as enrichment, not identity.

  • BSUID is keyed per portfolio; you never compare or dedupe BSUIDs across clients.

  • Identity routing is by field name (wa_id = phone, user_id = BSUID) — no generic E.164 validation on identity fields.

  • You tolerate unknown/new fields (username, parent_user_id, recipient_parent_user_id, from_user_id).

  • If you're a multi-portfolio managed business, you've evaluated parent BSUID (parent_user_id, format includes ENT) enrollment with your 360dialog contact.

Pass criteria: all boxes hold under review.

9

Deploy early and watch real traffic

Because phone numbers remain in webhooks through the entire pre-GA window, you can validate in production with zero risk:

  1. Deploy your BSUID-storing, phone-nullable code to production now.

  2. Watch real traffic for several weeks — confirm BSUIDs are captured on every conversation and that sends correlate by wamid.

  3. By GA, the only change is fewer phone fields — a path you already proved with the fixtures in §3.

Pass criteria: production shows BSUIDs persisted on 100% of recent conversations, with no parsing errors.


Self-certification checklist

Sign off each line before GA. Every item maps to a stage above.

Capture & send

Phone-withheld contract

Errors

Recovery & usernames

Data model

Production


Fixture appendix — phone-present variants

Keep these alongside the BSUID-only fixtures and confirm both flow through the same code path.

Inbound — phone present:

Status — phone present:


Need help?

  • Bespoke or high-complexity integration? Ask your 360dialog account manager about a deep-dive review session.

  • Found a discrepancy between this guide and live behavior? Report it — pre-GA behavior is still being confirmed with Meta engineering.

Last updated

Was this helpful?