Typebot en WhatsApp por la API oficial, con ZapClaw

En resumen: ZapClaw entrega cada mensaje de WhatsApp a un pequeño script intermediario (el relay), que ejecuta tu Typebot publicado por la Chat API de Typebot, y las respuestas del bot vuelven por ZapClaw. Los mensajes pasan por la Cloud API oficial de Meta, y el número sigue funcionando en la app de WhatsApp Business de tu celular. Mantienes el bot que ya construiste y no necesitas una app de Meta propia.

Conecta tu número en minutos y pon en él el bot que ya construiste, alojado por Typebot o self-hosted.

Plan Business: US$ 29 al mes, con números ilimitados

Empieza gratis Ver el relay

Lo que necesitas para conectar

No necesitas una empresa registrada ni un negocio verificado para conectar. La lista completa.

Por qué ZapClaw para un Typebot en WhatsApp

Por qué hay un relay en medio

Typebot no puede recibir el webhook de ZapClaw tal como llega. Su bloque Webhook solo reanuda una conversación que ya está en pausa en él, y su Chat API espera una llamada por mensaje, con una sesión guardada para cada persona.

Así que el relay se encarga de eso como lo describe la guía de Typebot para apps de mensajería (en inglés): abre una sesión con el primer mensaje del cliente, la continúa con los siguientes y devuelve las burbujas de texto del bot por ZapClaw. Son menos de 100 líneas de Node.js, sin nada que instalar.

Configúralo en cuatro pasos

  1. En Typebot, publica tu bot y copia su ID público (dónde encontrarlo). Mantén el flujo centrado en texto: burbujas de texto, campos de texto y botones.
  2. En ZapClaw, conecta tu número, crea una clave de API y copia la Clave de firma en la página Webhook.
  3. Ejecuta el relay de abajo con esos tres valores en un servidor con una dirección HTTPS pública, y pega esa dirección en Webhook en ZapClaw. ZapClaw solo entrega en una dirección HTTPS pública.
  4. Haz clic en Enviar evento de prueba en ZapClaw: el relay responde 200 y no inicia nada. Luego envía un mensaje de WhatsApp a tu número desde otro celular: el bot responde.

El relay, listo para ejecutar

Guárdalo como relay.mjs e inícialo con node relay.mjs en Node.js 18 o posterior. Lee su configuración del entorno: ZAPCLAW_API_KEY, ZAPCLAW_SECRET (la clave de firma), TYPEBOT_PUBLIC_ID, TYPEBOT_API para un Typebot self-hosted, y PORT, el puerto en el que escucha el relay (8787 por defecto), al que tu proxy HTTPS reenvía.

// relay.mjs: ZapClaw webhook in, Typebot Chat API, replies out through ZapClaw.
// Node.js 18 or newer, nothing to install. Start it with: node relay.mjs
import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';

const ZAPCLAW_API_KEY = process.env.ZAPCLAW_API_KEY; // zc_live_...
const ZAPCLAW_SECRET = process.env.ZAPCLAW_SECRET; // the webhook signing secret
const TYPEBOT_PUBLIC_ID = process.env.TYPEBOT_PUBLIC_ID; // your published bot
const TYPEBOT_API = process.env.TYPEBOT_API || 'https://typebot.co/api';

const sessions = new Map(); // customer: { id, choices }. Use your database in production.
const queues = new Map(); // one message at a time per customer, in order

const isSigned = (raw, header = '') => {
  const hmac = createHmac('sha256', ZAPCLAW_SECRET).update(raw).digest('hex');
  const expected = Buffer.from(`sha256=${hmac}`);
  const given = Buffer.from(String(header));
  return given.length === expected.length && timingSafeEqual(given, expected);
};

