> For the complete documentation index, see [llms.txt](https://docs.channel360.co.za/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.channel360.co.za/api-usage/using-the-channel360-v1.1-api/conversations/whatsapp-usernames-and-bsuid.md).

# WhatsApp usernames and BSUID

{% hint style="warning" %}
**Draft**

This guidance reflects live traffic captured on 13 August 2026.

Provider rollout timing is still being confirmed.
{% endhint %}

### Overview

WhatsApp is decoupling a user's identity from their phone number through usernames.

As this rolls out, identity fields in the `message:appUser` webhook change shape.

If your integration reads a phone number from the webhook, update it.

### What changes

The `client` object in the webhook payload is affected.

Fields that previously carried a phone number now carry an opaque **Business-Scoped User ID (BSUID)**.

A BSUID is a string prefixed with a two-letter country code, such as `ZA.2c7a9f3e8b...`.

A BSUID is not a phone number.

When the user still shares their number, it appears in `additionalIdentifiers`.

**Before**

```json
"client": {
  "externalId": "27824420729",
  "displayName": "+27 82 442 0729",
  "raw": {
    "profile": { "name": "Jane Doe" },
    "from": "27824420729"
  }
}
```

**After**

```json
"client": {
  "externalId": "ZA.2c7a9f3e8b...",
  "displayName": "ZA.2011621412753082",
  "additionalIdentifiers": [
    { "key": "phoneNumber", "value": "27606626358" }
  ],
  "raw": {
    "profile": { "name": "Jane Doe" },
    "from": "ZA.2c7a9f3e8b..."
  }
}
```

`additionalIdentifiers` is an array of key/value entries, not an object.

Find the entry whose `key` is `"phoneNumber"` and use its `value`.

Other keys may appear, including `parentUserId`.

`parentUserId` identifies a parent account in a portfolio hierarchy.

Ignore unknown keys. This keeps your integration forward-compatible.

Live phone values have no leading `+`, such as `"27606626358"`.

Normalise phone numbers to your own canonical format.

`displayName` is not a reliable human label after migration.

It mirrors the BSUID. Use `raw.profile.name` for the WhatsApp profile name.

`raw.profile.name` may be absent when the user has not set one.

`raw.from` is in transition. It often still carries a phone number today.

The provider documents it becoming a BSUID. Do not build on either format.

Read the phone from `additionalIdentifiers`.

Users who adopt a WhatsApp username can message you without sharing a number at all.

The rest of the payload stays the same, including `trigger`, `appUser`, `conversation`, and `messages`.

### What you must do

1. **Stop treating `externalId` and `raw.from` as phone numbers.** `externalId` becomes a BSUID. `raw.from` may contain either format. We do not rewrite them.
2. **Find the `phoneNumber` entry in `additionalIdentifiers`.** Handle a missing entry. Never store a BSUID in a phone-number field.
3. **Do not assume a cutover date.** Existing contacts are not back-filled, so both formats coexist for some time. Detect the value format instead.
4. **Correlate on Channel360 IDs, not the phone number.** Match inbound replies on `conversation._id` and `appUser._id`. Keep your own phone-number record keyed to those IDs.

### How to detect the format

BSUIDs can contain only digits after the dot.

Do not detect them by checking for letters or numeric-only values.

```js
// A BSUID has an uppercase country prefix and an opaque suffix.
// The suffix may contain only digits.
const isBsuid = (v) =>
  typeof v === 'string' && /^[A-Z]{2}\.[A-Za-z0-9]+$/.test(v);

// Check isBsuid first. A phone contains digits, with an optional '+'.
const isPhone = (v) =>
  typeof v === 'string' && !isBsuid(v) && /^\+?[0-9]{6,15}$/.test(v);

// Find a phone number in the identifier array.
const phoneOf = (client) =>
  (client?.additionalIdentifiers ?? [])
    .filter((i) => i?.key === 'phoneNumber')
    .map((i) => i.value)
    .find(isPhone) ?? null;
```

`null` means no phone is available. This is normal and permanent.

Do not retry to obtain a phone number.

### Recommended approach

This follows the same principle as [Recommended source of truth](/api-usage/using-the-channel360-v1.1-api/conversations.md).

Treat your own system as authoritative for the phone number.

Use Channel360 and WhatsApp identifiers for correlation.

### Identity refresh events are not forwarded

Channel360 does not relay `client:update` events to client webhooks.

This includes events with `reason: identifierUpdated`.

Channel360 also does not relay `user:merge` events. Do not wait for them.

**What this means:** the phone captured during a notification remains authoritative.

You receive no notification when a customer becomes username-only.

You also receive no notification when the provider later reveals a phone.

Your record refreshes only when the customer messages you with a phone present.

**Mitigation:** keep your own phone-to-identifier mapping at send time.

Map the phone number to `appUser._id` and `conversation._id`.

[Conversations](/api-usage/using-the-channel360-v1.1-api/conversations.md) describes the capture points.

This mapping is the source of truth.

### You cannot currently target a BSUID

The Channel360 v1.1 API accepts phone numbers as send destinations.

Sending to a BSUID is not supported.

For a username-only customer, you can reply through this endpoint:

```
/v1.1/org/:orgId/whatsapp/appuser/:appuser/reply
```

The reply must fall within the 24-hour window their inbound message opened.

You cannot re-engage them after that window closes.

Template sends require a phone number.

Capture consent and ordering context during the live conversation window.

### Phone-less customers and subscriber-scoped endpoints

Channel360 subscribers are keyed by phone number.

A BSUID-only customer has no subscriber record.

They do not appear in subscriber list or detail endpoints.

They cannot join recipient lists or campaigns.

Phone-addressed endpoints return no data for them. For example:

```
/v1.1/org/:orgId/whatsapp/subscriber/:destinationId/conversation/history
```

Their conversation remains available through `appUser`-addressed endpoints, including reply and export.
