Changelog

What's new in the Chat Bridge public API

Latest changes on the WhatsApp and RCS channels. Backwards compatibility is total in every case: integrations that do not use the new fields keep working unchanged.

1. WhatsApp — Send by BSUID

Besides the E.164 number, POST /v1/whatsapp/messages now accepts a BSUID (Business Solution User ID) in the to field. A BSUID is the anonymous identifier Meta assigns to a contact when the conversation comes from a “Click to WhatsApp” flow or another channel where Meta does not expose the user’s real number — it lets you reply without knowing their phone. Inbound webhooks carry it in message.from_user_id.

FormatPatternExample
E.164^\+[1-9]\d{1,14}$+5215512345678
BSUID^[A-Z]{2}\.[A-Za-z0-9]{1,128}$MX.1264908862298277
1{
2 "to": "MX.1264908862298277",
3 "type": "TEXT",
4 "text": { "body": "Hola, gracias por contactarnos" }
5}

If to matches neither format, the API rejects with 400. The format applies equally to template sends (type: TEMPLATE) and every other message type.

2. WhatsApp — Automatic SMS fallback

POST /v1/whatsapp/messages now admits an optional fallback to SMS: if the WhatsApp send fails definitively (synchronously or through a status webhook), the API automatically retries the same recipient over SMS — without the integrator having to detect the failure and resend.

Each send decides whether it wants the fallback and with which content, in the new fallback field:

1{
2 "to": "+5215512345678",
3 "type": "TEMPLATE",
4 "template": { "...": "..." },
5 "fallback": {
6 "sms": {
7 "mask": "665f1a2b3c4d5e6f7a8b9c0d",
8 "text": "No pudimos contactarte por WhatsApp. Tu pedido #1234 está confirmado.",
9 "has_accents": true,
10 "complement": {
11 "link": "https://midominio.com/tracking/1234",
12 "ttl_events": 1440
13 }
14 }
15 }
16}

fallback.sms uses exactly the same shape as the body of POST /v1/sms/messages (mask, text, has_accents, complement) except to — the recipient is inherited from the original WhatsApp message. The mask is validated at the moment of the original send (an invalid or disabled mask rejects the whole request with 400 before attempting WhatsApp). The fallback fires only once per message (atomic lock) and it is a single hop: WhatsApp → SMS, no further cascade.

The feature must be enabled for the account by whoever manages it:

HTTPerrorCodeDescription
403380APP_KEY_FALLBACK_REQUIRES_POSTPAID — the multichannel fallback requires the channel to be postpaid
403381APP_KEY_FALLBACK_ACCOUNT_DISABLED — the account does not have the multichannel fallback enabled

Synchronous response when the fallback activates. If the send fails synchronously and the request included fallback.sms, the 201 response includes fallback_id — the identifier to correlate the backup SMS in the webhooks:

1{
2 "message_id": "",
3 "status": "queued",
4 "fallback_id": "b3f1c2d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
5}

Webhooks. The status events of the backup SMS arrive as SMS webhooks carrying origin_identifier — the message_id of the WhatsApp send that originated the fallback. If the backup SMS also fails (or could not be generated, e.g. the to was a BSUID with no real phone behind it), a final Fallback exhausted event is emitted:

1{
2 "message_id": "b3f1c2d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
3 "status": "FALLBACK_EXHAUSTED",
4 "origin_identifier": "0f8e7d6c-5b4a-3928-1706-f5e4d3c2b1a0"
5}

See the Fallback exhausted webhook in the WhatsApp API reference for the message_id / origin_identifier semantics of each case.

3. RCS — Automatic SMS fallback

Same mechanism as section 2, applied to POST /v1/rcs/messages: if the RCS message is not delivered, the API automatically retries over SMS.

1{
2 "to": "5512345678",
3 "template_id": "665f1a2b3c4d5e6f7a8b9c0d",
4 "agent_id": "665f1a2b3c4d5e6f7a8b9c9a",
5 "variables": { "nombre": "Iván" },
6 "fallback": {
7 "sms": {
8 "mask": "665f1a2b3c4d5e6f7a8b9c0d",
9 "text": "No pudimos contactarte por RCS. Tu pedido #1234 está confirmado."
10 }
11 }
12}

What fires it:

  • The RCS delivery status webhook reports FAILED or EXPIRED for the message.
  • A late FAILED/EXPIRED that the RCS status priority logic already discards (a suppressed downgrade, an out-of-order event) does not fire the fallback — only a failure that actually advances the message status does.
  • Unlike WhatsApp, RCS has no synchronous failure point (the send always responds 202 Accepted) — the RCS fallback fires only through webhooks.

Webhooks work as in section 2: the backup SMS statuses arrive as SMS webhooks with origin_identifier, and the chain ends with a Fallback exhausted event. The mask of the fallback is validated when sending the original RCS message (fast 400), not when the fallback fires hours later.

4. RCS — Free-form sends (no template)

POST /v1/rcs/messages now accepts the message content directly in the body, without a pre-created and approved template. The mode is selected with the new discriminator field type:

typeModeRequired content field
TEMPLATE (default)Template-based — previous behavior, unchangedtemplate_id
TEXTFree-form: plain texttext
FILEFree-form: one file (image/video)contentInfo
RICH_CARDFree-form: single rich cardrichCard.standaloneCard
CAROUSELFree-form: carousel of 2 to 10 cardsrichCard.carouselCard

If type is omitted, TEMPLATE is assumed: existing integrations do not change.

