WhatsApp API comprobando…

Documentación

Tu número de WhatsApp, por HTTP.

Vinculas un número escaneando un QR, como WhatsApp Web. Desde ahí tu sistema recibe cada mensaje en un webhook y contesta con una llamada: texto, notas de voz, archivos. Sabes si lo leyeron y a qué mensaje responde cada persona.

Dirección de la API https://…
AD
Asistente de Danielen línea
Hola, vengo de parte de Daniel10:02
¡Hola, Camila! Le aviso a Daniel y en cuanto apruebe te escribo.10:02 ✓✓
Daniel respondió «sí» a la solicitud
Listo, Daniel te dio acceso. ¿Qué duda tienes?10:04 ✓✓
Listo, Daniel te dio acceso. ¿Qué duda…
¿Cómo priorizaría él este lanzamiento?10:05
Primero lo que el cliente ya pidió. Te lo resumo en tres pasos…10:05 ✓✓

Consola: hazlo aquí mismo

Pega tu llave y sigue los tres pasos: crear la conexión, escanear el QR y mandar un mensaje de prueba. A la derecha ves cada respuesta tal cual.

Esta consola es solo para probar y entender.No tienes que usarla: todo lo que hace aquí lo puede hacer tu propia página, llamando a la API directamente. Cada botón es una llamada HTTP que está explicada más abajo con su ejemplo. Lo normal es que tu sistema cree la conexión, pinte el QR en tu pantalla y envíe y reciba los mensajes solo, sin volver a entrar aquí.

0 Tu llave

Se queda solo en esta pestaña: al cerrarla se borra. No la pegues en un computador ajeno.

respuesta
// Aquí aparece lo que contesta la API.

Tu llave, y cómo encaja todo

¿Qué es la llave? Es una contraseña larga que empieza por hk_ y que identifica a tu sistema ante la API. No es de una persona ni se usa para iniciar sesión en ninguna pantalla: la manda tu servidor en cada llamada, en la cabecera Authorization: Bearer hk_…, y con eso la API sabe quién eres.

La llave es también tu espacio: solo ve tus números, tus conversaciones y tus archivos, y nadie más ve los tuyos. Con una sola llave puedes conectar varios números de WhatsApp. Te la entrega Zamir; si se pierde o se filtra, se cambia por otra y la anterior deja de servir.

De tu sistema hacia WhatsApp

Tu página → tu servidor → la API → WhatsApp. Tu servidor guarda la llave y hace las llamadas: crear la conexión, pedir el QR para pintarlo en tu pantalla, enviar mensajes.

De WhatsApp hacia tu sistema

WhatsApp → la API → tu webhook. Cada mensaje que le escriben al número te llega a una dirección tuya (el webhook) en menos de un segundo. No tienes que estar preguntando.

Es una API HTTP normal: sirve desde cualquier lenguaje (Node, Python, PHP, .NET…) y no hay nada que instalar. Tu página puede tener su propia pantalla de «Conectar WhatsApp», su propio chat y sus propias reglas; la API solo lleva y trae los mensajes.

Solo en tu servidor.Nunca en el navegador, en una app ni en un repositorio. Quien la tenga puede leer y escribir por tu WhatsApp.

Todo es JSON en los dos sentidos, y las fechas van en ISO 8601.

