Dev

Enviar mensajes de WhatsApp desde Node.js en 5 minutos

Puedes enviar un mensaje de WhatsApp desde Node.js solo con el fetch nativo: sin librería de WhatsApp y sin navegador headless. Toma unos cinco minutos si ya tienes un número conectado. Transparencia: ZapZap API es no oficial y conecta un número común por código QR. Eso significa que está fuera de los términos de WhatsApp y que los números pueden ser limitados o bloqueados, así que lee la lista del final antes de enviar a personas reales.

Publicado el · 4 min de lectura

Este artículo también está disponible en: Português · English

0. Lo que necesitas (2 minutos)

1) Una cuenta en ZapZap y una instancia conectada por código QR. Las cuentas nuevas reciben un crédito de bienvenida al verificar el teléfono, según la campaña vigente; el inicio rápido muestra dónde hacer clic. 2) Tu API key, tu API secret y el ID de la instancia. 3) Node 18 o más nuevo (node -v).

Pon las credenciales en variables de entorno, nunca en el código. La autenticación son dos encabezados en cada petición, x-api-key y x-api-secret.

Shell

export ZAPZAP_INSTANCE_ID="your-instance-id"
export ZAPZAP_API_KEY="your-api-key"
export ZAPZAP_API_SECRET="your-api-secret"

1. Enviar un texto (1 minuto)

Crea el archivo send.mjs con el código de abajo y ejecútalo con node send.mjs. La extensión .mjs habilita await a nivel de archivo; en CommonJS, envuelve la llamada en una función async. Usa tu propio número primero. El número va con código de país y solo dígitos, sin +, espacios ni guiones.

Node.js: send.mjs

const BASE = "https://api.zapzapapi.com/api/v1";
const { ZAPZAP_INSTANCE_ID, ZAPZAP_API_KEY, ZAPZAP_API_SECRET } = process.env;

async function zapzap(path, body) {
  const res = await fetch(BASE + "/" + ZAPZAP_INSTANCE_ID + path, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": ZAPZAP_API_KEY,
      "x-api-secret": ZAPZAP_API_SECRET,
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(20_000), // never leave a call hanging
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error("ZapZap " + res.status + ": " + JSON.stringify(data));
  return data;
}

// number: country code + digits only (no +, spaces or dashes)
console.log(await zapzap("/send/text", { number: "5511999999999", text: "Hello from Node.js" }));

2. Enviar un PDF o una imagen (1 minuto)

Los archivos pasan por /send/media. El campo type elige el tipo (image, video, audio, ptt, document, sticker), file es una URL pública, text es el pie de foto y docName es el nombre del archivo en los documentos. El archivo debe ser accesible por una URL pública; si está en tu disco, súbelo a un almacenamiento y usa un enlace temporal.

Node.js

await zapzap("/send/media", {
  number: "5511999999999",
  type: "document",
  file: "https://example.com/invoice-1042.pdf",
  text: "Your invoice",
  docName: "invoice-1042.pdf",
});

3. Recibir respuestas con Express (1 minuto)

Registra tu URL pública como webhook de la instancia con el evento de mensajes activado (consulta configurar webhook). En desarrollo local necesitas un túnel, porque la API tiene que alcanzarte por internet.

Descarta siempre los eventos fromMe, o una respuesta automática se responde a sí misma en bucle. Revisa el formato exacto con un mensaje real primero; para validar el encabezado X-ZapZap-Signature y eliminar entregas repetidas, sigue webhooks en producción.

Node.js: Express

import express from "express";

const app = express();
app.use(express.json());

app.post("/webhook", (req, res) => {
  res.sendStatus(200); // answer first, process after

  const msg = req.body?.data?.message;
  if (!msg || msg.fromMe) return; // ignore your own messages

  console.log("from " + msg.chatid + ": " + msg.text);
  // reply, save to a database, call an AI agent...
});

app.listen(3000, () => console.log("listening on :3000"));

4. Reintenta solo los errores correctos

Reintenta solo en 429 y 5xx, con esperas crecientes. Un 400, 401 o 403 significa que la petición o las credenciales están mal, y reintentar solo produce más errores.

Node.js

async function withRetry(fn, tries = 4) {
  for (let i = 0; ; i++) {
    try {
      return await fn();
    } catch (err) {
      const retryable = /ZapZap (429|5\d\d)/.test(err.message);
      if (!retryable || i >= tries - 1) throw err;
      await new Promise((r) => setTimeout(r, 2 ** i * 1000));
    }
  }
}

await withRetry(() => zapzap("/send/text", { number: "5511999999999", text: "Retried send" }));

5. Antes de enviar a personas reales

Credenciales solo en variables de entorno, nunca en git. Timeout en cada llamada y reintento solo en 429 y 5xx. En listas, envía de uno en uno con una pausa aleatoria entre mensajes en vez de disparar todo en paralelo; trata los valores de la pausa como un punto de partida tuyo, no como una regla de WhatsApp, y en campañas grandes la cola del servidor saca esto de tu app. Envía solo a quien aceptó recibir mensajes tuyos y respeta a quien pidió salir.

No oficial significa riesgo de bloqueo. Ritmo, calentamiento y listas limpias lo reducen, nada lo elimina. Usa un número que puedas perder mientras pruebas y lee la guía antibloqueo. Si necesitas un canal homologado, mira API oficial vs ZapZap. Para planificar: R$15 por número activo al mes, prepago, sin cobro por mensaje (precios).

Lista completa de endpoints en la referencia de la API y en el archivo OpenAPI. ¿Prefieres otro lenguaje? Los mismos endpoints funcionan desde Python y las otras guías del blog, o sin código en n8n. Para una versión más extensa de esta guía, mira enviar WhatsApp desde Node.js. Los SDK oficiales todavía no están publicados en npm ni en PyPI; el código está en GitHub en zapzap-sdk-node, zapzap-sdk-python y zapzap-sdk-php, y la API REST funciona directo con fetch, como arriba.

Resumen rápido

  • El fetch nativo de Node 18 basta: un POST a /send/text con x-api-key y x-api-secret.
  • Los archivos van por /send/media con una URL pública; las respuestas llegan por webhook, y los eventos fromMe deben descartarse.
  • Reintenta solo 429 y 5xx, con espera creciente. 400, 401 y 403 son error de petición o de credencial.
  • Los SDK aún no están en npm ni PyPI (repositorios en GitHub); la API REST funciona directo, sin librería.

Lee también

Preguntas frecuentes

¿Necesito alguna librería para enviar WhatsApp desde Node.js?

No. El fetch nativo de Node 18 o más nuevo hace el POST a /send/text. Express solo entra si quieres recibir respuestas por webhook.

¿Por qué mi bot se responde a sí mismo en bucle?

Porque el webhook también entrega los mensajes que enviaste tú mismo. Descarta los eventos en que fromMe es verdadero antes de responder.

¿Existe un SDK de ZapZap para Node.js en npm?

Todavía no está publicado en npm. El código está en github.com/zapzapapi/zapzap-sdk-node, y puedes usar la API REST directo con fetch, como en esta guía.

¿Enviar por esta API puede bloquear mi número?

Puede. La API es no oficial y va contra los términos de WhatsApp. Ritmo, calentamiento, opt-in y listas limpias reducen el riesgo, pero nada lo elimina. Prueba con un número que puedas perder.

Envía tu primer mensaje ahora

Crea la cuenta, conecta un número de prueba por código QR y ejecuta send.mjs. R$15 por número activo al mes, sin cobro por mensaje.