Skip to content

BSUID + WhatsApp username support

Released: July 1, 2026

Business-Scoped User IDs (BSUID) Added

Section titled “Business-Scoped User IDs (BSUID) ”

WhatsApp introduced usernames in 2026. Users who adopt a username no longer expose their phone number in webhooks after 30 days of no direct interaction. To keep integrations working, Meta ships a per-portfolio identifier called Business-Scoped User ID (BSUID) — format {ISO country}.{alphanumeric}, e.g. US.13491208655302741918. Managed businesses with linked portfolios also get a parent BSUID carrying an ENT segment (US.ENT.11815799212886844830).

Kirimdev now accepts, stores, and exposes both identifiers end-to-end.

Sending: recipient field on POST /v1/{phone_number_id}/messages

Section titled “Sending: recipient field on POST /v1/{phone_number_id}/messages”

to (E.164 phone number) stays required-or-recipient. Send to a BSUID by omitting to and setting recipient instead:

{
"messaging_product": "whatsapp",
"recipient": "US.13491208655302741918",
"type": "text",
"text": { "body": "Hi from BSUID." }
}

If both to and recipient are supplied, Meta uses to (documented precedence). The send response now includes:

  • to — the phone number when sent via to, null otherwise
  • bsuid, parent_bsuid, username — the recipient contact’s identifiers

POST /v1/{phone_number_id}/contacts accepts either phone_number or bsuid (at least one is required). username is display-only info and NOT identity — usernames can change.

GET /v1/{phone_number_id}/contacts gains bsuid and username filters alongside phone. Every contact response now includes bsuid, parent_bsuid, and username (all nullable).

WhatsApp Business Platform rejects OTP authentication templates (one-tap, zero-tap, copy-code) sent to a BSUID with error 131062. The public API pre-flight guard rejects the combo BEFORE calling Meta and returns:

{
"error": {
"type": "invalid_request_error",
"code": "bsuid_not_supported_for_template",
"message": "Business-Scoped User ID recipients are not supported for authentication OTP templates. Send to a phone number (`to`) instead."
}
}

Three tools accept the new identifiers:

  • send_messagerecipient alongside to. Description explains precedence + auth OTP limitation.
  • create_contactphone_number optional, bsuid optional, at least one required. username field added.
  • search_contactsbsuid (exact) and username (case-insensitive exact) filters.

Two changes for subscribers:

  • message.status events now include recipient_user_id and recipient_parent_user_id alongside the existing recipient_id (phone) — matching Meta’s shape 1:1 so BSUID sends can be reconciled without a follow-up lookup.

  • New event type contact.identity_updated fires when Meta rotates a contact’s BSUID (typically after the user changes their phone number). Payload:

    {
    "id": "evt_...",
    "type": "contact.identity_updated",
    "created_at": "2026-07-01T02:00:00Z",
    "data": {
    "contact_id": "ctc_...",
    "old_bsuid": "US.oldbsuid",
    "new_bsuid": "US.newbsuid",
    "old_parent_bsuid": null,
    "new_parent_bsuid": null,
    "phone_number": "628123456789"
    }
    }

    Subscribe to contact.identity_updated on any webhook subscription to keep your BSUID→user mapping fresh without polling.

@kirimdev/sdk ships the shape via a fresh openapi.json snapshot. SendMessageParams gains recipient; CreateContactParams gains bsuid + username; ListContactsParams gains bsuid + username filters; Contact + Message types expose the same three fields. Zero migration for existing callers using to + phone_number.

// Send by BSUID
await kirim.phoneNumbers(pnid).messages.send({
messaging_product: 'whatsapp',
recipient: 'US.13491208655302741918',
type: 'text',
text: { body: 'Hi.' },
})
// Search by BSUID
const paginator = kirim.phoneNumbers(pnid).contacts.list({
bsuid: 'US.13491208655302741918',
})

Broadcast recipients can be BSUID-only (contacts whose phone number is unknown). The worker’s dispatcher picks the identifier per row — phone wins over BSUID on Meta precedence. Auth OTP templates on BSUID-only recipients are pre-flighted to failed with bsuid_not_supported_for_template before enqueuing.

CSV imports still expect a phone_number column; audience-by-label / audience-by-selected pick up BSUID-enriched contacts automatically.

Read the sending docs →