Conectar un número con QR

  1. Crea la conexión

    POST/api/sessions
    curl
    curl -X POST /api/sessions \
      -H "Authorization: Bearer hk_…" -H "Content-Type: application/json" \
      -d '{
        "nombre": "Asistente principal",
        "webhook_url": "https://tu-sistema.com/whatsapp",
        "webhook_token": "un-secreto-que-inventas-tu",
        "capturar": "todo"
      }'

    Responde 201 con session_id y vincular_url. Con capturar: "todo", en cuanto escaneen te llegan todas las conversaciones al webhook, sin más pasos. El webhook_token te lo devolvemos en cada entrega como Bearer, para que sepas que viene de aquí.

    Tu webhook_url tiene que abrirse desde internet. localhost no sirve; un túnel temporal sirve para probar, pero cuando se cae dejan de llegarte mensajes.

  2. Muestra el QR

    Lo más rápido

    Abre el vincular_url. Es una pantalla lista, sin marca, que pinta el QR y se actualiza sola. Se le puede mandar a quien tenga el teléfono.

    En tu propia página

    GET /api/sessions/:id/qr devuelve qr_image, un PNG listo para <img src>. Caduca cada ~20 s: vuelve a pedirlo cada 2 s y repinta hasta que estado sea "conectada".

    javascript · tu página
    // Llama a TU servidor, que es quien tiene la llave y le pregunta a la API.
    async function pintarQr(img) {
      const r = await fetch('/mi-servidor/whatsapp/qr').then((x) => x.json());
      if (r.estado === 'conectada') return mostrarListo(r.numero);
      if (r.qr_image) img.src = r.qr_image;
      setTimeout(() => pintarQr(img), 2000);
    }
  3. Escanea

    En el teléfono del número que va a contestar: WhatsApp → Dispositivos vinculados → Vincular un dispositivo.

  4. Comprueba

    GET /api/sessions/:idestado: "conectada". Ya entra y sale WhatsApp por tu sistema.

    No fijes el session_id a mano para siempre: al arrancar, pide GET /api/sessions y usa la que venga conectada.

Si nadie escanea, o la desvinculan desde el teléfono, no crees otra.A la hora sin escanear la conexión queda archivada; pedir otra vez su QR la revive. Si la cerraron desde el teléfono, POST /api/sessions/:id/relink da un QR nuevo. En los dos casos conservas el mismo id y el historial.

Recibir mensajes

Por cada mensaje que le llega al número hacemos un POST a tu webhook:

lo que te llega
{
  "evento": "mensaje",
  "session_id": "…",
  "chat": { "id": "151883633164487@lid", "nombre": "Camila", "es_grupo": false },
  "de":   { "nombre": "Camila", "numero": "573005550104", "yo": false },
  "mensaje": {
    "id": "3EB0C05CEC9E064DA2B280",
    "tipo": "texto",
    "texto": "Hola, vengo de parte de Daniel",
    "fecha": "2026-09-17T15:10:04.000Z",
    "responde_a": null
  },
  "archivo": null
}
  • Contesta 200 de inmediato y procesa después. Si fallas o tardas, reintentamos con espera creciente; si contestas 4xx entendemos que lo rechazaste y no se reintenta.
  • La cabecera X-Entrega-Id es la misma en todos los reintentos: si la ves dos veces es el mismo mensaje, no lo proceses dos veces. X-Entrega-Edad-Seg dice cuánto lleva esperando.
  • de.numero es el teléfono real: úsalo para saber quién es. chat.id es a donde se contesta (suele ser un identificador …@lid, no un teléfono).
  • Las notas de voz y los archivos llegan con archivo: { url, mimetype, tamano }: pide esa url con tu llave y te llegan los bytes, por ejemplo para transcribir. Se guardan 90 días; el historial de mensajes, un año.
  • También llegan eventos "estado" (ver abajo) y "conexion" (el número se cayó, volvió o lo desvincularon). Contéstalos con 200 aunque no los uses.
node · un webhook mínimo
const express = require('express');
const app = express();
const vistos = new Set();

app.post('/whatsapp', express.json({ limit: '1mb' }), (req, res) => {
  if (req.headers.authorization !== 'Bearer ' + process.env.WEBHOOK_TOKEN) return res.sendStatus(401);
  res.sendStatus(200);                                // primero contestar

  const entrega = req.headers['x-entrega-id'];
  if (vistos.has(entrega)) return;                    // un reintento: ya lo tengo
  vistos.add(entrega);

  const e = req.body;
  if (e.evento === 'mensaje' && !e.de.yo) atender(e);  // despues, con calma
  if (e.evento === 'estado') marcarChulos(e.mensaje.wa_id, e.mensaje.estado);
});

¿No puedes exponer una URL? Lo mismo se lee por consulta: GET /api/sessions/:id/conversaciones y GET /api/sessions/:id/mensajes?chat_id=… (hasta 200 por página; antes=<id> para ir hacia atrás).

