Verificación OTP
Verificación en dos pasos (OTP): códigos de un solo uso por SMS, WhatsApp o llamada de voz — una sola integración para los tres canales. El parámetro channel elige el transporte en cada llamada, y el reenvío acepta otro canal con el mismo código: el clic en "No recibí el código" de tu app se resuelve repitiendo la misma petición, con cobertura total de números. WhatsApp sale por la integración oficial de SMS Masivos: sin cuenta de WhatsApp Business ni verificación de Meta. La API genera, guarda, expira y valida los códigos por ti.
El flujo completo usa un solo recurso, /v2/otp, con verbos explícitos:
POST /v2/otpgenera y envía el código — y también reenvía (mismo código) o rota (code_rotate: true) según el estado.POST /v2/otp/verifycomprueba el código que el usuario capturó.GET /v2/otp/statusconsulta el estado sin efectos secundarios.DELETE /v2/otpinvalida la verificación activa (no envía nada).
Usa estos endpoints para códigos de login, verificación de cuenta, 2FA y recuperación de contraseña. No construyas un sistema OTP manual sobre /sms/send: perderías la generación, expiración, validación y control de intentos que este flujo ya maneja. Guía completa en Verificación OTP.
Reglas del flujo: cada código admite 7 intentos de verificación; al agotarlos el estado pasa a locked y se destraba rotando el código (code_rotate: true). Entre envíos aplica un cooldown de 30 segundos (otp_throttled con Retry-After si no esperas). El código expira a las 24 horas por defecto, con un máximo de 3 días (expiration_date).
A diferencia del resto de la API, estos endpoints responden con status HTTP semántico: 201/200 en éxito y 4xx/5xx reales en error, siempre con un slug estable en error, una sugerencia accionable en hint y los endpoints sugeridos en next. La respuesta te dice qué hacer a continuación.
Enviar Código de Verificación (OTP)
channel: el mismo código sale por el canal nuevo y cubres los números que no reciben SMS. La respuesta siempre te dice qué hizo (action) y qué sigue (next).52.Canal de entrega del código:
sms(default): mensaje de texto.whatsapp: mensaje de WhatsApp oficial con plantilla fija (ignoramessageycompany). No requiere cuenta de WhatsApp Business ni verificación de Meta: sale por la integración oficial de SMS Masivos (remitenteAuthenticate).voice: llamada que dicta el código dígito por dígito.
En un reenvío puedes cambiar de canal: el mismo código pendiente sale por el canal nuevo (fallback).
smswhatsappvoicechannel: sms y voice (requisito de operadores); se ignora con whatsapp.Texto propio del mensaje (opcional, solo sms y voice). Debe incluir los placeholders {{code}} y {{company}}, usar solo caracteres GSM-7 (sin emojis ni acentos raros) y no exceder 160 caracteres ya con el código y la empresa sustituidos.
Si no lo envías se usa el texto default: {{code}} es tu codigo de verificacion de {{company}}. No lo compartas.
Formato del código generado:
numeric(default): solo números.alphanumeric: letras y números.letters: solo letras.
numericalphanumericletterstrue descarta el código vigente y genera uno nuevo (nueva ronda con 7 intentos). Sin él, un código vigente se reenvía tal cual. El cooldown de 30s aplica igual.Devuelve el código generado en el campo code de la respuesta:
0(default): no devolver.1: devolver (pruebas/automatización del lado del servidor).
01YYYY-MM-DD HH:mm:ss (futura). Por defecto 24 horas; máximo 3 días (si envías más, se ajusta al máximo).curl -X POST https://api.smsmasivos.com.mx/v2/otp \
-H "Content-Type: application/json" \
-H "apikey: TU_API_KEY" \
-d '{
"phone_number": "5512345678",
"country_code": "52",
"company": "Mi Empresa"
}'
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.smsmasivos.com.mx/v2/otp');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'apikey: ' . getenv('SMSMASIVOS_API_KEY')
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'phone_number' => '5512345678',
'country_code' => '52',
'company' => 'Mi Empresa'
]));
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $status . ' ' . $response;
import requests
response = requests.post(
'https://api.smsmasivos.com.mx/v2/otp',
headers={
'Content-Type': 'application/json',
'apikey': 'TU_API_KEY'
},
json={
'phone_number': '5512345678',
'country_code': '52',
'company': 'Mi Empresa'
}
)
print(response.status_code, response.json())
const response = await fetch('https://api.smsmasivos.com.mx/v2/otp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': process.env.SMSMASIVOS_API_KEY
},
body: JSON.stringify({
phone_number: '5512345678',
country_code: '52',
company: 'Mi Empresa'
})
});
const data = await response.json();
console.log(response.status, data);
const axios = require('axios');
const { status, data } = await axios.post(
'https://api.smsmasivos.com.mx/v2/otp',
{
phone_number: '5512345678',
country_code: '52',
company: 'Mi Empresa'
},
{
headers: {
'Content-Type': 'application/json',
'apikey': process.env.SMSMASIVOS_API_KEY
},
validateStatus: () => true
}
);
console.log(status, data);
require 'net/http'
require 'json'
uri = URI('https://api.smsmasivos.com.mx/v2/otp')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path, {
'Content-Type' => 'application/json',
'apikey' => ENV['SMSMASIVOS_API_KEY']
})
request.body = {
phone_number: '5512345678',
country_code: '52',
company: 'Mi Empresa'
}.to_json
response = http.request(request)
puts "#{response.code} #{JSON.parse(response.body)}"
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("apikey", "TU_API_KEY");
var payload = new
{
phone_number = "5512345678",
country_code = "52",
company = "Mi Empresa"
};
var response = await client.PostAsJsonAsync(
"https://api.smsmasivos.com.mx/v2/otp",
payload
);
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"{(int)response.StatusCode} {result}");
OkHttpClient client = new OkHttpClient();
String json = "{"
+ "\"phone_number\":\"5512345678\","
+ "\"country_code\":\"52\","
+ "\"company\":\"Mi Empresa\""
+ "}";
Request request = new Request.Builder()
.url("https://api.smsmasivos.com.mx/v2/otp")
.post(RequestBody.create(json, MediaType.parse("application/json")))
.addHeader("apikey", "TU_API_KEY")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.code() + " " + response.body().string());
{ "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, "warning": "channel=whatsapp usa una plantilla oficial fija; se ignoraron message/company.", "code": "483920", "request_id": "550e8400-e29b-41d4-a716-446655440000" }
{ "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" }
{ "success": false, "state": "pending", "error": "otp_incorrect_code", "message": "El código es incorrecto.", "hint": "Código incorrecto. Te quedan 3 intentos.", "next": [ "/v2/otp/verify", "/v2/otp" ], "expires_at": "2026-07-21 12:00:00", "attempts_remaining": 3, "resend_available_in": 18, "code": "sms_07", "request_id": "550e8400-e29b-41d4-a716-446655440000" }
{ "success": false, "state": "none", "error": "otp_insufficient_credits", "message": "Saldo insuficiente para enviar el código.", "hint": "Recarga saldo para enviar el código.", "next": [], "code": "sms_07" }
{ "success": false, "state": "pending", "error": "otp_incorrect_code", "message": "El código es incorrecto.", "hint": "Código incorrecto. Te quedan 3 intentos.", "next": [ "/v2/otp/verify", "/v2/otp" ], "expires_at": "2026-07-21 12:00:00", "attempts_remaining": 3, "resend_available_in": 18, "code": "sms_07", "request_id": "550e8400-e29b-41d4-a716-446655440000" }
{ "success": false, "error": "otp_internal", "message": "Ocurrió un error inesperado.", "hint": "Intenta de nuevo en unos momentos.", "next": [] }
{ "success": false, "state": "none", "error": "otp_send_failed", "message": "No se pudo enviar el código. Intenta de nuevo.", "hint": "No se pudo enviar el código. Intenta de nuevo.", "next": [] }
Invalidar Verificación (OTP)
52.curl -X DELETE https://api.smsmasivos.com.mx/v2/otp \
-H "Content-Type: application/json" \
-H "apikey: TU_API_KEY" \
-d '{
"phone_number": "5512345678",
"country_code": "52"
}'
import requests
response = requests.delete(
'https://api.smsmasivos.com.mx/v2/otp',
headers={
'Content-Type': 'application/json',
'apikey': 'TU_API_KEY'
},
json={
'phone_number': '5512345678',
'country_code': '52'
}
)
print(response.status_code, response.json())
const response = await fetch('https://api.smsmasivos.com.mx/v2/otp', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'apikey': process.env.SMSMASIVOS_API_KEY
},
body: JSON.stringify({
phone_number: '5512345678',
country_code: '52'
})
});
const data = await response.json();
console.log(response.status, data);
{ "success": true, "state": "none", "action": "deleted", "message": "El OTP pendiente fue invalidado.", "hint": "No se envió ningún mensaje.", "next": [ "/v2/otp" ], "request_id": "550e8400-e29b-41d4-a716-446655440000" }
{ "success": false, "error": "otp_missing_param", "message": "Falta un parámetro obligatorio.", "hint": "Envía 'phone_number' (hasta 10 dígitos).", "next": [] }
{ "success": false, "error": "otp_internal", "message": "Ocurrió un error inesperado.", "hint": "No se pudo invalidar el OTP. Intenta de nuevo.", "next": [] }
Verificar Código (OTP)
state: verified). Cada código admite 7 intentos; al agotarlos la verificación se bloquea (locked) hasta rotar el código con POST /v2/otp y code_rotate: true.52.curl -X POST https://api.smsmasivos.com.mx/v2/otp/verify \
-H "Content-Type: application/json" \
-H "apikey: TU_API_KEY" \
-d '{
"phone_number": "5512345678",
"country_code": "52",
"code": "483920"
}'
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.smsmasivos.com.mx/v2/otp/verify');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'apikey: ' . getenv('SMSMASIVOS_API_KEY')
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'phone_number' => '5512345678',
'country_code' => '52',
'code' => '483920'
]));
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $status . ' ' . $response;
import requests
response = requests.post(
'https://api.smsmasivos.com.mx/v2/otp/verify',
headers={
'Content-Type': 'application/json',
'apikey': 'TU_API_KEY'
},
json={
'phone_number': '5512345678',
'country_code': '52',
'code': '483920'
}
)
if response.status_code == 200:
print('Número verificado')
else:
print(response.status_code, response.json())
const response = await fetch('https://api.smsmasivos.com.mx/v2/otp/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': process.env.SMSMASIVOS_API_KEY
},
body: JSON.stringify({
phone_number: '5512345678',
country_code: '52',
code: '483920'
})
});
const data = await response.json();
console.log(response.status, data);
const axios = require('axios');
const { status, data } = await axios.post(
'https://api.smsmasivos.com.mx/v2/otp/verify',
{
phone_number: '5512345678',
country_code: '52',
code: '483920'
},
{
headers: {
'Content-Type': 'application/json',
'apikey': process.env.SMSMASIVOS_API_KEY
},
validateStatus: () => true
}
);
console.log(status, data);
require 'net/http'
require 'json'
uri = URI('https://api.smsmasivos.com.mx/v2/otp/verify')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path, {
'Content-Type' => 'application/json',
'apikey' => ENV['SMSMASIVOS_API_KEY']
})
request.body = {
phone_number: '5512345678',
country_code: '52',
code: '483920'
}.to_json
response = http.request(request)
puts "#{response.code} #{JSON.parse(response.body)}"
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("apikey", "TU_API_KEY");
var payload = new
{
phone_number = "5512345678",
country_code = "52",
code = "483920"
};
var response = await client.PostAsJsonAsync(
"https://api.smsmasivos.com.mx/v2/otp/verify",
payload
);
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"{(int)response.StatusCode} {result}");
OkHttpClient client = new OkHttpClient();
String json = "{"
+ "\"phone_number\":\"5512345678\","
+ "\"country_code\":\"52\","
+ "\"code\":\"483920\""
+ "}";
Request request = new Request.Builder()
.url("https://api.smsmasivos.com.mx/v2/otp/verify")
.post(RequestBody.create(json, MediaType.parse("application/json")))
.addHeader("apikey", "TU_API_KEY")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.code() + " " + response.body().string());
{ "success": true, "state": "verified", "action": "verified", "message": "Código verificado correctamente.", "hint": "", "next": [], "request_id": "550e8400-e29b-41d4-a716-446655440000" }
{ "success": false, "error": "otp_missing_param", "message": "Falta un parámetro obligatorio.", "hint": "Envía 'code' (el código que ingresó el usuario).", "next": [] }
{ "success": false, "state": "pending", "error": "otp_incorrect_code", "message": "El código es incorrecto.", "hint": "Código incorrecto. Te quedan 3 intentos.", "next": [ "/v2/otp/verify", "/v2/otp" ], "attempts_remaining": 3 }
{ "success": false, "state": "none", "error": "otp_not_found", "message": "No hay una verificación activa para este número.", "hint": "No hay una verificación activa. Inícia con POST /v2/otp.", "next": [ "/v2/otp" ] }
{ "success": false, "state": "verified", "error": "otp_already_verified", "message": "Este número ya está verificado.", "hint": "Este número ya está verificado.", "next": [] }
{ "success": false, "state": "expired", "error": "otp_code_expired", "message": "El código OTP expiró.", "hint": "El código expiró. Solicita uno nuevo con POST /v2/otp.", "next": [ "/v2/otp" ] }
{ "success": false, "state": "locked", "error": "otp_max_attempts", "message": "Se superó el máximo de intentos de verificación.", "hint": "Se agotaron los intentos. Genera un código nuevo con POST /v2/otp (code_rotate=true).", "next": [ "/v2/otp" ], "attempts_remaining": 0 }
{ "success": false, "error": "otp_internal", "message": "Ocurrió un error inesperado.", "hint": "Intenta de nuevo en unos momentos.", "next": [] }
Consultar Estado de Verificación (OTP)
52.curl -X GET "https://api.smsmasivos.com.mx/v2/otp/status?phone_number=5512345678&country_code=52" \ -H "apikey: TU_API_KEY"
import requests
response = requests.get(
'https://api.smsmasivos.com.mx/v2/otp/status',
headers={'apikey': 'TU_API_KEY'},
params={
'phone_number': '5512345678',
'country_code': '52'
}
)
print(response.json())
const params = new URLSearchParams({
phone_number: '5512345678',
country_code: '52'
});
const response = await fetch(
`https://api.smsmasivos.com.mx/v2/otp/status?${params}`,
{ headers: { apikey: process.env.SMSMASIVOS_API_KEY } }
);
const data = await response.json();
console.log(data);
{ "success": true, "state": "pending", "message": "Estado actual del OTP.", "hint": "Hay un código activo. Verifícalo con POST /v2/otp/verify o reenvíalo con POST /v2/otp.", "next": [ "/v2/otp/verify", "/v2/otp" ], "expires_at": "2026-07-21 12:00:00", "attempts_remaining": 5, "resend_available_in": 12, "request_id": "550e8400-e29b-41d4-a716-446655440000" }
{ "success": false, "error": "otp_missing_param", "message": "Falta un parámetro obligatorio.", "hint": "Envía 'phone_number' (hasta 10 dígitos).", "next": [] }
{ "success": false, "error": "otp_internal", "message": "Ocurrió un error inesperado.", "hint": "Intenta de nuevo en unos momentos.", "next": [] }
Objeto de respuesta
Estructura de la respuesta que devuelven estos endpoints.
pending: hay un código activo esperando verificación.pendingQué hizo la API con tu solicitud:
created: primera verificación para el número (o la anterior ya había expirado). HTTP201.resent: reenvió el mismo código vigente. HTTP200.restarted: generó un código nuevo e inició otra ronda (concode_rotate: true, o tras una verificación completada). HTTP200o201.
createdresentrestartedYYYY-MM-DD HH:mm:ss, hora del centro de México).message/company con channel: whatsapp (se ignoran, la plantilla de WhatsApp es fija).code_show: 1 (pruebas/automatización).{ "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, "warning": "channel=whatsapp usa una plantilla oficial fija; se ignoraron message/company.", "code": "483920", "request_id": "550e8400-e29b-41d4-a716-446655440000" }