Skip to navigation

Changelog

What's new in the Chat Bridge public API

Latest changes in the public API. Integrations that do not use the new fields or features keep working unchanged, except where a change is marked as Behavior change.

1. SMS — Get a message

New GET /v1/sms/messages/{message_id} endpoint that returns the current status and data of a message. It works with every message_id you receive: a single send, each number of a batch, or the fallback_id of a WhatsApp/RCS send with SMS fallback.

curl -X GET https://connect.chatbridge.mx/v1/sms/messages/b3e1c2a4-9f3d-4b7a-8c1e-2a6f9d3b7e10 \
-H "x-api-key: <your-api-key>"
{
"message_id": "b3e1c2a4-9f3d-4b7a-8c1e-2a6f9d3b7e10",
"to": "5555555555",
"message": "mensaje ejemplo Chatbridge.",
"status": "SENT",
"operator": "telcel",
"created_at": "2026-10-19T17:00:00.000Z",
"sent_at": "2026-10-19T17:00:01.000Z",
"failed_at": null
}
  • status uses the same values as the status webhook. message is the final text that was sent, including the short link when the send had a complement. to is the number as it was sent: 10 digits for a national mask, E.164 without + for an international one.
  • batch_id is included only for numbers of a batch, and origin_identifier (the message_id of the original WhatsApp/RCS send) only for fallback SMS.
  • Any SMS App-Key of the same account can query the message. A message from another account responds 404 with errorCode 591. A single send rejected with 400 is not stored, so its id also responds 404.
  • A message_id that is not a UUID responds 400 with errorCode 400.
  • Each query counts toward the App-Key’s rate limit like any other request.

See SMS → Messages → Get message for the full reference.

2. SMS — Specific errors for the destination number

POST /v1/sms/messages now tells you why the destination number was rejected. Before, every rejection of to responded errorCode 352. Now:

  • 352 is only for a national mask whose to is not 10 digits. A national mask also accepts the Mexico country code 52/+52 or the old mobile prefix 521/+521 (for example, 525555555555 or +5215555555555); the prefix is removed before sending and storing the number.
  • 571: international mask and to is not E.164. It must always include the country code, for example +16462713111, or +525512345678 for Mexico.
  • 572: national mask and the number does not exist in the numbering plan.
  • 573: national mask and the number exists but is not a mobile line (for example, a landline).
  • A temporary failure while checking the numbering plan now responds 500 and can be retried, instead of rejecting the number as invalid.
  • Error messages are now in Spanish, like the rest of the SMS errors.

Behavior change: if your integration uses errorCode 352 to detect any invalid number, also handle 571, 572 and 573.

In POST /v1/sms/batches nothing changes: rejected numbers are still reported per number as INVALID_FORMAT, INVALID_SOURCE or OUTPUT_NOT_FOUND.

SMS-specific errors

errorCodeHTTPmessageCause
351400Máscara inválida o no disponible para esta cuentamask does not exist or does not belong to the App-Key’s account
352400Número destino inválido: las máscaras nacionales requieren 10 dígitos, con o sin lada 52 o 521National mask and to is not 10 digits (with or without 52/+52 or 521/+521)
353400La máscara no tiene salida configurada para el operador del destinatarioThe mask has no output for the recipient’s operator
355400La máscara está deshabilitadaThe mask exists but is disabled
571400Número destino inválido: las máscaras internacionales requieren formato E.164International mask and to is not E.164
572400Número destino inválido: no existe en el plan de numeraciónNational mask and the number is not in the numbering plan
573400El número destino no es una línea móvilNational mask and the number is not a mobile line
591404El mensaje no existe o no pertenece a la cuentaGET /v1/sms/messages/{message_id}: unknown message or from another account
500500Internal Server Error.Unhandled internal error, including a temporary failure checking the numbering plan; can be retried
{
"message": "El número destino no es una línea móvil",
"error": "Bad Request",
"statusCode": 400,
"errorCode": 573
}

See SMS → Messages → Send message for the full reference.

complement.ttl_events (minutes during which clicks on the short link are recorded) now accepts values from 60 (1 hour) to 4320 (72 hours). The default is still 1440 (24 hours) when it is omitted.

The range applies to:

  • POST /v1/sms/messages
  • POST /v1/sms/batches, both the batch complement and each number’s
  • fallback.sms.complement in WhatsApp and RCS sends

A value outside the range responds 400 with errorCode 400:

{
"message": "complement.ttl_events no puede exceder 4320 minutos (72 horas)",
"error": "Bad Request",
"statusCode": 400,
"errorCode": 400
}

Behavior change: values below 60 or above 4320 used to be accepted and are now rejected.

4. RCS — Batch sends

