Automatizar verificação por OTP em Node.js
Equipe AltiviaCloud · 11 de setembro de 2026 · 4 min de leitura
O laço inteiro: pedir o número, esperar o código, encerrar o pedido. Com o try/catch que evita pedido pendurado e o erro que o tutorial feliz não mostra.
Teste automatizado que passa por um cadastro real precisa de um telefone real. Abaixo está o laço inteiro em Node, incluindo as partes que só importam quando ele roda sozinho às três da manhã.
Antes de começar
Você precisa de uma chave de API criada no painel e de saldo na carteira. Toda requisição autentica com um token:
const API = "https://altiviacloud.com.br/api/v1";
const headers = {
Authorization: `Bearer ${process.env.ALTIVIA_API_KEY}`,
"Content-Type": "application/json",
};
A chave fica na variável de ambiente. Chave que entra no repositório é chave que você vai rotacionar com pressa depois.
Passo 1 — pedir um número
async function pedirNumero(countryId, serviceId) {
const res = await fetch(`${API}/orders`, {
method: "POST",
headers,
body: JSON.stringify({ countryId, serviceId }),
});
const body = await res.json();
if (!res.ok) {
throw new Error(body.error?.message ?? `Falha ao criar pedido (${res.status})`);
}
return body.data; // { id, phoneNumber, expiresAt, ... }
}
Leia body.error.code, não o texto da mensagem. Mensagem é reescrita; código não. Dois que aparecem na prática:
- saldo insuficiente — adicione saldo antes de repetir. Repetir na hora só falha de novo.
- limite excedido — você passou do teto por minuto. O cabeçalho
Retry-Afterdiz quanto esperar.
Os identificadores de país e serviço vêm de /api/v1/countries e /api/v1/services. Consulte uma vez e guarde — buscar a lista inteira antes de cada pedido é requisição desperdiçada e latência somada ao seu fluxo.
Passo 2 — esperar o código
Consultar em laço é o mais simples que funciona, desde que respeite a expiração que a própria API já informou:
async function esperarCodigo(orderId, expiresAt) {
const limite = new Date(expiresAt).getTime();
while (Date.now() < limite) {
const res = await fetch(`${API}/orders/${orderId}/sms`, { headers });
const { data } = await res.json();
const codigo = data?.find((sms) => sms.code)?.code;
if (codigo) return codigo;
await new Promise((r) => setTimeout(r, 3000));
}
return null; // nada chegou dentro da janela
}
Três segundos é um intervalo razoável. Consultar a cada 200 milissegundos não torna a operadora mais rápida — só gasta a sua cota de requisições.
Repare que o laço se limita pelo expiresAt em vez de um número fixo de tentativas. Quem sabe por quanto tempo o número vive é a API, então deixe ela decidir.
Passo 3 — encerrar o pedido
Este é o passo que as pessoas esquecem, e é o que decide se você paga:
async function finalizar(orderId) {
await fetch(`${API}/orders/${orderId}/finish`, { method: "POST", headers });
}
async function cancelar(orderId) {
await fetch(`${API}/orders/${orderId}/cancel`, { method: "POST", headers });
}
A regra é direta: chegou código, finalize. Não chegou, cancele. Cancelar um pedido que não recebeu SMS devolve o valor integral. Cancelar um que já recebeu é recusado com 400 — nesse ponto o serviço foi prestado, e finalizar é a chamada correta.
Juntando tudo
async function verificar(countryId, serviceId) {
const pedido = await pedirNumero(countryId, serviceId);
try {
const codigo = await esperarCodigo(pedido.id, pedido.expiresAt);
if (!codigo) {
await cancelar(pedido.id); // sem SMS: estorno integral
return null;
}
await finalizar(pedido.id);
return { telefone: pedido.phoneNumber, codigo };
} catch (erro) {
await cancelar(pedido.id); // não deixe o pedido pendurado
throw erro;
}
}
O try/catch não é enfeite. Sem ele, uma falha entre pedir e finalizar deixa o pedido aberto — ele vai expirar e estornar sozinho, mas vinte minutos depois, e a sua suíte de testes fica sem número nesse meio-tempo.
Onde isso quebra em produção
Três coisas que o tutorial feliz não mostra:
O número pode não receber. É resultado normal, não exceção. O seu teste precisa tratar o null como caminho previsto, com mensagem que diga "nenhum SMS chegou na janela" — e não estourar três passos adiante com um erro que manda quem investigar para o lugar errado.
O laço não escala. Com um pedido, tudo bem. Com trezentos simultâneos, são milhares de requisições quase todas inúteis, e o limite de taxa derruba inclusive a criação de pedidos novos.
A limpeza precisa estar no bloco de finalização, não na última linha do caminho feliz. Pedido de teste abandonado ocupa número e não devolve nada até expirar.
Trocando o laço por aviso
Se você prefere ser avisado a ficar perguntando, a API entrega os eventos do pedido por SSE e também por webhook para um endereço seu. Isso elimina o laço por completo, e passa a valer a pena assim que o teste entra no CI.
A decisão entre os dois está em webhook ou consulta em laço, e o passo a passo de configuração, na documentação de webhooks. Para o contexto do produto, a página de números virtuais.