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
destination.userId can also accept a BSUID for backward-compatible notification targetingYesYes

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.

After the cutover, your system may continue to see both formats for an extended period, depending on user activity. Low-activity users may retain a phone-number externalId for weeks or months.

For that reason, the format detection logic must remain in place permanently. Do not remove the phone-number fallback after the migration window ends.

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.

additionalIdentifiers is only populated when Meta sends a new event for that user after the migration date. Client records created before the migration date are not updated retroactively. As a result, older client records may not have additionalIdentifiers at all until a new event arrives.

Do not assume this field exists on every client. Always handle both cases:

  • additionalIdentifiers present, but phoneNumber absent
  • additionalIdentifiers absent entirely

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

If Meta stops returning wa_id in later webhooks, Sunshine Conversations may still retain the last known phone number on the client record. The stored phone number is not proactively removed, so the value in your system may continue to exist even when Meta no longer includes it in new webhook payloads.

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.

When handling client:update, always use the full updated client object from the webhook payload as the source of truth. Do not diff against a cached version of the client.

The reason field tells you what triggered the update. The client object tells you the current canonical state, including the latest externalId and additionalIdentifiers.

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": "identifierUpdated"  }}

The reason field

A new enum value identifierUpdated 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
identifierUpdated(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 identifierUpdated 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, there is no user, client, or conversation record to look up from this event. Your failure-handling logic should rely on the notification payload itself, not on entity identifiers that would normally exist after delivery.

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.

Notifications API: BSUID targeting via destinationId

The POST /v1/apps/:appId/notifications endpoint accepts a BSUID in the existing destination.destinationId field, in addition to phone numbers. The existing contract supports BSUID targeting.

Value formatExampleWhen to use
Phone number (E.164)+1234567890Reaching a user for the first time, or when you only have their phone number
BSUIDUS.13491208655302741918Re-engaging a user whose BSUID you received via a client:update webhook or client object

Rules:

  • destinationId is required (unchanged).
  • When a BSUID is provided, it must match the BSUID format. Invalid values return HTTP 400.
  • Phone number validation still applies when the value is not a BSUID.
  • The system auto-detects the format; no additional flags or fields are needed.

Behavior when targeting by BSUID:

  • If an existing client matches the provided BSUID (via externalId), the message is delivered immediately through the existing conversation.
  • If no existing client matches, the deferred delivery path is used: the message is sent to Meta, and entities (user, client, conversation) are created when Meta confirms delivery via the sent status webhook.
  • Entity creation on the sent webhook uses Meta's confirmed IDs, not the customer-provided BSUID.

Note:

  • BSUIDs are only available after an initial interaction with the user.
  • Phone numbers remain the primary way to reach a user for the first time.
  • Once a BSUID is known (for example, from a client:update webhook or a client object), you can pass it directly in destinationId for re-engagement.

What to do

If your integration sends proactive WhatsApp notifications and you want to target users by BSUID, pass the BSUID in destination.destinationId instead of a phone number. No other changes to your request structure are needed.

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.

Under BSUID, client activation is deferred when the recipient is targeted by phone number. The client remains in a pending state until Meta confirms delivery through the sent status webhook. That confirmation is what allows Sunshine Conversations to populate the canonical BSUID identifiers on the client record.

This is why a message is required for WhatsApp client creation by phone number with immediate confirmation: without a message, no confirmation webhook is triggered and the client cannot complete activation.

What is not affected

  • Targeting by BSUID or parentUserId: no message required. Meta returns these identifiers as-is
  • Backward-compatible BSUID targeting through destination.userId: no message required
  • 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 identifierUpdated 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.
If your notification flow supports backward-compatible BSUID targeting, confirm whether you can pass a BSUID in destination.userId as well as destination.destinationId.

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