Phone number is no longer how WhatsApp identifies your customers. Most CRMs have not kept up.
WhatsApp is moving customer identity off the phone number, and if you run a CRM or any kind of messaging integration, the change has already reached you. Since the late-March to early-April 2026 rollout, every WhatsApp Cloud API message webhook has carried a new identity field called the Business-Scoped User ID, or BSUID, an opaque token Meta issues per business portfolio. [1][2] It arrives as its own field rather than as a new value stuffed into the old phone field, and the phone number itself can now be missing once a user picks a WhatsApp username. If you change one thing after reading this, make it this: treat the BSUID as the identity key on every inbound message, keep the phone number as a nullable alias, and stop assuming to, from, or wa_id will hold a phone number. [2] The rest of this post is the field-by-field map, checked against the primary docs, plus the mistakes already doing the rounds.
Two things are landing at the same time and almost everyone runs them together. One is a privacy feature for users, the WhatsApp username, which lets someone reach a business without handing over their number. The other is an identity change for developers, the BSUID, which shows up in the Cloud API whether or not a given user ever sets a username. Usernames get the headlines, but the BSUID is the one that quietly breaks integrations, so it pays to keep the two apart. They run on different timelines and they touch different parts of your stack.
Two layers, not one
Most coverage treats "WhatsApp usernames" and "BSUID" as one launch. They are really two layers, aimed at different people, with very different blast radii.
| Layer |
What it is |
Who it affects |
Status as of June 2026 |
| WhatsApp username |
An optional public handle that lets a user be reached without sharing their phone number [2][6] |
End users (opt-in, nobody is forced to adopt one) |
Limited beta from April 2026, broader rollout begins June 2026 [2][3][5] |
| Business-Scoped User ID (BSUID) |
A new opaque identity token in the Cloud API, present for every user regardless of username adoption [1][2] |
Every business, CRM, and BSP integration on the platform |
Live in message webhooks since the late-March to early-April 2026 rollout [1][2][5] |
That gap is what trips teams up. You cannot wait for usernames to finish rolling out before doing the work, because the developer side has already shipped. The BSUID is in your webhooks now, including for users who will never go near the username feature.
The rollout, with dates
The dated milestones below are drawn from the BSP documentation that mirrors Meta's partner guidance. Where a date is a rollout-in-progress rather than a fixed switch, we say so.
| Date |
Milestone |
Layer |
| Late March / early April 2026 |
BSUIDs begin appearing in Cloud API and BSP message webhooks. Meta's primary page says early April; Azure and 360dialog timeline tables say 31 March [1][2][5] |
Developer |
| Early April 2026 |
Contact Book launches: a Meta-hosted, default-on store of phone-to-BSUID mappings [2] |
Developer |
| April 2026 |
WhatsApp username limited beta for a small set of users [3][5] |
User |
| May 2026 (exact date pending) |
Meta's direct Cloud API begins supporting sends to a BSUID through the new recipient field [1] |
Developer |
| June 2026 |
Usernames begin reaching end users and businesses can claim handles; Azure and Twilio frame their wrapper send availability around here [2][3] |
User |
| Rest of 2026 |
Staged, country-by-country expansion toward broad availability [5] |
User |
One date deserves a caveat, and it is a clean illustration of the raw-API-versus-BSP split this post keeps returning to. Meta's own BSUID page says the direct Cloud API will not support sending to a BSUID until May 2026, with the exact date pending. [1] Azure and Twilio frame production availability around June 2026, tied to usernames reaching end users. [2][3] So treat outbound availability as provider-specific: the raw contract is the new recipient field, but when you can actually use it depends on whether you are on the direct API or a BSP, and each can land on its own date. Confirm yours with your provider.
What the inbound webhook actually looks like
This is where most write-ups go wrong. On the direct Cloud API the BSUID is its own field. It is not the old wa_id carrying a new kind of value.
| Field (raw Cloud API) |
Carries |
Present when |
contacts[].user_id |
The user's BSUID |
Always, in every message webhook [1][6] |
messages[].from_user_id |
The sender's BSUID |
Always, in every message webhook [1] |
contacts[].profile.username |
The WhatsApp username |
Only when the user has set one [1][6] |
contacts[].wa_id |
The phone number |
When the phone is available; omitted once a username is adopted and phone-inclusion conditions are not met [2] |
messages[].from |
The sender's phone number |
Same condition as wa_id [2] |
statuses[].recipient_user_id |
The recipient's BSUID |
On status webhooks, whether the original send used a phone number or a BSUID. The one omission: a failed status whose message was phone-addressed [1][6] |
The shape to remember is that user_id and from_user_id are always there, while the phone fields are not. When the phone is available you get both at once, the phone in wa_id and from and the BSUID in user_id and from_user_id. [2] Once a user adopts a username and has no recent history with you, the phone fields can come through empty or missing, and the BSUID is the only thing identifying the sender.
Meta keeps the phone number flowing when at least one of these conditions holds, checked per business phone number rather than per portfolio: [2]
- You messaged or called the user's number within the last 30 days.
- You received a message or call from the user's number within the last 30 days.
- The user is in your Contact Book.
So an ongoing conversation is fine. The number goes missing for new, username-only contacts with no history, which happens to be the exact group a lead-gen business deals with all day: a first message from someone you have never spoken to.
Direct Cloud API names differ from your BSP's names
If you are not on the direct Cloud API, your Business Solution Provider has renamed these fields. Reading a BSP integration guide and then hunting for those names in a raw Cloud API payload is a common dead end.
| Surface |
BSUID field name |
Source |
| Direct Cloud API |
contacts[].user_id and messages[].from_user_id |
[1] |
| Twilio |
ExternalUserId |
[3] |
| Infobip |
contact.userId |
[4] |
| Azure Communication Services |
fromBSUID (inbound) and toBSUID (delivery status) |
[2] |
Underneath it is the same opaque BSUID either way. Only the wrapper around it changes.
Outbound addressing: same endpoint, but the raw API adds recipient
The send endpoint does not change, but the raw Cloud API field does. Phone-number sends keep using to. BSUID sends use a new recipient field. [1] At least one of the two has to be present, and if you send both, Meta gives to precedence, so they are separate addressing inputs rather than a single field that auto-detects what you hand it. [1] This is the spot where the BSP wrappers diverge most from the direct contract: Azure Communication Services, for instance, takes either a phone number or a BSUID in its one to array and sorts it out for you [2], which is a convenient normalization but not what the raw API does. If your post claims to map the direct Cloud API, as this one does, recipient is the field that matters.
Format is fussy either way. A BSUID is an ISO 3166 alpha-2 country code, then a period, then up to 128 alphanumeric characters, like US.13491208655302741918. The country code and the period are part of the value [2], so stripping them or "normalising" the BSUID the way you might tidy a phone number will break the send.
There is one real exception. Authentication-category templates still need a phone number. One-tap, zero-tap, and copy-code auth templates cannot go to a BSUID, and trying returns Meta error 131062. [6] If your one-time-passcode flow runs over WhatsApp, it needs the number, which is one more reason to grab the phone-to-BSUID pair while you still have the phone.
The identity model: opaque, scoped, and not always reversible
How the BSUID behaves decides where it belongs in your data model. Each row below maps to a design decision.
| Property |
Behavior |
| Scope |
Unique per business portfolio. The same user has a different BSUID with every business they interact with, so it is not a global person key. [1][2] |
| Opacity |
An opaque token. It is not a phone number and carries no parseable meaning beyond the country prefix. [2] |
| Format |
{ISO 3166 alpha-2}.{up to 128 alphanumeric}, used whole, including the country code and period. [2] |
| Username change |
Stable. Changing a username does not change the BSUID. [2] |
| Phone-number change |
Regenerated. Changing the phone number produces a new BSUID, and Meta sends a user_id_update webhook carrying both the old and the new BSUID so you can relink. [1][2] |
Relink on identifier change, do not duplicate
When someone changes the phone number on their account, the BSUID is regenerated, and Meta announces it through a documented user_id_update webhook that carries both the old and the new BSUID. [1][2] Use the old BSUID to find the existing record and the new one to update it, so the change lands as a merge rather than a new contact. Treat it as a brand-new user and you end up with one person's history split across two records.
One thing to get right: this is its own identifier-change payload, not a delivery status, so do not wire relinking to the statuses[] callbacks elsewhere in this map. Some BSP docs also surface a parallel system message for the same change, so confirm the exact payload shape against Meta's live reference before you build on it. The behaviour is the same everywhere: the identifier changed, so relink instead of duplicating.
Phone recovery is forward-only
Meta's Contact Book stores phone-to-BSUID mappings automatically. It is Meta-hosted, on by default, and scoped to the business portfolio. [2] The catch that surprises people is that it only records interactions from after it launched in April 2026, never backfills, and has nothing to record for a brand-new username-only contact who never shared a number. [2] So you cannot count on recovering a phone number for a BSUID-only contact later.
Which leads to a simple habit: whenever a webhook does include the phone number, store the phone-to-BSUID pair right then. Do not leave it for later.
The canonical data-model pattern
Every CRM and messaging integration ends up at the same shape, and it is a small change built on one rule. The BSUID is the primary key; the phone is the alias.
- Store the BSUID as the durable primary identity key per (business, contact). Keep the phone number as a supplemental, nullable alias on the same record. [2][5]
- Read the BSUID field (
user_id or from_user_id, or your BSP's equivalent) as the reliable identity on every inbound message. Stop reading to, from, or wa_id as a guaranteed phone number. [2]
- Capture the phone-to-BSUID pair whenever the phone is present, because the Contact Book will not backfill it for you. [2]
- Merge on dual identifier. If you already have a contact by phone and a new BSUID arrives for the same person during an active conversation, link them rather than creating a second record.
- On the identifier-change event, relink the existing contact to the new BSUID. [2]
If your schema keys WhatsApp contacts on the phone number, that is the first thing to fix, because the phone is now the field that can vanish and the BSUID is the one that always arrives.
Common misconceptions
These are already going around in vendor posts and integration threads. Most are the kind of reasonable-sounding shortcut that sails through review and then falls over the first time a username-only lead shows up.
The BSUID is just the wa_id field with a new kind of value. Not so. It is a separate field, contacts[].user_id and messages[].from_user_id, sitting alongside the phone fields. [1] Code that watches wa_id for "a value that looks different" will miss the BSUID completely on any payload where the phone is also present.
The phone number is always there, so parsing from as a phone is safe. Not anymore. Once a user adopts a username and has no recent history with you, from and wa_id can be empty or absent. [2] Anything that validates or formats those fields as an E.164 number breaks on exactly the contacts you most want, the new ones.
You just drop the BSUID into the to field and the API sorts it out. That is true on some BSP wrappers, such as Azure ACS [2], but not on the raw Cloud API. The direct API keeps to for phone numbers and adds a separate recipient field for BSUID sends, with to taking precedence if both are present. [1] Put a BSUID in to against the raw API and you are not sending what you think you are. Authentication templates are a further exception: they still need a phone number and return error 131062 if you point them at a BSUID. [6]
You can strip the country prefix and period to "clean up" the BSUID. You cannot. The country code and the period are part of the value, and changing any part of it makes the request fail. [2]
The BSUID identifies a person across the platform. It does not. It is portfolio-scoped, so the same person carries a different BSUID with every business. You cannot use it to recognise someone across two businesses or to share identity between unrelated portfolios. [1][2] It is identity inside your world, not across the platform.
The Contact Book means you can always look up a phone number later. It only goes forward. It records mappings from after its April 2026 launch, never backfills, and has nothing to record for a username-only contact who never shared a number. [2] Capture the pair yourself while you have it.
A phone-number-change event means a new user. It is the same user with a regenerated BSUID, so relink the existing record. [2] Duplicate it instead and you split one person's history across two contacts.
A note for teams on the unofficial protocol: BSUID is not @lid
If your stack also touches the unofficial multi-device protocol through a library like Baileys, you have probably already run into a different identifier: the @lid "LinkedID" JID that shows up when a user's real phone-number JID is hidden. [8] These look like the same thing, but they are not.
The BSUID is an official Cloud API construct, portfolio-scoped and opaque, delivered in documented webhook fields. The @lid JID lives inside WhatsApp's multi-device addressing and is global per user, not scoped to a single business. [8] Both are chasing the same idea, getting identity off the phone number, but they are different mechanisms on different surfaces, and a mapping you build for one will not carry over to the other.
The deadline is binding on WhatsApp, more than on other channels
On WhatsApp the identity change lands on top of a channel that already runs on a clock. Meta's Business Platform gives you a 24-hour customer-service window: once a user messages you, you have 24 hours to reply in free-form, and after that your only outbound option is a pre-approved, paid template. [7] A first message from a username-only lead can arrive with a BSUID, no phone number, and that 24-hour timer already counting down. An integration that cannot recognise, store, and reply to that contact by its BSUID is not just missing a clean data model. It can miss the free reply window, and the lead with it.
So we did not treat BSUID readiness as optional, and we did not wait for the rollout to force our hand. StaffOS already runs the pattern this post argues for. We key WhatsApp contacts on the durable platform identifier, keep the phone number as a nullable alias, capture the phone-to-BSUID pair whenever the phone is present, and relink on the user_id_update event instead of duplicating. For us a username-only first touch is an ordinary contact from the first message, not a special case that leans on phone-number logic. If you are weighing one WhatsApp vendor against another, treat the audit above as the bar: the work should already be done, not sitting on a roadmap.
What to verify before you ship
The platform docs are the source of truth, and they are still being filled in as the user-facing rollout continues. Before you write code against a specific payload, check three things against Meta's live reference: the exact shape of the user_id_update identifier-change payload, whether your BSP hands you the raw fields or its own renamed version, and whether any new field has appeared in the contacts object since this was written. The map above reflects the primary and partner docs as of June 2026, and we will update it when Meta changes the surface.
If something here no longer matches the live docs, tell us and we will fix it. The whole point is that it stays checkable.