Migrating your WhatsApp integration to business-scoped user IDs (BSUIDs)
This guide covers the changes introduced by Meta's WhatsApp Business-Scoped User ID (BSUID) rollout and explains what you need to do to keep your integration working after the migration date.
If you use the Zendesk out-of-the-box WhatsApp integration with no custom code, no action is required. Zendesk handles the transition automatically.
Usernames and BSUIDs
WhatsApp introduced usernames as a choice-based feature. Neither users nor businesses are required to set one.
For personal WhatsApp accounts, choosing a username is a privacy feature. When a personal user sets a username, businesses they message see that username instead of their phone number. This gives individuals greater control over how they are identified in conversations.
The same option exists for businesses, but it works differently. A business username is purely a display preference. It changes how the business name appears, but does not hide any contact information. Customers will continue to see the business phone number in the app regardless of whether a username has been set.
Because personal users can now shield their phone number from businesses, Meta needed a new way for businesses to identify and message those users. That is the problem BSUID is designed to solve. Without BSUIDs, a business would have no reliable way to reach a user whose phone number is no longer visible.
| Phone number (old) | BSUID (new) | |
|---|---|---|
| Format | Numeric E.164 string (for example, 1234567890) | Opaque string with country prefix (for example, US.abc123…) |
| Scope | Universal. Consistent across all businesses | Per business portfolio. A different BSUID for each business |
| Stability | Only if the user keeps their number | Consistent for the lifetime of a phone number. Regenerated if the user changes their number \ |
| Phone number visibility | Always visible to the business | Hidden by default when the user has a username enabled |
A BSUID is tied to a business portfolio rather than to any individual phone number within it.
- Any phone number belonging to a given portfolio can send messages to a BSUID scoped to that same portfolio
- Sending to a BSUID from a phone number in a different portfolio is not permitted and results in a failure
For more information on BSUIDs, see Business-scoped user IDs in Meta's documentation.
Summary of the changes
This guide covers both the Sunshine Conversations v1 and v2 APIs. If you are unsure which version your integration uses, see the Sunshine Conversations API Reference.
| Change | Affects Sunshine Conversations v1 API | Affects Sunshine Conversations v2 API |
|---|---|---|
externalId becomes BSUID for WhatsApp clients | Yes | Yes |
New additionalIdentifiers field on client object | Yes | Yes |
New notification:match:failure webhook event | Yes | Yes |
correctedDestinationId removed from responses | Yes | No |
CreateClient with phone number requires a message | Yes | Yes |
| Phone number may be absent from client record | Yes | Yes |
Identity and client model changes
externalId semantic change
For WhatsApp clients, externalId has always been the user's phone number. After the BSUID migration, externalId will contain the BSUID.
Before
{"externalId": "1234567890"}
After
{"externalId": "US.abc123...","additionalIdentifiers": [{ "key": "phoneNumber", "value": "1234567890" },{ "key": "parentUserId", "value": "US.parent456..." }]}
externalId now contains the BSUID. The phone number moves to additionalIdentifiers.
How to detect which format a client uses
Parse the externalId value itself. Do not rely on the presence or absence of additionalIdentifiers:
- BSUID format: opaque string with country-code prefix (for example,
US.abc123…). This is not a valid phone number - Phone number format: numeric E.164-style string (for example,
1234567890)
Handling both formats simultaneously
Legacy clients that existed before the migration date are not updated retroactively. A legacy client's externalId will remain a phone number until Meta sends a new event for that user after the migration, such as an inbound message or status update, at which point the client record will be updated with the BSUID.
This means your codebase will see both formats simultaneously for an extended period after the migration date. How long depends on how active each user is. Low-activity users may retain a phone number in externalId for weeks or months.
Common affected use cases
- Storing a phone number in a CRM from
externalId - Constructing WhatsApp deep links from
externalId - Looking up a user record by phone number using the value from
externalId - Displaying the phone number in a UI from
externalId
What to update
Update all code that reads externalId as a phone number. Use additionalIdentifiers.phoneNumber instead, and handle the case where it may be absent. See Phone number may be absent.
additionalIdentifiers field
A new array field additionalIdentifiers is included on client objects in all API responses and webhook payloads. It carries the phone number and other supplemental identifiers alongside the BSUID.
Structure
{"externalId": "US.abc123...","additionalIdentifiers": [{ "key": "phoneNumber", "value": "1234567890" },{ "key": "parentUserId", "value": "US.parent456..." }]}
Valid keys:
phoneNumber: The user's WhatsApp phone number in E.164 format. May be absent if the user has enabled the username feature and the conditions for phone number visibility are not met. See Phone number may be absent.parentUserId: A higher-level BSUID representing the user's parent account within the portfolio hierarchy, if applicable.
additionalIdentifiers is only added to a client record when Meta sends a new event for that user, such as an inbound message or a status update, after the migration date. Client records created before the migration date are not updated retroactively. For users who have not interacted with your business since before the migration, additionalIdentifiers may not be present on their client record at all. Do not assume this field exists on every client.
What to update
Update any code that needs the phone number to read from additionalIdentifiers.phoneNumber instead of externalId. Always handle the case where phoneNumber is not present in the array.
Phone number may be absent
When a WhatsApp user enables the username feature, their phone number is no longer included in webhooks by default.
Conditions for phone number to be available
The phone number (wa_id) is only present if at least one of the following is true:
- The business messaged or called the user's phone number within the last 30 days
- The business received a message or call from the user's phone number within the last 30 days
- The user is in the business's Meta-hosted contact book (only captures interactions after the feature launched, not retroactive)
Per-phone-number scoping matters. If a business uses multiple phone numbers in their portfolio, interacting with a user from one business number does not make the phone number available on webhooks from a different business number. Businesses with multi-number portfolios will be disproportionately affected.
What this means for your integration
- Never assume
phoneNumberis present inadditionalIdentifiers - Build a fallback for identity lookups that rely on phone number
- If your CRM requires a phone number, log or flag records where it is absent
- Test your integration with a client that has no
phoneNumberinadditionalIdentifiers
client:update
When a user changes their WhatsApp phone number, Meta fires a phone number change event. externalId (the BSUID) and additionalIdentifiers may also update in the same event.
No new webhook subscription needed
Identifier changes fire the existing client:update webhook. The payload includes the fully updated client object. You do not need to subscribe to a new event type.
If Meta stops returning wa_id in subsequent webhooks for a user, Sunshine Conversations retains the phone number that was previously stored on the client record. It is not proactively removed.
When to act
Update your integration if it does any of the following:
- Stores phone numbers or BSUIDs in a CRM or external database
- Constructs WhatsApp deep links from client identifiers
- Correlates WhatsApp users with records in external systems
Webhook envelope
Events are delivered inside the existing events array. The envelope structure is unchanged:
{"app": { "id": "507f1f77bcf86cd799439011" },"webhook": { "id": "507f191e810c19729de860ea", "version": "v2" },"events": [/* array of event objects */]}
Identifier change example
{"id": "evt_abc123","createdAt": "2026-04-13T14:30:45.000Z","type": "client:update","payload": {"conversation": {"id": "conv_123","type": "personal"},"user": {"id": "usr_123","externalId": "user-ext-123","profile": {"givenName": "John","surname": "Doe","email": "[email protected]","avatarUrl": "https://example.com/avatar.jpg"},"signedUpAt": "2025-01-01T00:00:00.000Z","identities": [],"metadata": {},"authenticated": false},"client": {"id": "cli_123","type": "whatsapp","status": "active","externalId": "US.13491208655302741918","integrationId": "int_123","additionalIdentifiers": [{ "key": "phoneNumber", "value": "1234567890" },{ "key": "parentUserId", "value": "US.ENT.11815799212886844830" }],"linkedAt": "2025-02-01T00:00:00.000Z","lastSeen": "2026-04-13T14:30:00.000Z","displayName": "John's WhatsApp","avatarUrl": null,"info": {},"raw": {}},"reason": "identifier_updated"}}
The reason field
A new enum value identifier_updated is added to the ClientUpdateReason enum. It fires when externalId or additionalIdentifiers change on a WhatsApp client.
reason value | When it fires |
|---|---|
| matched | Client was matched to an existing user |
| confirmed | Pending client was confirmed by Meta's sent webhook |
| blocked | User blocked the business |
| unblocked | User unblocked the business |
| identifier_updated | (New) externalId or additionalIdentifiers changed. For example due to the BSUID rollout, a phone number availability change, or a parentUserId update |
When handling client:update, always read the full updated client object from the payload. Do not diff against a cached version. The reason field tells you what triggered the update; the client object tells you the current canonical state. Use identifier_updated to trigger any CRM sync, deep-link rebuild, or identity correlation logic in your system.
What to do
Subscribe to client:update webhooks if not already. When the event fires, read the updated externalId and additionalIdentifiers, and handle the case where phoneNumber may be absent.
Outbound and proactive messaging
notification:match:failure
A new webhook event fires when a WhatsApp notification fails and no entities (user, client, conversation) were created. This is distinct from notification:delivery:failure, which fires only after entities have already been created.
notification:match:failure (new) | notification:delivery:failure (existing) | |
|---|---|---|
| Entities created? | No — nothing was created | Yes — user, client, conversation exist |
| When does it fire? | Meta never confirmed delivery (no sent status) | Delivery failed after entities were created |
| Available in | v1 and v2 | v1 |
Because no entities exist when notification:match:failure fires, you cannot look up a conversation or client from this event. Handle it as a hard failure and apply your retry or fallback logic directly from the notification ID in the payload.
Sunshine Conversations v2 payload
{"id": "...","createdAt": "2026-04-20T12:00:00.000Z","type": "notification:match:failure","payload": {"notification": { "id": "..." },"destination": { "type": "whatsapp", "integrationId": "..." },"error": { "code": "...", "message": "..." }}}
Sunshine Conversations v1 payload
{"trigger": "notification:match:failure","app": { "_id": "..." },"version": "v1","timestamp": 1234567890,"notification": { "_id": "..." },"destination": { "type": "whatsapp", "integrationId": "..." },"error": { "code": "...", "message": "..." }}
For a full list of error.code values and recommended retry or fallback handling, see the API reference.
What to do
Subscribe to notification:match:failure in your webhook configuration. Handle this event in your failure logic separately from notification:delivery:failure. No user, client, or conversation exists when this event fires.
correctedDestinationId
correctedDestinationId has been removed from both Sunshine Conversations v1 and v2 notification webhook responses. Identifier correction is now handled automatically by the platform.
If your integration reads correctedDestinationId from a notification response and uses it to update stored phone numbers or routing data, remove that logic. The correction is applied automatically, no manual action is required.
What to do
Search your codebase for correctedDestinationId and remove any logic that depends on it.
Client creation changes
CreateClient
When creating a WhatsApp client with confirmation.type: immediate, whether a message is required depends on how you are targeting the recipient.
Targeting by phone number — message required
When targeting by phone number, a message must be included inside the confirmation object. Without it, the endpoint returns HTTP 400:
"WhatsApp clients targeting by phone number with immediate confirmation require a message"
{"matchCriteria": {"type": "whatsapp","integrationId": "63a1f4d0c1e2f3a4b5c6d7e8","phoneNumber": "1234567890"},"confirmation": {"type": "immediate","message": {"author": { "type": "business" },"content": {"type": "text","text": "Hello! Welcome to our support channel."}}},"target": {"conversationId": "63a1f4d0c1e2f3a4b5a3c3a6"}}
Targeting by user ID — message not required
When targeting by BSUID (userId), no message is required. Meta returns the identifier directly without needing a sent webhook to confirm it.
{"matchCriteria": {"type": "whatsapp","integrationId": "63a1f4d0c1e2f3a4b5c6d7e8","userId": "US.13491208655302741918"},"confirmation": {"type": "immediate"},"target": {"conversationId": "63a1f4d0c1e2f3a4b5a3c3a6"}}
Why a message is required when targeting by phone number
Client activation is deferred when targeting by phone number — the client is not confirmed until Meta returns a sent status webhook containing the canonical BSUID. Sending a message is what triggers that webhook. Without a message, no webhook fires and the client remains in a pending state indefinitely.
What is not affected
- Targeting by BSUID or
parentUserId: no message required. Meta returns these identifiers as-is - Non-WhatsApp channels: unchanged. Immediate activation continues as before
What to do
Update any client creation flow that targets a WhatsApp user by phone number with confirmation.type: immediate to include a message inside the confirmation object. Ensure your integration correctly handles the deferred activation lifecycle described in Deferred activation lifecycle below.
Deferred activation lifecycle
When a client is created by phone number, activation is deferred. The full lifecycle is:
| Step | Event | What it means |
|---|---|---|
| 1 | client:add fires | Client created in pending status immediately after POST /client |
| 2a — success | client:update fires | Meta confirmed delivery via sent webhook. Client is now active with canonical BSUID identifiers |
| 2b — merge | user:merge fires | The confirmed BSUID matched an existing client. The pending client merges into it |
| 2c — failure | client:remove fires | Meta returned a failed status. Pending client is deleted |
Do not treat client:add as confirmation of activation. Wait for client:update or user:merge before relying on the client having canonical BSUID identifiers. If client:remove fires, the notification failed and no further action is required on that client.
Migration checklist
Work through the following checklist before the migration date. Not every item will apply to your integration. Skip any that are not relevant to your use case.
Identity and phone number handling
| Done | Action |
|---|---|
| ☐ | Audit all code that reads externalId from WhatsApp client objects. After the migration, externalId will contain a BSUID, not a phone number. |
| ☐ | Update phone number lookups to read from additionalIdentifiers.phoneNumber instead of externalId. |
| ☐ | Handle the case where phoneNumber is absent from additionalIdentifiers. Never assume the field is present. |
| ☐ | Subscribe to client:update webhooks if not already. Read the full updated client object when the event fires. Do not diff against a cached version. |
| ☐ | Update any logic that reads the reason field from client:update to handle the new identifier_updated value. |
Outbound and proactive messaging
| Done | Action |
|---|---|
| ☐ | Subscribe to notification:match:failure in your webhook configuration. Handle this event separately from notification:delivery:failure. No user, client, or conversation is created when this event fires. |
| ☐ | Search your codebase for correctedDestinationId and remove any logic that depends on it. |
Client creation and activation
| Done | Action |
|---|---|
| ☐ | Update any CreateClient flow that uses WhatsApp + phone number + confirmation.type: immediate to include a message parameter. Without it, the endpoint returns HTTP 400. |
| ☐ | Do not treat client:add as confirmation of activation. Wait for client:update or user:merge before relying on the client having canonical BSUID identifiers. |
| ☐ | Handle client:remove on a pending client as a notification failure signal. No further action is required on that client. |
Merge and client theft
| Done | Action |
|---|---|
| ☐ | Audit any logic that expects user:merge to be triggered by a message or notification send. Merge now only occurs via POST /client or system status webhooks from Meta. |
| ☐ | Audit any logic that expects client:remove (client theft) to be triggered by an API send. Theft now only occurs on inbound messages or system status webhooks from Meta. |
Final validation
| Done | Action |
|---|---|
| ☐ | Test your full end-to-end flow in a sandbox environment before the migration date. |
| ☐ | Test with a client that has no phoneNumber in additionalIdentifiers to verify your fallback logic works correctly. |
| ☐ | Contact Zendesk Support if you are unsure whether any item in this checklist applies to your integration. |
Additional resources
| Resource | Details |
|---|---|
| API Reference | Sunshine Conversations API Reference |
| Meta BSUID documentation | developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids |
| Zendesk Support | Contact Support |
| Community | Zendesk Developer Community |