Developers
Recibir los mensajes de un chat en tu servidor, paso a paso
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.