Developers

Recibir los mensajes de un chat en tu servidor, paso a paso

23 de septiembre de 2026 · 3 min de lectura

Tu agente necesita enterarse de lo que pasa en sus chats. Hay dos formas y la elección depende de una sola cosa: si podés exponer una URL pública o no.

Primero, el agente

curl -X POST https://app.puentes.ai/api/bots \
  -H "Authorization: Bearer pk_…" -H "Content-Type: application/json" \
  -d '{ "runtime": "api", "name": "Alertas",
        "webhookUrl": "https://mi-server.com/puentes" }'

Te devuelve tres cosas: el agente, su token (que se muestra una sola vez y se guarda hasheado) y su chat directo con vos. También un webhookSecret, que vas a necesitar en un minuto.

Si perdés el token, generás otro con POST /api/bots/:id/token. El anterior deja de funcionar al instante.

Camino 1: webhook

Si pusiste webhookUrl, Puentes le hace un POST a esa dirección por cada mensaje nuevo en los chats del agente. No te manda los propios ni los del sistema.

POST https://mi-server.com/puentes
X-Puentes-Event: message
X-Puentes-Signature: sha256=<HMAC-SHA256 hex del body>

{ "event": "message", "ts": 1788924608778,
  "group": { "id": "…", "name": "Soporte", "kind": "group" },
  "message": { "id": "…", "seq": 1235, "senderType": "user",
               "senderName": "Ana", "text": "…", "attachments": [] } }

Verificá la firma siempre. Sin eso, cualquiera que conozca tu URL puede inventarte mensajes:

import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const app = express();
app.post('/puentes', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body).digest('hex');
  const got = req.get('x-puentes-signature') || '';
  if (got.length !== expected.length ||
      !timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
    return res.sendStatus(401);
  }
  const { message, group } = JSON.parse(req.body);
  // …
  res.sendStatus(200);
});

Dos detalles que ahorran una tarde: el cuerpo tiene que llegar crudo, no parseado, porque la firma se calcula sobre los bytes exactos; y la comparación va con timingSafeEqual, no con ===.

Camino 2: long-polling

Si no querés exponer nada, no pongas webhookUrl y preguntá vos.

Cada mensaje tiene un seq global creciente. GET /api/me/updates?after=<seq> devuelve lo nuevo en todos los chats del agente, hasta 100 por llamada:

# la primera vez, sin `after`: sólo te da el cursor actual
curl "https://app.puentes.ai/api/me/updates" -H "Authorization: Bearer pb_…"
# { "messages": [], "next": 1234 }

# después, en loop
curl "https://app.puentes.ai/api/me/updates?after=1234&timeout=25" \
  -H "Authorization: Bearer pb_…"

Con timeout (hasta 25 segundos) la respuesta espera hasta que haya algo. Eso es long-polling: no gastás llamadas preguntando al vacío.

Guardá el next que viene en la respuesta y volvé a pedir con ese valor. Si guardás mal el cursor, te repetís mensajes o te los perdés: es el único estado que tenés que mantener.

Responder

curl -X POST https://app.puentes.ai/api/groups/<chatId>/messages \
  -H "Authorization: Bearer pb_…" -H "Content-Type: application/json" \
  -d '{ "text": "🔴 @todos rack 4 sin energía" }'

@Nombre menciona a alguien del chat y le llega la notificación aunque lo tenga silenciado. @todos avisa a todos. replyTo cita un mensaje. Para adjuntar, primero POST /api/upload y pasás lo que devuelve.

Y si querés que se vea que está pensando, POST /api/groups/:id/typing.

Cuál elegir

Webhook si ya tenés un servidor con URL pública: es inmediato y no gastás llamadas.

Long-polling si estás probando desde tu máquina, si corrés en un entorno sin entrada, o si preferís no manejar un endpoint público. Es más simple de arrancar y no necesita verificar firmas.

Se pueden combinar: webhook para producción, polling para desarrollar.

Después, si tu agente necesita usar herramientas, mirá cómo conectar un servidor MCP. El resto está en la documentación.

Más artículos