1{
2 "to": "5512345678",
3 "type": "TEXT",
4 "agent_id": "665f1a2b3c4d5e6f7a8b9c9a",
5 "text": "Hola {{nombre}}, tu pedido va en camino.",
6 "variables": { "nombre": "Iván" },
7 "suggestions": [{ "reply": { "text": "Rastrear" } }]
8}

Rules to keep in mind:

  1. template_id is forbidden (400) with any free-form type, and required with TEMPLATE.
  2. variables also applies to free-form content: the {{placeholders}} written in text, contentInfo, or richCard are substituted with the same type rules (URL in fileUrl/thumbnailUrl/iconUrl/url, free text elsewhere) and the same HEAD media validation.
  3. variables.UUID_MESSAGE is a reserved name, now rejected explicitly with 400 in the five types (before, in the template path, it was silently overwritten — the only behavior change in the TEMPLATE flow).
  4. One content per request → 1 credit per free-form send.
  5. The billing channel derives from the agent, not the content: a CONVERSATIONAL agent bills as RCS_CONVERSATIONAL whether it sends text or a rich card; same for the conversation window.
  6. Root-level suggestions (message-level chips, max 11) are independent from the ones embedded inside a card, and can be combined. Never send postbackData — the backend generates it.
  7. Agent ↔ content compatibility (replaces RCS_TYPE_MISMATCH, which stays exclusive to TEMPLATE mode):
Agent typeTEXTFILERICH_CARDCAROUSELsuggestions
BASIC
SINGLE
CONVERSATIONAL

New errors:

HTTPerrorCodeCodeWhen
400376RCS_CONTENT_TYPE_NOT_ALLOWED_FOR_AGENTThe content type is not compatible with the agent’s type (e.g. RICH_CARD with a BASIC agent).
400377RCS_SUGGESTIONS_NOT_ALLOWEDsuggestions were sent with a BASIC agent, which has no buttons.

Unchanged: the 202 Accepted response, the webhooks (same envelope, same correlation by message_id, clicks on free-form suggestions arrive classified with action/element_type/element_value exactly like template ones), and the agents/templates endpoints. In the App-Key CSV report, free-form sends show template_name = Envío libre, with the full content (including type) in the data column.

5. WhatsApp — Inbound contacts messages (vCard)

The Inbound message webhook supports a new content type: contacts. It fires when a user shares one or more contacts (vCard) in the conversation.

message.type can now be "contacts" (previously: text, image, video, document, audio, button, location).

New message.contacts field (array): the shared contacts. Every field is optional — Meta omits the ones the user did not share from their address book.

FieldTypeDescription
contacts[].name.formatted_namestringFull formatted name, as displayed in WhatsApp
contacts[].name.first_namestringFirst name
contacts[].name.last_namestringLast name
contacts[].name.middle_namestringMiddle name
contacts[].name.prefixstringName prefix (e.g. “Sr.”, “Dra.”)
contacts[].name.suffixstringName suffix (e.g. “Jr.”, “III”)
contacts[].org.companystringCompany associated with the contact
contacts[].org.departmentstringDepartment within the company
contacts[].org.titlestringContact’s job title
contacts[].phones[].phonestringPhone number in human-readable format
contacts[].phones[].wa_idstringWhatsApp identifier of the phone number
contacts[].phones[].typestringPhone type (CELL, WORK, HOME, etc.)
contacts[].emails[].emailstringContact’s email address
contacts[].emails[].typestringEmail type (WORK, HOME, etc.)
contacts[].urls[].urlstringURL associated with the contact
contacts[].urls[].typestringURL type (WORK, HOME, etc.)
contacts[].addresses[].streetstringStreet
contacts[].addresses[].citystringCity
contacts[].addresses[].statestringState/province
contacts[].addresses[].zipstringPostal code
contacts[].addresses[].countrystringCountry
contacts[].addresses[].country_codestringCountry code (ISO)
contacts[].addresses[].typestringAddress type (WORK, HOME, etc.)
contacts[].birthdaystringBirth date, YYYY-MM-DD format

Note: in real traffic we have also observed the fields vcard (the full vCard in Base64) and origin (e.g. "other"), which are not part of Meta’s officially documented schema but do arrive in production. Those fields — and any other not listed above — are propagated as-is: do not assume a closed shape.

Example:

1{
2 "phone_number_id": "123456789012345",
3 "message_id": "wamid.HBgNNTIxNTU1MTIzNDU2NxUCABIYFjNFQjBDMTAyRjg5NzRBMEE4RUNE",
4 "type": "message",
5 "message": {
6 "from": "5215551234567",
7 "from_user_id": "MX.1264908862125796",
8 "timestamp": "1716249600",
9 "type": "contacts",
10 "status": "reply",
11 "contacts": [
12 {
13 "name": {
14 "formatted_name": "Juan Pérez",
15 "first_name": "Juan",
16 "last_name": "Pérez"
17 },
18 "org": {
19 "company": "Acme Corp"
20 },
21 "phones": [
22 {
23 "phone": "+52 55 1234 5678",
24 "wa_id": "5215512345678",
25 "type": "CELL"
26 }
27 ],
28 "vcard": "RkVzSU=="
29 }
30 ],
31 "context": {
32 "message_id": "wamid.HBgNNTIxNTU1MTIzNDU2NxUCABEYEjNFQjBDMTAyRjg5NzRBMEE4RQ=="
33 }
34 }
35}