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)
FormatNumeric E.164 string (for example, 1234567890)Opaque string with country prefix (for example, US.abc123…)
ScopeUniversal. Consistent across all businessesPer business portfolio. A different BSUID for each business
StabilityOnly if the user keeps their numberConsistent for the lifetime of a phone number. Regenerated if the user changes their number \
Phone number visibilityAlways visible to the businessHidden 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.

ChangeAffects Sunshine Conversations v1 APIAffects Sunshine Conversations v2 API
externalId becomes BSUID for WhatsApp clientsYesYes
New additionalIdentifiers field on client objectYesYes
New notification:match:failure webhook eventYesYes
correctedDestinationId removed from responsesYesNo
CreateClient with phone number requires a messageYesYes
Phone number may be absent from client recordYesYes

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 phoneNumber is present in additionalIdentifiers
  • 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 phoneNumber in additionalIdentifiers

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 valueWhen it fires
matchedClient was matched to an existing user
confirmedPending client was confirmed by Meta's sent webhook
blockedUser blocked the business
unblockedUser 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 createdYes — user, client, conversation exist
When does it fire?Meta never confirmed delivery (no sent status)Delivery failed after entities were created
Available inv1 and v2v1

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:

StepEventWhat it means
1client:add firesClient created in pending status immediately after POST /client
2a — successclient:update firesMeta confirmed delivery via sent webhook. Client is now active with canonical BSUID identifiers
2b — mergeuser:merge firesThe confirmed BSUID matched an existing client. The pending client merges into it
2c — failureclient:remove firesMeta 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

DoneAction
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

DoneAction
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

DoneAction
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

DoneAction
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

DoneAction
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