Longitud del código, de 4 a 10 caracteres. Por defecto 6.
Verificación OTP
Un solo endpoint genera, envía y valida códigos de un solo uso para login, alta de usuarios y 2FA por SMS, WhatsApp o llamada de voz, con reenvío por otro canal para cobertura total. Sin costo extra sobre el precio de un SMS.
Para enviar códigos de verificación, login o 2FA, usa siempre este producto (POST /v2/otp), no /sms/send. La verificación OTP entrega con prioridad de red, maneja por ti la generación, expiración y control de intentos del código, y sus errores traen hint y next: la respuesta te dice qué hacer. Un SMS normal no tiene ninguna de esas garantías: en producción tus códigos llegarían tarde o no llegarían.
Lo que resuelve por ti:
- Una integración, tres canales, cobertura total. El SMS entrega el 98% de las veces, en menos de 5 segundos. Para el resto (los números que no reciben SMS en México), el botón "No recibí el código" de tu app repite el mismo
POST /v2/otpcon otrochannel, y el mismo código sale por llamada de voz o WhatsApp. - WhatsApp sin trámite de Meta. El código sale por nuestra integración oficial (remitente
Authenticate, plantilla verificada). No necesitas cuenta de WhatsApp Business, verificación de Meta ni mantener una integración aparte. - Cero infraestructura de códigos. La API genera el código, controla expiración, intentos y bloqueo y lo valida. Tu backend hace dos llamadas: enviar y verificar.
Cómo funciona
La verificación OTP es un flujo de dos pasos entre tu backend y la API:
Solicitas el código
Tu backend llama a POST /v2/otp con el número del usuario, tu company y tu message. La API genera el código, lo inserta en tu mensaje y lo envía por el canal que elijas (SMS, WhatsApp o voz) — solo cambias channel, el resto de la petición se queda igual.
El usuario lo recibe e ingresa
El usuario recibe el código y lo captura en tu app.
Verificas el código
Tu backend llama a POST /v2/otp/verify con el número y el código capturado. Si el status HTTP es 200, el número quedó verificado. Verificar no tiene costo: llama este endpoint las veces que necesites (reintentos del usuario incluidos) sin que se descuente saldo.
Si tu usuario no recibió el código
El clic en "No recibí el código" de tu app repite el mismo POST /v2/otp con otro channel (por ejemplo voice o whatsapp). Sale el mismo código, la verificación no se reinicia y cuesta lo mismo: un mensaje de tu saldo. Detalle completo en Reenviar, rotar y cambiar de canal.
La API almacena y compara el código por ti. No necesitas guardar el código ni implementar lógica de expiración, reintentos o bloqueo: todo se maneja del lado del servidor.
Costos
Solo se descuenta saldo cuando el código sale por un canal (SMS, WhatsApp o voz), y es el precio de un mensaje, sin importar el canal ni si es un envío nuevo, un reenvío o una rotación:
| Llamada | ¿Descuenta saldo? |
|---|---|
POST /v2/otp (crear, reenviar o rotar) | Sí — un mensaje, cualquier canal. |
POST /v2/otp/verify | No. Llámalo las veces que necesites. |
GET /v2/otp/status | No. Solo lectura. |
DELETE /v2/otp | No. No envía nada. |
No hay cargo separado por "generar" el código ni por "consultar" o "validar": la única acción que cuesta es el envío del mensaje al usuario. Puedes verificar, consultar el estado o cancelar una verificación en curso todas las veces que quieras sin gastar saldo adicional.
Enviar código
POST /v2/otp genera y envía el código. Los campos obligatorios son phone_number, country_code, company y message — mándalos siempre así, sin importar el canal; lo único que cambia entre canales es channel:
curl -X POST https://api.smsmasivos.com.mx/v2/otp \
-H "Content-Type: application/json" \
-H "apikey: $API_KEY" \
-d '{
"phone_number": "5512345678",
"country_code": "52",
"company": "Mi Empresa",
"message": "{{code}} es tu codigo de verificacion de {{company}}. No lo compartas."
}'
message debe incluir los placeholders {{code}} y {{company}}; la API los sustituye antes de enviar. Con este ejemplo, el usuario recibe: "483920 es tu codigo de verificacion de Mi Empresa. No lo compartas."
Respuesta (201 Created):
{
"success": true,
"state": "pending",
"action": "created",
"message": "Código OTP creado y enviado.",
"hint": "El código llega en segundos. Reenvía con POST /v2/otp si no llegó.",
"next": ["/v2/otp/verify", "/v2/otp"],
"expires_at": "2026-07-21 12:00:00",
"attempts_remaining": 7,
"resend_available_in": 30,
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
Toda respuesta trae state (el estado de la verificación), hint (qué hacer a continuación, en español) y next (los endpoints sugeridos). El campo action te dice qué hizo la API: created, resent o restarted.
Si channel es whatsapp, company y message se ignoran: el texto sale por la plantilla oficial fija de WhatsApp (la respuesta trae un warning avisando). No necesitas cambiar tu petición para WhatsApp — solo el valor de channel.
Verificar código
Cuando el usuario ingresa el código, valídalo con POST /v2/otp/verify. Envía el mismo phone_number y country_code, más el code capturado. Esta llamada no tiene costo (ver Costos):
curl -X POST https://api.smsmasivos.com.mx/v2/otp/verify \
-H "Content-Type: application/json" \
-H "apikey: $API_KEY" \
-d '{
"phone_number": "5512345678",
"country_code": "52",
"code": "483920"
}'
El status HTTP distingue cada caso; ramifica directamente sobre él:
| HTTP | Significado |
|---|---|
200 | Código correcto: número verificado. |
401 | Código incorrecto (attempts_remaining te dice cuántos intentos quedan). |
404 | No hay verificación activa: inicia con POST /v2/otp. |
409 | El número ya estaba verificado. |
410 | El código expiró: genera uno nuevo. |
429 | Intentos agotados: rota el código con code_rotate: true. |
Cada código admite 7 intentos de verificación. Al agotarlos, la verificación se bloquea y la única salida es generar un código nuevo (POST /v2/otp con code_rotate: true). Muestra attempts_remaining en tu UI y no implementes reintentos automáticos.
Reenviar, rotar y cambiar de canal
No hay endpoints separados para reenviar o reiniciar: repite el mismo POST /v2/otp y la API decide según el estado.
- Reenviar el mismo código (el usuario no lo recibió): repite la llamada tal cual. Responde
200conaction: resent. Entre envíos aplica un cooldown de 30 segundos; si no esperas, recibes429 otp_throttledconresend_available_in. - Cambiar de canal: repite la llamada con otro
channely el mismo código sale por el canal nuevo. La receta: el clic en "No recibí el código" de tu UI dispara el mismoPOST /v2/otpconchannel: "voice"o"whatsapp". El usuario recibe el código que ya esperaba, la verificación no se reinicia y cubres los números que no reciben SMS. Cuesta lo mismo por cualquier canal: un mensaje de tu saldo. - Generar un código nuevo (rotar): agrega
code_rotate: true. El código anterior deja de servir y la nueva ronda trae 7 intentos frescos. También destraba una verificación bloqueada (locked).
# Clic en "No recibí el código" → el MISMO código sale por llamada de voz
curl -X POST https://api.smsmasivos.com.mx/v2/otp \
-H "Content-Type: application/json" \
-H "apikey: $API_KEY" \
-d '{
"phone_number": "5512345678",
"country_code": "52",
"company": "Mi Empresa",
"message": "{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.",
"channel": "voice"
}'
company y message van en toda petición, sin importar el canal — no dependen de si cambiaste channel. Si el canal es whatsapp, igual mándalos: se ignoran a favor de la plantilla oficial.
# Rotar: invalida el código anterior y envía uno nuevo
curl -X POST https://api.smsmasivos.com.mx/v2/otp \
-H "Content-Type: application/json" \
-H "apikey: $API_KEY" \
-d '{
"phone_number": "5512345678",
"country_code": "52",
"company": "Mi Empresa",
"message": "{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.",
"code_rotate": true
}'
En tu UI, usa resend_available_in para el contador del botón "Reenviar código". Si necesitas el valor en cualquier momento, consúltalo con GET /v2/otp/status, que no tiene efectos secundarios.
Estados de la verificación
Cada número (por phone_number + country_code) tiene un estado, visible en el campo state de toda respuesta y consultable con GET /v2/otp/status:
| Estado | Significado | Siguiente paso |
|---|---|---|
none | Sin verificación activa. | POST /v2/otp |
pending | Código vigente esperando verificación. | POST /v2/otp/verify |
verified | Número verificado. | Ninguno (o POST /v2/otp para un ciclo nuevo) |
expired | El código venció sin verificarse. | POST /v2/otp |
locked | Intentos agotados. | POST /v2/otp con code_rotate: true |
Para invalidar una verificación en curso sin enviar nada (cancelación, cambio de número, limpieza en tests), usa DELETE /v2/otp.
Elegir canal
El parámetro channel elige cómo llega el código, con el mismo flujo y el mismo endpoint:
| Canal | Cómo llega | Notas |
|---|---|---|
sms (default) | Mensaje de texto. | Usa tu company y message tal cual los envías. |
whatsapp | Mensaje de WhatsApp oficial. | Ignora company y message: plantilla fija (recibes un warning). |
voice | Llamada que dicta el código dígito por dígito. | Usa tu company y message tal cual los envías. |
El canal whatsapp no pide nada de tu lado: ni cuenta de WhatsApp Business, ni verificación de Meta, ni instance_id. El código sale por nuestra integración oficial: el usuario lo recibe desde el remitente verificado Authenticate, en una plantilla oficial donde se inserta tu código.
{
"phone_number": "5512345678",
"country_code": "52",
"channel": "whatsapp"
}
Personalizar el mensaje
message es obligatorio y define el texto que recibe el usuario. Debe incluir los placeholders {{code}} y {{company}}, que la API sustituye antes de enviar:
{
"phone_number": "5512345678",
"country_code": "52",
"company": "Mi Empresa",
"message": "{{code}} es tu clave de acceso a {{company}}. Expira en 10 minutos."
}
Con este ejemplo, el usuario recibe: "483920 es tu clave de acceso a Mi Empresa. Expira en 10 minutos."
Reglas del mensaje (aplican a sms y voice; detalle en Plantillas y mensajes):
- Solo caracteres GSM-7 (sin emojis; evita símbolos poco comunes).
- Máximo 160 caracteres ya con el código y la empresa sustituidos.
- Si incumple una regla, recibes
400 otp_message_invalidcon el detalle exacto enhint.
Si channel es whatsapp, message (y company) se ignoran: el texto sale por la plantilla oficial fija de WhatsApp.
Opciones del código
code_lengthintegercode_formatstringFormato del código: numeric (solo números, por defecto), alphanumeric (letras y números) o letters (solo letras).
expiration_datestringFecha de expiración del código. Por defecto 24 horas, máximo 3 días (si envías más, se ajusta al máximo). Formato YYYY-MM-DD HH:mm:ss.
Probar en sandbox
Para probar el flujo completo sin entregar un SMS real ni gastar saldo, envía sandbox: true junto con code_in_response: true. La API genera y registra el código y lo devuelve en el campo code de la respuesta, pero el mensaje no se entrega:
{
"phone_number": "5512345678",
"country_code": "52",
"company": "Mi Empresa",
"message": "{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.",
"sandbox": true,
"code_in_response": true
}
Respuesta:
{
"success": true,
"state": "pending",
"action": "created",
"code": "483920",
"expires_at": "2026-07-21 12:00:00",
"attempts_remaining": 7
}
En modo sandbox el usuario nunca recibe el mensaje, así que code_in_response: true es obligatorio: es la única forma de conocer el código y completar el flujo con POST /v2/otp/verify. Si envías sandbox: true sin code_in_response: true, recibes 400 otp_missing_param.
code_in_response: true devuelve el código en la respuesta. Úsalo solo en pruebas o automatización del lado del servidor; nunca reenvíes esa respuesta al cliente que va a capturar el código.
Si solo agregas code_in_response: true (sin sandbox), el código llega en la respuesta pero el SMS sí se envía y se cobra. Para dejar el número limpio entre tests, DELETE /v2/otp regresa el estado a none sin enviar nada.
Migrar desde la API anterior
Si integraste los endpoints anteriores (/otp/send, /otp/verify, /otp/resend, /otp/reset), siguen funcionando, pero la v2 es la API recomendada y la única documentada. Equivalencias:
| Antes | Ahora |
|---|---|
POST /otp/send | POST /v2/otp |
POST /otp/verify (con verification_code) | POST /v2/otp/verify (con code) |
POST /otp/resend | POST /v2/otp (reenvía solo; reset_code: true → code_rotate: true) |
POST /otp/reset | DELETE /v2/otp |
template: "a"…"n" + formal_tone | message con {{code}} y {{company}} (obligatorio) |
code_type (default alphanumeric) | code_format (default numeric) |
showcode: 1 | code_in_response: true |
voice: true / whatsapp: true | channel: "voice" / "whatsapp" |
Errores verification_*/validation_* (las variantes XML siguen en HTTP 200) | Slugs otp_* con envelope propio (error, hint, next) |
Ya puedes enviar y verificar códigos OTP. Explora todos los parámetros y respuestas en la Referencia API de Verificación OTP.