New POST /v1/rcs/batches endpoint to send the same RCS content (a template or free-form content) to up to 250 recipients in a single request, personalizing each number’s variables. It responds 202 immediately with a batch_id and one message_id per number (in the order of the request); the batch is processed in the background.

  • The content and delivery settings (agent_id, type, template_id, text, contentInfo, richCard, suggestions, ttl_message, ttl_events, fallback) go once at the root; each item of messages carries only its to and variables.
  • Content validations run once and reject the whole batch if any fails; numbers rejected by destination (INVALID_FORMAT, INVALID_SOURCE, OUTPUT_NOT_FOUND) do not stop the others.
  • webhook_mode: INDIVIDUAL (default) keeps the regular per-number webhooks now with batch_id; webhook_mode: BATCH sends a single batchStatus webhook with the result of every number.
  • A batch counts as a single request for the rate limit.
curl -X POST https://connect.chatbridge.mx/v1/rcs/batches \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "66f0c2a1e4b0a1b2c3d4e5f6",
"template_id": "66f0c2a1e4b0a1b2c3d4e5f7",
"webhook_mode": "BATCH",
"messages": [
{ "to": "5555555555", "variables": { "nombre": "Ana", "pedido": "123" } },
{ "to": "5544444444", "variables": { "nombre": "Luis", "pedido": "456" } }
]
}'

See RCS → Messages → Send batch and RCS → Webhooks → Batch status for the full reference.

5. WhatsApp — Batch sends

New POST /v1/whatsapp/batches endpoint to send WhatsApp messages to up to 250 recipients in a single request. It responds 202 immediately with a batch_id and one message_id per recipient (in the order of the request); the messages are sent in the background.

  • type goes at the root and each item of messages carries its own to and the content of that type (text, image, template…). All 11 message types are supported.
  • fallback.sms at the root is the SMS fallback of every recipient; each item of messages can carry its own fallback.sms, which replaces the root one for that recipient. The response returns a fallback_id per recipient with fallback.
  • webhook_mode: INDIVIDUAL (default) keeps the regular status webhook per recipient, now with batch_id; webhook_mode: BATCH sends a single batchStatus webhook with the status of every recipient.
  • Media URLs are validated against public destinations only: URLs pointing to private networks are rejected with errorCode 575, also in POST /v1/whatsapp/messages.
  • A batch counts as a single request for the rate limit.
curl -X POST https://connect.chatbridge.mx/v1/whatsapp/batches \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"type": "TEXT",
"messages": [
{ "to": "+5215512345678", "text": { "body": "Hola Ana, tu pedido va en camino." } },
{ "to": "+5215587654321", "text": { "body": "Hola Luis, tu pedido va en camino." } }
]
}'

See WhatsApp → Messages → Send batch and WhatsApp → Webhooks → Batch status for the full reference.

6. Email — Transactional sends

New Email channel: POST /v1/email/messages sends an email from a template of your account. The send is asynchronous: it responds 202 with a message_id, and the events (SENT, DELIVERED, BOUNCED, OPENED, CLICKED…) are notified through webhooks. The App-Key is created with the sender (mailing output) linked, so the request only carries the recipient, the template, the subject, and the template variables. GET /v1/email/sender and GET /v1/email/templates let you query the sender and the templates (with the variables each one expects).

curl -X POST https://connect.chatbridge.mx/v1/email/messages \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"to": "customer@example.com",
"template_id": "6a4bffd5e77672e33fd53050",
"subject": "Your order is confirmed",
"variables": { "name": "Ana", "order_number": 10482 }
}'

Variable values are HTML-escaped, and every placeholder of the template must be sent. See the Email API reference for the full contract and the webhook payload.

7. SMS — Batch sends

New POST /v1/sms/batches endpoint to send SMS to up to 250 numbers in a single request. It responds 202 immediately with a batch_id and one message_id per number (in the order of the request); the numbers are processed in the background.

curl -X POST https://connect.chatbridge.mx/v1/sms/batches \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"mask": "6a4bffd5e77672e33fd5304f",
"text": "Tu pedido va en camino.",
"webhook_mode": "BATCH",
"messages": [
{ "to": "5555555555" },
{ "to": "5544444444", "text": "Tu pedido llegó a la sucursal.", "has_accents": true },
{ "to": "5533333333", "complement": { "link": "https://mitienda.mx/pedidos/5533333333" } }
]
}'
  • text, has_accents and complement at the root are the batch defaults; each number can override any of them. A number’s complement replaces the batch one entirely, and every number with a complement gets its own short link.
  • An invalid number does not reject the batch: it is reported per number as INVALID_FORMAT, INVALID_SOURCE or OUTPUT_NOT_FOUND.
  • webhook_mode: INDIVIDUAL (default) keeps the regular status webhook per number, now with batch_id. webhook_mode: BATCH sends a single batchStatus webhook with every number’s status once all of them have a send result; later changes arrive as per-message webhooks.
  • A batch counts as one request for the rate limit.

