Messages
Send service notifications (order shipped, appointment confirmed, payslip available...) to your customers and employees through the xYago app chat, from a verified business profile. Delivery is through the xYago network only: if the person does not have the app yet, the message waits for them for up to 7 days.
Before sending
- In the panel → API Developer → Senders, create your sender (its id,
e.g.
tiendaexpress, display name, logo, website and category) and submit it for review. xYago verifies and approves it. - Create a template for each kind of notification, with its texts in
es,enand/ordeand numbered variables{{1}},{{2}}... Submit it for review: xYago approves it. Only service notifications (categoryutility) are allowed, never advertising. No links in the text: to link, add a button (up to 3 quick reply or link buttons; see Templates and buttons). - Enable the Messages product and create an API key with the
messages:writescope (andmessages:readto query).
Use GET /senders and GET /templates to check the review status
of your senders and templates through the API.
Send a message
curl -X POST 'https://api.xyago.com/v1/messages' \
-H "Authorization: Bearer $XYAGO_API_KEY" \
-H "Idempotency-Key: pedido-TE-48213-enviado" \
-H "Content-Type: application/json" \
-d '{"to": "+573005550123", "sender_id": "tiendaexpress", "template": "pedido_enviado",
"language": "en", "variables": ["Valentina", "TE-48213"], "reference": "CLI-000123"}'
202 response:
{
"id": "7a1c9e35-2b4d-4f6a-8c0e-1d3f5a7b9c21",
"status": "sent",
"to_masked": "+57 ••• ••• 0123",
"sender_id": "tiendaexpress",
"template": "pedido_enviado",
"language": "es",
"reference": "CLI-000123",
"failure_code": null,
"created_at": "2026-11-04T15:00:00Z",
"sent_at": "2026-11-04T15:00:01Z",
"delivered_at": null,
"read_at": null,
"expires_at": null
}
variables: values of{{1}}..{{n}}in order. Plain text (max. 256 characters, no links outside your domain); their number must match the template'svariables_count(422 template_variables_mismatch).language: if the template lacks that language its default one is used. The buttons are shown in the same language as the text.reference(optional, max. 64): your customer id. Shown in the panel and usable as a filter:GET /messages?reference=....button_variables(only if the template has a link button with a variable): the value of that variable by button index, e.g.{"0": "TE-48213"}(422 invalid_button_variablesif missing or unexpected). See Buttons.
Statuses
| Status | Meaning |
|---|---|
accepted | Received by the API (transient). |
pending_install | The phone does not use xYago yet: the message waits up to 7 days (expires_at). |
sent | Stored in the recipient's chat. |
delivered | Reached one of the recipient's devices. |
read | The recipient opened the chat. |
failed | Will not be delivered. failure_code: recipient_not_on_xyago (did not install xYago within 7 days), undeliverable, sender_suspended, invalid_message. |
Query a message with GET /messages/{id} or list yours with
GET /messages?status=&sender_id=&reference=; better, subscribe to the
webhooks.
Recipients without xYago (pending_install)
If the phone does not use xYago yet, the API does not fail: it answers 202
with status: "pending_install". xYago keeps the message encrypted and delivers
it as soon as that person installs the app and verifies their number (you get
message.sent). If they do not within 7 days the message ends as
failed with recipient_not_on_xyago (you get
message.failed) and the text is deleted. To speed this up,
invite your users.
If the recipient cannot receive messages from your sender the API answers
409 undeliverable. Do not retry that message.
Idempotency-Key (required)
Every POST /messages carries the Idempotency-Key header (8-128
characters A-Z a-z 0-9 . _ : -), unique per message: the notification id in
your system or a UUID. If the response is lost (timeout, 502,
503), repeat the request with the same key and the
same body:
| Repetition | Response |
|---|---|
| Same key, same body | 200 with the original message and the Idempotent-Replayed: true header (or the same error if it had failed). Never sent twice. |
| Same key, different body | 409 idempotency_key_reused. |
Keys are per environment: the same key in sandbox and live are different messages.
Limits
Besides the API key's per-minute request limit, each sender has caps against spam:
messages per minute (the product limit in the panel), messages per recipient per day (10)
and new recipients per day (1,000). Above them the API answers 429 rate_limited
with Retry-After. If xYago detects that many messages go to phones without
xYago, it pauses the sender's messages to new recipients
(403 new_recipients_paused) until it reviews it with you.
If the limits cannot be checked at that moment nothing is sent, for safety:
503 limits_unavailable. Retry with the same Idempotency-Key.
Webhooks
Subscribe in the panel to message.sent, message.delivered,
message.read, message.failed, message.button_clicked
(button tapped), sender.status_changed
and template.status_changed (see Webhooks). Its
data is the same object returned by GET /messages/{id}:
{
"created_at": "2026-03-02T15:00:04Z",
"data": {
"id": "3e5a7c91-4b6d-4e8f-a0c2-5d7f9b1e3a64",
"status": "sent",
"to_masked": "+57 ••• ••• 0187",
"sender_id": "tiendaexpress",
"template": "pedido_enviado",
"language": "es",
"reference": "CLI-000087",
"failure_code": null,
"created_at": "2026-11-04T15:00:00Z",
"sent_at": "2026-11-05T08:10:00Z",
"delivered_at": null,
"read_at": null,
"expires_at": "2026-11-11T15:00:00Z"
},
"environment": "live",
"id": "evt_4f9c2b7a1d3e4c5b8a6f0e1d2c3b4a59",
"type": "message.sent"
}
Sandbox
With an xy_test_ key messages are real (they reach the
app), but only to your test numbers: up to 5, which must use xYago. Add
them in the panel → Sandbox; each one receives in xYago a 6-digit code from
xYago Sandbox that you type in the panel to confirm it. In sandbox you can use any
sender of your account and approved or draft templates; while your sender is
not approved, messages arrive from xYago Sandbox prefixed with your sender's
display name, e.g. [Sandbox · Tienda Express] Hi Valentina, …. Sending to
another number answers
422 sandbox_recipient_not_allowed.