Confirmar que o telefone informado realmente pertence ao usuário é uma das formas mais eficientes de reduzir cadastros falsos e proteger logins. O jeito mais popular de fazer isso é o SMS OTP: você envia um código de uso único por SMS e o usuário digita esse código no seu site ou app.
Neste tutorial você vai implementar uma verificação por SMS OTP com Node.js usando a API SMS OTP da APIBrasil, do envio do código à validação, incluindo o webhook que avisa o que aconteceu com cada mensagem.
Como funciona o fluxo de SMS OTP
- O usuário informa o celular.
- O seu back-end gera um código aleatório de 6 dígitos e guarda um hash dele com prazo de validade.
- O back-end chama a APIBrasil, que envia o SMS.
- A APIBrasil avisa o seu webhook sobre cada etapa do envio.
- O usuário digita o código, e o back-end compara com o hash guardado.
A API SMS OTP fica na categoria Comunicação do catálogo em doc.apibrasil.io, com preço a partir de R$ 0,10 por mensagem. Ela é a opção indicada para mensagens transacionais de alta prioridade, como códigos de verificação.
Pré-requisitos
- Node.js 18 ou mais novo e o pacote
express(npm install express). - Bearer Token da APIBrasil, em Credenciais no painel. Veja os primeiros passos.
- Uma URL pública com HTTPS para o webhook. Em desenvolvimento, serviços de túnel como ngrok ou Cloudflare Tunnel resolvem.
Passo 1: a chamada de envio
POST https://gateway.apibrasil.io/api/v2/sms/send/credits
Authorization: Bearer SEU_TOKEN
Content-Type: application/json
{
"tipo": "sms-otp",
"number": "5531999999999",
"message": "Seu código de verificação é 482913",
"operator": "claro",
"user_reply": true,
"webhook_url": "https://seusite.com.br/webhook/sms"
}
| Campo | Para que serve |
|---|---|
number | Celular com código do país e DDD, só números |
message | Texto do SMS |
operator | Operadora, como no exemplo do catálogo |
user_reply | Permite que o destinatário responda |
webhook_url | URL que vai receber os avisos de status desta mensagem |
A resposta traz o resultado do envio dentro de response, e não em data como na maioria das APIs:

Passo 2: gere e guarde o código com segurança (otp.js)
Três regras de segurança para OTP. Salve o código abaixo como otp.js:
- Gere o código com
crypto.randomInt, nunca comMath.random. - Guarde só o hash do código, com prazo de validade curto (5 minutos é comum).
- Limite as tentativas de validação e os reenvios por número.
import crypto from "node:crypto";
const codigos = new Map(); // em produção, use Redis ou o seu banco
export function gerarOtp(numero) {
const codigo = String(crypto.randomInt(0, 1_000_000)).padStart(6, "0");
const hash = crypto.createHash("sha256").update(numero + codigo).digest("hex");
codigos.set(numero, { hash, expira: Date.now() + 5 * 60_000, tentativas: 0 });
return codigo;
}
export function validarOtp(numero, codigo) {
const reg = codigos.get(numero);
if (!reg || Date.now() > reg.expira) return false;
if (++reg.tentativas > 5) {
codigos.delete(numero);
return false;
}
const hash = crypto.createHash("sha256").update(numero + codigo).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(reg.hash));
if (ok) codigos.delete(numero);
return ok;
}
Passo 3: o servidor com envio, validação e webhook
Crie o arquivo server.js:
import express from "express";
import { gerarOtp, validarOtp } from "./otp.js";
const app = express();
app.use(express.json());
async function enviarSms(numero, texto) {
const resp = await fetch("https://gateway.apibrasil.io/api/v2/sms/send/credits", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.APIBRASIL_TOKEN}`,
},
body: JSON.stringify({
tipo: "sms-otp",
number: numero,
message: texto,
operator: "claro",
user_reply: false,
webhook_url: process.env.WEBHOOK_URL,
}),
signal: AbortSignal.timeout(60_000),
});
if (resp.status === 402) throw new Error("Saldo insuficiente na APIBrasil");
const json = await resp.json();
if (!resp.ok || json.error) throw new Error(json.message || `HTTP ${resp.status}`);
return json.response?.data; // { id, status, created_at }
}
app.post("/otp/enviar", async (req, res) => {
const numero = String(req.body.numero || "").replace(/\D/g, "");
if (!/^55\d{10,11}$/.test(numero)) return res.status(400).json({ erro: "Número inválido" });
const codigo = gerarOtp(numero);
try {
const envio = await enviarSms(numero, `Seu código de verificação é ${codigo}. Ele expira em 5 minutos.`);
console.log("POST /otp/enviar", numero, "→ id", envio?.id, `(${envio?.status})`);
res.json({ enviado: true });
} catch (e) {
res.status(502).json({ erro: "Não foi possível enviar o SMS" });
}
});
app.post("/otp/validar", (req, res) => {
const numero = String(req.body.numero || "").replace(/\D/g, "");
const ok = validarOtp(numero, String(req.body.codigo || ""));
res.status(ok ? 200 : 401).json({ verificado: ok });
});
app.post("/webhook/sms", (req, res) => {
const [aviso] = Array.isArray(req.body) ? req.body : [];
res.sendStatus(200); // responda antes de processar
if (!aviso) return;
console.log("POST /webhook/sms", aviso.id, aviso.status);
});
app.listen(3000, () => console.log("Servidor OTP ouvindo na porta 3000"));
Passo 4: entenda os avisos do webhook
A documentação de webhooks da APIBrasil descreve quatro eventos para SMS, na ordem em que acontecem:
| Status | Significado |
|---|---|
inserted_for_processing | A mensagem entrou na fila, ainda não foi validada nem enviada |
valid | O número passou na validação de formato e de bloqueio |
sent_to_carrier | A operadora recebeu a mensagem (não é a confirmação no aparelho) |
reply | O destinatário respondeu, e o texto vem junto |
Três detalhes que a documentação faz questão de destacar:
- O corpo do webhook é um array, com um aviso dentro. Por isso o
const [aviso] = req.body. - Responda 200 antes de processar. Gravar no banco antes de responder pode transformar lentidão em timeout de webhook.
- A chave de idempotência é
id+status. O mesmo id chega uma vez por status. Deduplicar só pelo id descartaria avisos legítimos.

Boas práticas de SMS OTP
- Texto curto e claro: diga o que é o código, de quem é e quando expira. Nunca peça para o usuário compartilhar o código.
- Limite de reenvio: um reenvio a cada 60 segundos e poucos por hora por número evitam abuso e custo.
- Não revele se o número existe na sua base na tela de login.
- Use HTTPS no webhook: o aviso carrega o número do destinatário e, no caso de resposta, o texto.
Perguntas frequentes
Qual a diferença entre SMS OTP, SMS Avulso e SMS Marketing?
São APIs diferentes no catálogo, pensadas para usos diferentes. A SMS OTP é voltada a mensagens transacionais de alta prioridade, como códigos. Para campanhas, use a SMS Marketing.
Dá para testar sem gastar?
Consulte a página da API SMS OTP em doc.apibrasil.io para ver o suporte a homologação desta API. O corpo de exemplo do catálogo não inclui o campo homolog, então confirme antes de rodar testes automatizados.
Próximos passos
Use a verificação por SMS junto com a consulta de CPF na Receita Federal para um cadastro mais seguro, ou veja como enviar SMS com PHP usando a SDK da APIBrasil.
![]()









