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/otp con otro channel, 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:

1

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.

2

El usuario lo recibe e ingresa

El usuario recibe el código y lo captura en tu app.

3

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.

4

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/verifyNo. Llámalo las veces que necesites.
GET /v2/otp/statusNo. Solo lectura.
DELETE /v2/otpNo. 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:

HTTPSignificado
200Código correcto: número verificado.
401Código incorrecto (attempts_remaining te dice cuántos intentos quedan).
404No hay verificación activa: inicia con POST /v2/otp.
409El número ya estaba verificado.
410El código expiró: genera uno nuevo.
429Intentos 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 200 con action: resent. Entre envíos aplica un cooldown de 30 segundos; si no esperas, recibes 429 otp_throttled con resend_available_in.
  • Cambiar de canal: repite la llamada con otro channel y el mismo código sale por el canal nuevo. La receta: el clic en "No recibí el código" de tu UI dispara el mismo POST /v2/otp con channel: "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:

EstadoSignificadoSiguiente paso
noneSin verificación activa.POST /v2/otp
pendingCódigo vigente esperando verificación.POST /v2/otp/verify
verifiedNúmero verificado.Ninguno (o POST /v2/otp para un ciclo nuevo)
expiredEl código venció sin verificarse.POST /v2/otp
lockedIntentos 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:

CanalCómo llegaNotas
sms (default)Mensaje de texto.Usa tu company y message tal cual los envías.
whatsappMensaje de WhatsApp oficial.Ignora company y message: plantilla fija (recibes un warning).
voiceLlamada 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_invalid con el detalle exacto en hint.

Si channel es whatsapp, message (y company) se ignoran: el texto sale por la plantilla oficial fija de WhatsApp.

Opciones del código

code_lengthinteger

Longitud del código, de 4 a 10 caracteres. Por defecto 6.

code_formatstring

Formato del código: numeric (solo números, por defecto), alphanumeric (letras y números) o letters (solo letras).

expiration_datestring

Fecha 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:

AntesAhora
POST /otp/sendPOST /v2/otp
POST /otp/verify (con verification_code)POST /v2/otp/verify (con code)
POST /otp/resendPOST /v2/otp (reenvía solo; reset_code: truecode_rotate: true)
POST /otp/resetDELETE /v2/otp
template: "a"…"n" + formal_tonemessage con {{code}} y {{company}} (obligatorio)
code_type (default alphanumeric)code_format (default numeric)
showcode: 1code_in_response: true
voice: true / whatsapp: truechannel: "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.