Plantillas y botones
Las plantillas son los avisos que puedes enviar con
POST /messages: textos por idioma con variables, hasta 3
botones y una revisión de xYago antes de usarlas en producción. Se crean en el panel
(API Developer → Plantillas); por API solo se consultan
(GET /templates).
Texto
- Solo avisos de servicio (categoría
utility): nunca publicidad. - Un texto por idioma (
es,en,de), de 1 a 1.024 caracteres, con variables numeradas{{1}},{{2}}... (las mismas en todos los idiomas, máx. 20). - Sin enlaces en el texto: un texto con
http://,https://owww.se rechaza con422 link_in_body_not_allowed. Para enlazar usa un botónurl.
Botones
Una plantilla puede tener hasta 3 botones, que aparecen bajo el mensaje
en el mismo orden. Como el cuerpo, el texto del botón va por idioma
(texts, con la misma forma que languages: p. ej.
{"es": "Seguir pedido", "en": "Track order"}): uno por cada idioma de la
plantilla, de máx. 20 caracteres, sin variables y distinto del de los demás botones. Cada
mensaje muestra los botones en el mismo idioma que el cuerpo.
| Tipo | Campos | Qué pasa al pulsarlo |
|---|---|---|
quick_reply | texts, payload (opcional, máx. 128, fijo en la plantilla) | Recibes el webhook message.button_clicked con tu payload. No es un mensaje: el usuario no escribe nada y el payload nunca llega a su teléfono. |
url (máx. 2) | texts, url | Se abre el enlace (y recibes message.button_clicked con la URL final). |
Reglas de la url: solo https://, con un nombre de dominio (no una
IP), sin usuario ni contraseña y sin acortadores (bit.ly, tinyurl.com, t.co…). Puede llevar
una variable {{1}} en la ruta o la query
(nunca en el dominio), p. ej.
https://tiendaexpress.example.com/pedidos/{{1}}. Su valor
se envía en cada mensaje en button_variables, por índice del botón:
{"to": "+573005550123", "sender_id": "tiendaexpress", "template": "pedido_enviado",
"variables": ["Valentina", "TE-48213"], "button_variables": {"0": "TE-48213"}}
El valor es texto (máx. 200 caracteres) y xYago lo codifica para la URL. Falta el valor de
un botón con variable, sobra uno o no es válido → 422 invalid_button_variables.
Plantilla de ejemplo (GET /templates/{id}):
{
"id": "d2f4a6c8-0e1b-4d3f-9a5c-7e9b1d3f5a70",
"sender_id": "tiendaexpress",
"name": "pedido_enviado",
"category": "utility",
"default_language": "es",
"languages": {
"es": "Hola {{1}}, tu pedido {{2}} va en camino.",
"en": "Hi {{1}}, your order {{2}} is on its way."
},
"variables_count": 2,
"buttons": [
{
"type": "url",
"texts": {
"es": "Seguir pedido",
"en": "Track order"
},
"payload": null,
"url": "https://tiendaexpress.example.com/pedidos/{{1}}"
},
{
"type": "quick_reply",
"texts": {
"es": "No lo pedí",
"en": "Not my order"
},
"payload": "order_not_mine",
"url": null
}
],
"version": 1,
"status": "approved",
"review_flags": [],
"rejection_reason": null,
"created_at": "2026-11-03T10:00:00Z",
"updated_at": "2026-11-03T16:45:00Z"
}
Revisión de xYago
- Creas la plantilla (
draft) y la envías a revisión (pending_review). En sandbox ya puedes probar una endraft. - Al enviarla se calculan avisos automáticos (
review_flags) que ve quien la revisa y tú en el panel. No bloquean el envío a revisión, pero conviene corregirlos:
| Aviso | Significado |
|---|---|
marketing_words | Palabras de publicidad (descuento, oferta, gratis, cupón…). Las plantillas son solo para avisos de servicio. |
url_shortener | Menciona un acortador de enlaces. |
too_many_variables | Más de 10 variables. |
variables_adjacent | Dos variables seguidas sin texto entre ellas. |
starts_or_ends_with_variable | El texto empieza o termina con una variable. |
only_variables | El texto es solo variables. |
xYago la aprueba (approved) o la rechaza con un motivo
(rejected; corrígela en el panel y vuelve a revisión). Cada paso queda en su
historial en el panel y emite template.status_changed.
Versiones
Una plantilla aprobada no cambia. Si la editas se crea la versión
siguiente (mismo name, version + 1) en revisión, y la
aprobada se sigue usando en tus envíos hasta que xYago apruebe la nueva; entonces la anterior
pasa a superseded. No hace falta cambiar nada en tu integración: envías por
name.
Si xYago pausa una plantilla (p. ej. por quejas de los usuarios), los
envíos con ella responden 409 template_paused hasta que la reactive.
Estados
| Estado | Significado |
|---|---|
draft | Borrador (solo sandbox). |
pending_review | En revisión por xYago. |
approved | Se puede usar en live y sandbox. |
rejected | Rechazada (ver rejection_reason). |
paused | Pausada por xYago: 409 template_paused. |
superseded | Sustituida por una versión más nueva aprobada. |
Webhook de clics
Suscríbete a message.button_clicked. Solo se avisa del primer
clic de cada botón de cada mensaje. data lleva el mensaje (como en
message.*, con tu reference), el botón y la hora del clic. El
text del botón es el del idioma en que se envió el mensaje
(button.language, igual que message.language):
{
"created_at": "2026-03-02T15:00:04Z",
"data": {
"message": {
"id": "7a1c9e35-2b4d-4f6a-8c0e-1d3f5a7b9c21",
"status": "read",
"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": "2026-11-04T15:02:10Z",
"read_at": "2026-11-04T15:07:42Z",
"expires_at": null
},
"button": {
"index": 1,
"id": "b1",
"type": "quick_reply",
"text": "No lo pedí",
"language": "es",
"payload": "order_not_mine",
"url": null
},
"clicked_at": "2026-11-04T15:08:03Z"
},
"environment": "live",
"id": "evt_4f9c2b7a1d3e4c5b8a6f0e1d2c3b4a59",
"type": "message.button_clicked"
}