const typebot = async (path, body) => {
  const res = await fetch(`${TYPEBOT_API}/v1/${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...body, textBubbleContentFormat: 'markdown' }),
  });
  return res.ok ? res.json() : null;
};

const sendText = (from, to, text) =>
  fetch('https://api.zapclaw.app/api/v1/send/text', {
    method: 'POST',
    headers: { 'X-API-Key': ZAPCLAW_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ from, to, text }),
  });

const answer = async ({ data, account }) => {
  const customer = data.user_id || data.from;
  const from = account.display_phone_number || account.phone_number_id;
  const to = data.from || data.user_id;
  const session = sessions.get(customer);
  let reply = null;
  if (session) {
    // Typebot takes a button by its exact label, so accept its number or any letter case.
    const typed = data.text.body.trim();
    const choice = session.choices.find(
      (label, i) => typed === String(i + 1) || typed.toLowerCase() === label.toLowerCase()
    );
    reply = await typebot(`sessions/${session.id}/continueChat`, {
      message: { type: 'text', text: choice || typed },
    });
  }
  if (!reply) {
    // First message, or a session that expired (continueChat answers 404): start the bot.
    reply = await typebot(`typebots/${TYPEBOT_PUBLIC_ID}/startChat`, {
      prefilledVariables: { name: data.contact_name, phone: to },
    });
    if (!reply) return;
  }
  const choices =
    reply.input?.type === 'choice input'
      ? reply.input.items.map((item) => item.content).filter(Boolean)
      : [];
  if (reply.input) sessions.set(customer, { id: reply.sessionId || session.id, choices });
  else sessions.delete(customer); // the flow ended: the next message starts it again
  const texts = (reply.messages || [])
    .filter((m) => m.type === 'text')
    // Typebot writes standard Markdown; WhatsApp reads *bold*, ~strike~ and bare links.
    .map((m) => m.content.markdown
      .replace(/\*\*(.+?)\*\*/g, (_, t) => `*${t}*`)
      .replace(/~~(.+?)~~/g, (_, t) => `~${t}~`)
      .replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, label, url) => `${label} (${url})`));
  if (choices.length) texts.push(choices.map((label, i) => `${i + 1}. ${label}`).join('\n'));
  for (const text of texts) if (text) await sendText(from, to, text);
};

createServer((req, res) => {
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const raw = Buffer.concat(chunks);
    if (!isSigned(raw, req.headers['x-zapclaw-signature'])) return res.writeHead(401).end();
    res.writeHead(200).end(); // answer ZapClaw now: it waits 10 seconds, a bot can take longer
    let event;
    try {
      event = JSON.parse(raw.toString('utf8'));
    } catch {
      return;
    }
    if (event.event !== 'message.received' || event.data?.type !== 'text') return;
    const key = event.data.user_id || event.data.from;
    const next = (queues.get(key) || Promise.resolve())
      .then(() => answer(event))
      .catch((err) => console.error(err));
    queues.set(key, next);
  });
}).listen(process.env.PORT || 8787);

En un Typebot self-hosted, apunta TYPEBOT_API a la dirección de tu viewer, la app donde las personas responden a tus bots, seguida de /api.

Lo que hace: comprueba el encabezado X-ZapClaw-Signature, responde a ZapClaw de inmediato (ZapClaw espera 10 segundos, y un bot que llama a una IA puede tardar más) y atiende los mensajes de cada cliente de uno en uno, en orden. Las opciones de un bloque Buttons salen como una lista numerada, y el cliente responde con el número o con el texto de la opción, en mayúsculas o minúsculas. Cuando el flujo termina, el siguiente mensaje lo vuelve a empezar.

Crea variables llamadas name y phone en tu bot y el relay las llena con el primer mensaje, con el nombre del contacto en WhatsApp y su número. Las sesiones viven en la memoria del relay, así que reiniciarlo vuelve a empezar todas las conversaciones: en producción, guárdalas en tu base de datos, como recomienda la guía de Typebot.

El relay envía las burbujas de texto. Una burbuja de imagen trae su dirección en content.url, que puedes enviar con POST /api/v1/send/media.

Llamar a ZapClaw desde dentro del bot

El bloque HTTP Request de Typebot también puede llamar a la API de ZapClaw, con un encabezado X-API-Key y un cuerpo personalizado armado con tus variables: por ejemplo, para avisar a un vendedor por WhatsApp cuando un lead llega al final del bot. La misma llamada como comando curl:

curl -X POST https://api.zapclaw.app/api/v1/send/text \
  -H "X-API-Key: zc_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "from": "15551617409", "to": "15555550100", "text": "New lead from the bot" }'

from es tu número conectado, que el webhook trae en account.display_phone_number, y to es la persona a quien escribes. Un mensaje enviado responde 201. El texto libre funciona dentro de las 24 horas siguientes al último mensaje de esa persona a tu número; fuera de esa ventana, Meta solo acepta una plantilla aprobada, con POST /api/v1/send/template.

Lo que llega al relay

Cada mensaje llega como un evento message.received: el texto en data.text.body, el remitente en data.from (o data.user_id para un contacto que solo muestra su nombre de usuario), el nombre del contacto en data.contact_name y tu número en account.display_phone_number.

{
  "event": "message.received",
  "sequence": 1042,
  "timestamp": "2026-10-15T14:00:00.000Z",
  "data": {
    "from": "15555550100",
    "user_id": "US.1A2B3C4D5E6F7G8H9I0J",
    "contact_name": "Maria Silva",
    "message_id": "wamid.HBg...",
    "type": "text",
    "timestamp": "1792072800",
    "text": { "body": "Hi" }
  },
  "account": {
    "phone_number_id": "964283400112599",
    "display_name": "My Business",
    "display_phone_number": "+1 555 161-7409"
  }
}

El relay solo pasa los mensajes de texto entrantes: el evento de prueba del panel y cualquier otro evento reciben un 200 y nada más. Todos los eventos y campos están en la documentación (en inglés).

Cuánto cuesta

Meta te cobra directamente las tarifas de mensajes de WhatsApp, sin margen de ZapClaw. Todos los planes y precios.

Para quién es

Equipos que construyeron en Typebot flujos de captación de leads, calificación, preguntas frecuentes o reservas y quieren llevarlos al número de WhatsApp que sus clientes ya usan. Las agencias conectan los números de sus clientes en el plan Agency, cada cliente aislado de los demás. No sirve para envíos masivos.

Para ir más allá

Fuentes

Documentación de Typebot, en inglés, consultada el 15 de octubre de 2026. Las funciones cambian, así que revisa cada fuente antes de construir.

Pon tu Typebot en WhatsApp hoy

Empieza en el plan gratuito: un número, sin tarjeta. Pasa al plan Business cuando necesites más números o más mensajes.

Empieza gratis Ver la documentación del webhook (en inglés)