Enviar

POST/api/sessions/:id/send
node
async function enviar(sessionId, cuerpo) {
  const r = await fetch(`${API}/api/sessions/${sessionId}/send`, {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.WHATSAPP_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify(cuerpo),
  });
  return r.json();   // { ok, mensaje: { wa_id, chat_id, estado } }
}

await enviar(id, { para: chatId, texto: 'Listo, ¿qué duda tienes?' });
await enviar(id, { para: chatId, texto: '¿Ya quedó esto?', citar: waIdOriginal });
await enviar(id, { para: chatId, tipo: 'audio',
  archivo: { url: 'https://tu-sistema.com/respuesta.ogg', ptt: true } });
  • para: el chat.id que te llegó (recomendado) o un teléfono con indicativo.
  • citar: el wa_id de un mensaje, para responder sobre él.
  • tipo: imagen, video, audio, documento, sticker, ubicacion. El archivo va como url o base64, hasta 16 MB. Con ptt: true el audio sale como nota de voz, con su onda.
  • Guarda el wa_id que te devuelve: es la llave para saber si lo leyeron y qué te contestaron.

La API atiende como una persona: muestra «escribiendo…» (o «grabando…») y tarda lo que se tarda en escribir eso, entre 1 y 3 segundos. Por eso la llamada no vuelve al instante; no hace falta simularlo de tu lado.

¿Lo leyó?

Cada mensaje que envías tiene un estado que avanza solo, igual que los chulos de WhatsApp:

enviadoWhatsApp lo aceptó
✓✓entregadollegó al teléfono
✓✓leidolo abrió
✓✓reproducidoescuchó la nota de voz

Cuando avanza, te llega al webhook un evento "estado" con el wa_id:

lo que te llega
{ "evento": "estado", "chat": { "id": "…" },
  "mensaje": { "wa_id": "3EB0F48F5E6D535A061AC2", "estado": "leido",
               "entregado_at": "2026-09-17T15:11:10.000Z", "leido_at": "2026-09-17T15:13:42.000Z" } }

Quédate siempre con el más avanzado y nunca retrocedas. También se puede consultar: GET /api/sessions/:id/mensajes?wa_id=…. Si la persona tiene apagadas las confirmaciones de lectura, nunca llega leido: te quedas en entregado, igual que le pasa a cualquiera con el teléfono. En un grupo, leido significa que al menos una persona lo leyó.

A qué mensaje responde

Si alguien usa Responder sobre un mensaje tuyo, el suyo llega con responde_a:

dentro de "mensaje"
"responde_a": {
  "wa_id": "3EB0F48F5E6D535A061AC2",   // el wa_id que guardaste al enviar
  "texto": "Camila quiere preguntarle a tu asistente. ¿La admites?",
  "es_reaccion": false,               // true = puso un emoji encima (👍)
  "encontrado": true
}

Así se empareja sin adivinar: envías, guardas el wa_id, y cuando llegue una respuesta con ese mismo wa_id sabes exactamente a qué contestaron, aunque haya cinco preguntas abiertas a la vez.

El enlace solo existe si la persona usa Responder. Un «sí» escrito suelto no está unido a nada: si hay una sola cosa pendiente, úsala; si hay varias, pregunta cuál.

Patrón: acceso con aprobación

