Verify (códigos)
Envía códigos de verificación de un solo uso (inicio de sesión, registro, confirmación de una operación) por el chat de la app de xYago, desde tu remitente verificado. Si el teléfono no usa xYago lo sabes al momento y envías el código por tu otro canal (p. ej. SMS).
Antes de empezar
- Necesitas un remitente aprobado (panel → API Developer → Remitentes; ver Mensajes). Verify no usa plantillas: el texto lo pone xYago.
- Activa el producto Verify y crea una API key con el scope
verify:write(yverify:readpara consultar).
Flujo
- Tu backend llama a
POST /verify/startcon el teléfono. xYago genera un código de 6 dígitos, válido 10 minutos y con 5 intentos, y lo envía al chat del usuario (con botón «Copiar código»). La respuesta nunca contiene el código. - El usuario escribe el código en tu app.
- Tu backend llama a
POST /verify/checkcon elidde la verificación y el código.
curl -X POST 'https://api.xyago.com/v1/verify/start' \
-H "Authorization: Bearer $XYAGO_API_KEY" \
-H "Idempotency-Key: login-7f3a9c2e-0001" \
-H "Content-Type: application/json" \
-d '{"to": "+573005550123", "sender_id": "tiendaexpress", "language": "es",
"reference": "login-CLI-000123"}'
Respuesta 201:
{
"id": "3e8b1f6a-9c2d-4a7e-b5f0-6d1c2a3b4e59",
"object": "verification",
"sender_id": "tiendaexpress",
"to_masked": "+57 ••• ••• 0123",
"channel": "xyago_chat",
"language": "es",
"reference": "login-CLI-000123",
"status": "pending",
"delivery_status": "sent",
"failure_code": null,
"attempts": 0,
"max_attempts": 5,
"expires_at": "2026-11-04T15:10:00Z",
"verified_at": null,
"created_at": "2026-11-04T15:00:00Z"
}
curl -X POST 'https://api.xyago.com/v1/verify/check' \
-H "Authorization: Bearer $XYAGO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"verification_id": "3e8b1f6a-9c2d-4a7e-b5f0-6d1c2a3b4e59", "code": "482913"}'
{
"id": "3e8b1f6a-9c2d-4a7e-b5f0-6d1c2a3b4e59",
"valid": true,
"status": "verified",
"attempts_remaining": 0
}
Un código erróneo responde 200 con valid: false y los
intentos que quedan:
{
"id": "3e8b1f6a-9c2d-4a7e-b5f0-6d1c2a3b4e59",
"valid": false,
"status": "pending",
"attempts_remaining": 4
}
- El código es de un solo uso: tras
valid: trueotra comprobación responde409 verification_not_pending. - Al quinto código erróneo la verificación pasa a
too_many_attempts(después,409 max_attempts_reached). Caducada:409 verification_expired. En ambos casos inicia una nueva. - Pedir un código nuevo al mismo teléfono sustituye al anterior
(pasa a
canceledy el chat oculta su código). language(es,en,de) es opcional: por defecto, el del país de tu cuenta (español en países hispanohablantes, alemán en DE/AT/CH/LI, inglés en el resto). El texto es fijo, p. ej. «482913 es tu código de verificación de Tienda Express. Caduca en 10 minutos. No lo compartas con nadie; Tienda Express nunca te lo pedirá.»
Teléfonos sin xYago: tu SMS de respaldo
Si el teléfono no usa xYago, POST /verify/start responde al momento
404 recipient_not_on_xyago: no se envía nada, no queda en espera y no se
reintenta. Envía entonces el código por tu canal habitual (SMS, correo...). Si el usuario
no puede recibir mensajes de tu remitente la respuesta es 409 undeliverable
(trátalo igual: usa tu otro canal).
Estados
| Estado | Significado |
|---|---|
pending | Código enviado, esperando la comprobación. delivery_status: sent → delivered → read. |
verified | Código correcto (ya no sirve). |
expired | Pasaron los 10 minutos. |
too_many_attempts | 5 códigos erróneos. |
canceled | Sustituida por una verificación más reciente al mismo teléfono. |
failed | No se pudo enviar (failure_code: recipient_not_on_xyago, undeliverable...). |
Consulta una verificación con GET /verify/{id}.
Idempotency-Key (obligatoria)
POST /verify/start lleva la cabecera Idempotency-Key, como
POST /messages. Si la respuesta no llega (timeout, 502,
503), repite con la misma clave y el mismo cuerpo: nunca se envían dos
códigos a la vez. Si el primero ya llegó al chat recibes la misma verificación; si no
llegó se envía uno nuevo (el anterior deja de valer), pero solo pasada la espera entre
códigos desde el intento anterior, dentro de los límites y sin pausa de Verify (si no:
429 rate_limited con Retry-After, o 403 verify_paused;
repite después con la misma clave). Misma clave con otro cuerpo:
409 idempotency_key_reused.
Límites
- Entre dos códigos al mismo teléfono (mismo remitente): 30 segundos.
- Por remitente y teléfono: 5 códigos cada 15 minutos y 10 al día; por teléfono, sumando todos los remitentes: 20 al día; y un tope diario por remitente.
- Códigos erróneos por remitente y teléfono: 10 por hora (además de los 5 intentos de cada verificación).
Al superar un límite: 429 rate_limited con Retry-After. Si los
límites no se pueden comprobar, POST /verify/start responde
503 limits_unavailable (reintenta con la misma Idempotency-Key);
POST /verify/check sigue funcionando (el tope horario de códigos erróneos no se
aplica en ese momento, pero sí los 5 intentos, el un solo uso y la caducidad de cada
verificación), así que un usuario con su código nunca se queda sin poder entrar. Si muchos de tus códigos van a teléfonos sin xYago,
xYago pausa Verify para tu remitente (403 verify_paused; contacta con
xYago).
Sandbox
Con una clave xy_test_ solo se envían códigos (reales) a tus
números de prueba confirmados
(422 sandbox_recipient_not_allowed si no). Mientras tu remitente no está
aprobado, el código llega desde xYago Sandbox con el prefijo
[Sandbox · Tu remitente].
Webhooks
Suscríbete a verification.delivered, verification.read,
verification.verified, verification.expired y
verification.failed (también al agotar los intentos). data es la
verificación (sin el código):
{
"created_at": "2026-03-02T15:00:04Z",
"data": {
"id": "3e8b1f6a-9c2d-4a7e-b5f0-6d1c2a3b4e59",
"object": "verification",
"sender_id": "tiendaexpress",
"to_masked": "+57 ••• ••• 0123",
"channel": "xyago_chat",
"language": "es",
"reference": "login-CLI-000123",
"status": "verified",
"delivery_status": "read",
"failure_code": null,
"attempts": 1,
"max_attempts": 5,
"expires_at": "2026-11-04T15:10:00Z",
"verified_at": "2026-11-04T15:01:12Z",
"created_at": "2026-11-04T15:00:00Z"
},
"environment": "live",
"id": "evt_4f9c2b7a1d3e4c5b8a6f0e1d2c3b4a59",
"type": "verification.verified"
}
Buenas prácticas
- Nunca registres el código que escribe el usuario (ni en logs ni en
tu base de datos); envíalo solo a
POST /verify/check. - Comprueba siempre en tu backend, nunca desde la app del usuario con tu API key.
- No reveles al usuario si su teléfono usa xYago: muestra el mismo mensaje tanto si el código llega por xYago como por SMS.