See SMS → Messages → Send batch and SMS → Webhooks → Batch status for the full reference.

8. OAuth 2.0 client credentials

Besides the x-api-key header, every endpoint now accepts an OAuth 2.0 access token in the Authorization: Bearer header. The token is obtained from the new POST /api/oauth/token endpoint (grant client_credentials) with the client_id / client_secret pair the Chat Bridge team issues for your App-Key.

curl -X POST https://connect.chatbridge.mx/api/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"client_credentials","client_id":"ck_...","client_secret":"..."}'
{ "access_token": "eyJhbGciOi...", "token_type": "Bearer", "expires_in": 3600 }
  • Tokens last 1 hour. Request a new one before it expires and reuse it: the token endpoint is limited to 30 requests per minute per IP, and 5 failed attempts block the IP for 15 minutes.
  • Credentials can go in the JSON body, as application/x-www-form-urlencoded, or as Authorization: Basic base64(client_id:client_secret).
  • The token identifies the same App-Key: account, channel, webhooks and rate limit do not change. If both headers are sent, Bearer wins.
  • When a secret is rotated the previous one keeps working for 24 hours.
  • An App-Key can be switched to OAuth only; from then on x-api-key responds 401 with errorCode 450.

See OAuth 2.0 in the Authentication section for the full reference.

New Tools → Short Links section with a URL shortener on the sl2.mx domain, available to every App-Key regardless of its channel:

  • POST /shortlinks shortens an absolute http/https URL (up to 2048 characters) and returns a 10-character identifier plus the ready-to-use short_link. Links expire after a fixed 3 days.
  • GET /shortlinks/{code} returns whether the link is still active, its creation and expiry dates and the accumulated total_clicks. Only the user of the App-Key that created the link can query it (403, errorCode 484 otherwise; 404, errorCode 483 if the code does not exist).
curl -X POST https://connect.chatbridge.mx/shortlinks \
-H "x-api-key: YOUR_APP_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.example.com/tracking/1234"}'
{ "identifier": "aB3dE5gH7j", "url": "https://www.example.com/tracking/1234", "short_link": "sl2.mx/aB3dE5gH7j" }

Invalid URLs respond 400 with errorCode 482 and metadata.reason (url_required, url_too_long, url_not_absolute, invalid_scheme, url_control_chars).

Previous changes (WhatsApp and RCS)

10. 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
{
"to": "MX.1264908862298277",
"type": "TEXT",
"text": { "body": "Hola, gracias por contactarnos" }
}

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

11. 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:

{
"to": "+5215512345678",
"type": "TEMPLATE",
"template": { "...": "..." },
"fallback": {
"sms": {
"mask": "665f1a2b3c4d5e6f7a8b9c0d",
"text": "No pudimos contactarte por WhatsApp. Tu pedido #1234 está confirmado.",
"has_accents": true,
"complement": {
"link": "https://midominio.com/tracking/1234",
"ttl_events": 1440
}
}
}
}

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:

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

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:

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

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

12. RCS — Automatic SMS fallback

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

{
"to": "5512345678",
"template_id": "665f1a2b3c4d5e6f7a8b9c0d",
"agent_id": "665f1a2b3c4d5e6f7a8b9c9a",
"variables": { "nombre": "Iván" },
"fallback": {
"sms": {
"mask": "665f1a2b3c4d5e6f7a8b9c0d",
"text": "No pudimos contactarte por RCS. Tu pedido #1234 está confirmado."
}
}
}

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 11: 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.

13. 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.

{
"to": "5512345678",
"type": "TEXT",
"agent_id": "665f1a2b3c4d5e6f7a8b9c9a",
"text": "Hola {{nombre}}, tu pedido va en camino.",
"variables": { "nombre": "Iván" },
"suggestions": [{ "reply": { "text": "Rastrear" } }]
}

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.

14. 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:

{
"phone_number_id": "123456789012345",
"message_id": "wamid.HBgNNTIxNTU1MTIzNDU2NxUCABIYFjNFQjBDMTAyRjg5NzRBMEE4RUNE",
"type": "message",
"message": {
"from": "5215551234567",
"from_user_id": "MX.1264908862125796",
"timestamp": "1716249600",
"type": "contacts",
"status": "reply",
"contacts": [
{
"name": {
"formatted_name": "Juan Pérez",
"first_name": "Juan",
"last_name": "Pérez"
},
"org": {
"company": "Acme Corp"
},
"phones": [
{
"phone": "+52 55 1234 5678",
"wa_id": "5215512345678",
"type": "CELL"
}
],
"vcard": "RkVzSU=="
}
],
"context": {
"message_id": "wamid.HBgNNTIxNTU1MTIzNDU2NxUCABEYEjNFQjBDMTAyRjg5NzRBMEE4RQ=="
}
}
}