Un asistente que habla en nombre de alguien, y al que solo entra quien esa persona aprueba. La API lleva y trae los mensajes; quién es quién, las aprobaciones y las tareas viven en tu sistema.

  1. El dueño activa su asistente, una vez

    Daniel le escribe al número desde su WhatsApp: «Activar DZ-7F3K», un código que le da tu plataforma. Guardas número de Daniel ↔ su asistente ↔ su chat.id.

    No es opcional: WhatsApp bloquea los números que le escriben primero a quien nunca les escribió. Como Daniel escribió primero, después puedes pedirle aprobaciones sin riesgo.

  2. Llega alguien nuevo

    Dale a Daniel un enlace para compartir que ya trae el texto escrito. Camila lo toca y envía:

    enlace
    https://wa.me/57XXXXXXXXXX?text=Hola%2C%20vengo%20de%20parte%20de%20DZ-7F3K

    Lees el código del texto. Si no trae código, pregúntale de parte de quién viene.

  3. Pides la aprobación

    Le envías a Daniel: «Camila (+57 321…) quiere preguntarle a tu asistente. ¿La admites? Responde sí o no sobre este mensaje». Guardas el wa_id junto a la solicitud de Camila. A ella: «Le avisé a Daniel; en cuanto apruebe te escribo».

  4. Daniel responde

    Su «sí» llega con responde_a.wa_id igual al que guardaste: sabes exactamente a quién aprobó. Un 👍 sobre el mensaje también llega, con es_reaccion: true.

  5. Conversación

    Cada pregunta de Camila → tu motor → /send con la respuesta. Si manda nota de voz: descargas el audio, transcribes, respondes (en texto o en nota de voz).

  6. Tareas y seguimiento

    Si la respuesta crea un compromiso («te lo entrego el 10»), lo guardas y programas el seguimiento. El «¿ya quedó lo que hablamos?» es un /send normal citando el mensaje original: como esa persona ya te escribió, es seguro, y si contesta sobre él, responde_a te dice de qué tarea habla.

Costo y seguridad.Nadie consulta sin el «sí» del dueño. Añade un tope de preguntas por persona y por día, y que el dueño pueda revocar («quitar a Camila»).

Cuidar el número

Todo esto evita una sola cosa: que WhatsApp tome el número por un robot o un emisor masivo y lo bloquee. Un número bloqueado no se recupera.

No escribas primero

A quien nunca te escribió no se le escribe: la API lo vigila y puede rechazarlo con 403. Para invitar gente, que sea ella quien escriba (enlace wa.me).

Sin prisa

Máximo 20 mensajes por minuto y 60 destinatarios nuevos por hora, por número. Al pasarte llega un 429 con Retry-After: espera y sigue.

Nada de difusiones

El mismo texto a muchos números es justo lo que WhatsApp persigue. Y mejor una o dos respuestas que seis seguidas.

Para crecer: el canal oficial

Un número público atendiendo a mucha gente debe ir por la API oficial, con aprobación de Meta. La misma llamada /send sale por ahí cuando esté configurado; pasadas 24 h sin respuesta, ese canal solo permite plantillas aprobadas.

Errores y estados

CódigoQué pasaQué hacer
400Falta un campo o va mal.El error dice cuál.
401La llave no vale.Revisa Authorization: Bearer hk_….
403No se le puede escribir a ese chat.Mira codigo: casi siempre es que nunca te escribió.
404Esa conexión no existe para tu llave.GET /api/sessions.
409El número no está conectado ahora.Mira su estado.
429Vas muy rápido.Espera lo que diga Retry-After.
504No se sabe si salió.No reintentes a ciegas: GET …/mensajes?wa_id=<mensaje_id>; si aparece, salió.
EstadoQué significa
conectadaFuncionando.
desconectadaSe cayó y se está reconectando sola. Es normal que pase unos segundos varias veces al día.
qr · esperando_qrEsperando a que alguien escanee.
deslogueadaLa cerraron desde el teléfono: POST …/relink y escanear de nuevo.
archivadaNadie la atendió a tiempo. Pedir su QR la revive, con el mismo id.
bloqueadaWhatsApp bloqueó el número. No se arregla con otro QR.

Antes de salir en vivo

  • La llave hk_… está solo en el servidor (si se filtró, pídele otra a Zamir).
  • El webhook contesta 200 de inmediato a todo (mensaje, estado, conexion) y procesa después.
  • Se descartan duplicados por X-Entrega-Id o por mensaje.id.
  • Se identifica a la persona por de.numero y se contesta a chat.id.
  • Se guarda el wa_id de cada envío.
  • Quien va a recibir mensajes tuyos te escribió primero.
  • Se manejan el 429 (esperar) y el 504 (consultar antes de reintentar).