Enviar WhatsApp com Node.js: exemplo de API REST com fetch
Para enviar WhatsApp com Node.js você faz um POST com fetch em /api/v1/{instanceId}/send/text, com os cabeçalhos x-api-key e x-api-secret e um JSON com number e text. O fetch é nativo desde o Node 18, então não precisa de axios nem de pacote de WhatsApp. Abaixo: envio de texto e de arquivo, retry, lote com intervalo e um webhook em Express.
Publicado em · 3 min de leitura
O que você precisa antes de começar
Conta no ZapZap, instância conectada por QR code, chave e segredo da API e o ID da instância. A autenticação é por dois cabeçalhos. Use Node 18 ou superior e guarde as credenciais em variáveis de ambiente.
Valide a instância com cURL antes de codar, para separar problema de código de problema de conta.
cURL
curl -X POST https://api.zapzapapi.com/api/v1/SEU_INSTANCE_ID/send/text \
-H "Content-Type: application/json" \
-H "x-api-key: SUA_API_KEY" \
-H "x-api-secret: SEU_API_SECRET" \
-d '{"number":"5511999999999","text":"Olá! Teste de envio pela API."}'Como enviar uma mensagem de texto com fetch
A função abaixo monta a requisição, define timeout com AbortSignal.timeout e trata erro HTTP. Sem timeout, uma chamada travada segura o seu processo.
Um módulo ES (arquivo .mjs ou "type": "module") permite usar await no topo. Em CommonJS, envolva a chamada numa função assíncrona.
Node.js
const BASE = "https://api.zapzapapi.com/api/v1/" + process.env.ZAPZAP_INSTANCE_ID;
const HEADERS = {
"Content-Type": "application/json",
"x-api-key": process.env.ZAPZAP_API_KEY,
"x-api-secret": process.env.ZAPZAP_API_SECRET,
};
export async function enviarTexto(number, text) {
const r = await fetch(BASE + "/send/text", {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ number, text }),
signal: AbortSignal.timeout(15000),
});
const corpo = await r.json().catch(() => ({}));
if (!r.ok) throw Object.assign(new Error("ZapZap " + r.status), { status: r.status, corpo });
return corpo; // { messageId, status }
}
console.log(await enviarTexto("5511999999999", "Olá! Seu pedido saiu para entrega."));Como enviar imagem, PDF e áudio com Node.js
A rota é /send/media. O campo type escolhe o tipo (image, video, audio, ptt, document, sticker e outros), file é a URL pública, text é a legenda e docName o nome do documento. Para PDF use type document.
O arquivo precisa estar numa URL pública. Se ele está no seu disco ou em memória, publique em um armazenamento com link temporário primeiro.
Node.js
export async function enviarPdf(number, file, docName, text = "") {
const r = await fetch(BASE + "/send/media", {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ number, type: "document", file, docName, text }),
signal: AbortSignal.timeout(60000),
});
if (!r.ok) throw new Error("ZapZap " + r.status + " " + (await r.text()));
return r.json();
}Como fazer retry e enviar uma lista com intervalo
Repita apenas 429 e 5xx, com espera crescente. Erros 400, 401 e 403 apontam para o pedido ou para as credenciais, e repetir só gera mais erro.
Para listas, envie em série com uma pausa aleatória entre mensagens, em vez de disparar tudo em paralelo. Os valores do exemplo (2 a 6 segundos) são um ponto de partida seu, não uma regra do WhatsApp. Para campanhas maiores, a fila do servidor (POST /sender/simple, com delayMin e delayMax) tira esse controle da sua aplicação.
Node.js
const dormir = (ms) => new Promise((ok) => setTimeout(ok, ms));
export async function enviarComRetry(number, text, tentativas = 4) {
for (let i = 0; i < tentativas; i++) {
try {
return await enviarTexto(number, text);
} catch (e) {
const transitorio = !e.status || e.status === 429 || e.status >= 500;
if (!transitorio || i === tentativas - 1) throw e;
await dormir(2 ** i * 1000 + Math.random() * 500);
}
}
}
export async function enviarLista(contatos) {
for (const [numero, texto] of Object.entries(contatos)) {
await enviarComRetry(numero, texto);
await dormir(2000 + Math.random() * 4000);
}
}Como receber as respostas com um webhook em Express
Configure a URL em configurar webhook e ligue o evento messages. A mensagem recebida fica em data.message (text, chatid, fromMe). Ignore fromMe verdadeiro. Responda 200 antes de processar.
Para validar a assinatura X-ZapZap-Signature e deduplicar reentregas, siga o guia de webhook em produção. Se o evento não chega, veja webhook que não chega.
Node.js (Express)
import express from "express";
const app = express();
app.use(express.json({ limit: "2mb" }));
app.post("/webhooks/zapzap", (req, res) => {
res.sendStatus(200); // confirma primeiro
const evento = req.body;
const msg = evento.data && evento.data.message;
if (evento.event !== "messages" || !msg || msg.fromMe) return;
console.log("mensagem de", msg.chatid, ":", msg.text);
});
app.listen(3000);Checklist antes de colocar em produção
Credenciais fora do código; timeout em toda chamada; retry só em 429 e 5xx; envio em série com intervalo; log de status e corpo nas falhas; assinatura do webhook validada; e envio só para quem aceitou receber. Se prefere automação sem código, o nó do n8n usa os mesmos endpoints.
Outras linguagens: Python, PHP, Ruby, Go. Todos os endpoints estão na referência da API.
Resumo rápido
- Node 18 ou superior já traz fetch: não precisa de axios para enviar WhatsApp.
- Sempre use timeout (AbortSignal.timeout) e repita só 429 e 5xx.
- Para listas, envie em série com pausa aleatória entre as mensagens.
- No Express, responda 200 primeiro e ignore mensagens com fromMe.
Continue lendo
Perguntas frequentes
Preciso de axios ou de alguma biblioteca de WhatsApp?
Não. O fetch nativo do Node 18 resolve. Axios funciona do mesmo jeito, com os mesmos cabeçalhos e o mesmo JSON.
Funciona com TypeScript?
Sim. O código é o mesmo com tipos nos parâmetros. O arquivo OpenAPI pode gerar tipos para as respostas se você quiser tipagem completa.
Como evitar enviar a mesma mensagem duas vezes?
Guarde uma chave de idempotência na sua base (por exemplo, pedido mais tipo de aviso) antes de chamar a API e consulte antes de reenviar. O retry só deve ocorrer quando a chamada falhou sem resposta clara.
Usar a API não oficial pode levar a bloqueio do número?
Pode. Uma API não oficial viola os termos do WhatsApp e o número pode ser limitado ou bloqueado. Opt-in, ritmo humano, aquecimento e lista limpa reduzem o risco, mas não existe garantia. Para um canal homologado, a opção é a API oficial da Meta.
Rode o exemplo na sua instância
Crie a conta, conecte um número e envie a primeira mensagem. R$15 por número ativo ao mês, sem cobrança por mensagem.
