Verificación OTP
Verificación en dos pasos (OTP): códigos de un solo uso por SMS, WhatsApp o llamada de voz, con una sola integración para los tres canales.
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 (channel: sms, whatsapp o voice) — solo cambias channel.
El usuario lo recibe e ingresa
El usuario recibe el código y lo captura en tu app. Por WhatsApp sale por nuestra integración oficial: sin cuenta de WhatsApp Business ni verificación de Meta.
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. No tiene costo.
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.
Usar estos métodos no tiene costo extra: solo se descuenta un mensaje de tu saldo cuando la API le envía el código al usuario por SMS, WhatsApp o llamada de voz (POST /v2/otp, ya sea al crear, reenviar o rotar), sin importar el canal. Verificar el código, consultar su estado o invalidarlo nunca tienen costo, sin importar cuántas veces los llames.
Los otros dos endpoints son auxiliares; en un flujo normal no los necesitas:
GET /v2/otp/status: solo lectura, para tu UI (contador de reenvío, intentos restantes).DELETE /v2/otp: cancela una verificación en curso (limpieza en tests, cambio de número). 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.
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 nuestra integración oficial (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).
smswhatsappvoiceNombre de tu empresa o app. Se inserta en message mediante el placeholder {{company}}.
Si channel es whatsapp, se ignora: la plantilla oficial de WhatsApp es fija (recibes un warning avisando).
Texto que recibe el usuario. 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.
Ejemplo armado (con code: "483920" y company: "Mi Empresa"): 483920 es tu codigo de verificacion de Mi Empresa. No lo compartas.
Si channel es whatsapp, se ignora: el texto sale por la plantilla oficial fija de WhatsApp (recibes un warning avisando).
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:
false(default): no devolver.true: devolver (pruebas/automatización del lado del servidor).
true el flujo se ejecuta completo (genera y registra el código) pero el SMS no se entrega y no se descuenta saldo. Como el usuario nunca recibe el mensaje, debes enviar también code_in_response: true para obtener el código en la respuesta; si falta, responde 400 otp_missing_param.YYYY-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",
"message": "{{code}} es tu codigo de verificacion de {{company}}. No lo compartas."
}'
<?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('API_KEY')
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'phone_number' => '5512345678',
'country_code' => '52',
'company' => 'Mi Empresa',
'message' => '{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.'
]));
$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',
'message': '{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.'
}
)
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.API_KEY
},
body: JSON.stringify({
phone_number: '5512345678',
country_code: '52',
company: 'Mi Empresa',
message: '{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.'
})
});
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',
message: '{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.'
},
{
headers: {
'Content-Type': 'application/json',
'apikey': process.env.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['API_KEY']
})
request.body = {
phone_number: '5512345678',
country_code: '52',
company: 'Mi Empresa',
message: '{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.'
}.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",
message = "{{code}} es tu codigo de verificacion de {{company}}. No lo compartas."
};
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\","
+ "\"message\":\"{{code}} es tu codigo de verificacion de {{company}}. No lo compartas.\""
+ "}";
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": [] }
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('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.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.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['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.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": [] }
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.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": [] }
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_in_response: true (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" }