8.3 KiB
whatsapp-bridge — referencia rápida de endpoints
Base URL: https://whatsapp.nucleoriofrio.com
Auth: Authorization: Bearer $WA_BRIDGE_TOKEN (excepto /healthz)
Diagnóstico
| Método | Ruta | Respuesta |
|---|---|---|
| GET | /healthz |
{ ok: true, ts } |
| GET | /api/auth/status |
{ status, hasSession, user, pairingCode, lastError } |
status ∈ idle | connecting | open | closed | needs-pair.
Auth / pairing
| Método | Ruta | Body / Headers |
|---|---|---|
| POST | /api/auth/pair |
— → { code, hint } |
| POST | /api/auth/logout |
Header X-Confirm-Wipe: yes (destructivo) |
Chats
GET /api/chats
?type=group|individual|all (default all)
&q=<texto> (busca en name + jid, ILIKE)
&limit=<1..500> (default 100)
&offset=<int> (default 0)
Respuesta:
{
"count": N,
"chats": [
{ "jid": "...@g.us", "isGroup": true, "name": "...", "lastMessageAt": "ISO", "participants": 42 }
]
}
Grupo específico
GET /api/groups/:jid (URL-encode el jid, termina en @g.us)
Respuesta: { jid, stored: {…}, live: GroupMetadata | null }.
GET /api/groups/:jid/messages
?since=<ISO> (timestamp >=)
&until=<ISO> (timestamp <=)
&q=<texto> (ILIKE en content)
&sender=<jid> (filtra por participant)
&order=asc|desc (default desc)
&limit=<1..1000> (default 100)
&offset=<int>
Respuesta:
{
"jid": "...@g.us",
"count": N,
"filters": { ... },
"messages": [
{
"msgId": "...",
"fromMe": false,
"senderJid": "...@s.whatsapp.net",
"pushName": "Don Fulano",
"messageType": "extendedTextMessage",
"content": "texto extraído",
"contextInfo": { "quotedMsgId": "...", "mentionedJid": ["..."] } | null,
"timestamp": "ISO",
"media": null | {
"type": "image"|"video"|"audio"|"document"|"sticker",
"mimetype": "image/jpeg",
"size": 12345,
"fileName": "... (solo document)",
"caption": "... (image/video/document)",
"durationSec": 30 (video/audio),
"ptt": true (solo audio = voice note),
"width": 800 (image/video/sticker),
"height": 600,
"pages": 5 (solo document),
"isAnimated": true (solo sticker),
"downloadUrl": "/api/messages/media/<enc-chatJid>/<msgId>"
}
}
]
}
El campo media aparece en cada mensaje que contiene archivo; downloadUrl es la ruta relativa al endpoint que descarga + desencripta.
Mensajes individuales
GET /api/messages/individual/:jid (termina en @s.whatsapp.net)
?since=&until=&q=&order=&limit=&offset=
Búsqueda cross-chat
GET /api/messages/search
?q=<texto> (requerido, min 1 char, ILIKE)
&since=<ISO>
&until=<ISO>
&chatType=group|individual|all (default all)
&limit=<1..500> (default 100)
Respuesta:
{
"query": "quintal",
"count": N,
"matches": [
{ "chatJid": "...", "msgId": "...", "senderJid": "...", "pushName": "...", "isGroup": true, "content": "...", "timestamp": "ISO" }
]
}
Envío de texto (gated por ENABLE_SEND)
POST /api/messages/send
Content-Type: application/json
{
"jid": "...@g.us" | "...@s.whatsapp.net" | "...@lid",
"text": "...",
"quotedChatJid": "...", // opcional; default = mismo jid destino
"quotedMsgId": "...", // opcional; responder a mensaje previo
"mentions": ["jid1", ...] // opcional; notifica a esos JIDs
}
Respuesta: { ok: true, msgId: "..." } o 403 si ENABLE_SEND=false.
Reenvío (forward)
POST /api/messages/forward
{
"sourceChatJid": "...", // chat donde está el mensaje original
"sourceMsgId": "...", // id del mensaje
"destJid": "..." // destino
}
Requiere que el mensaje esté en la DB del bridge (escuchado mientras el socket estaba vivo).
Reacción (emoji)
POST /api/messages/react
{
"chatJid": "...",
"msgId": "...",
"emoji": "👍" // vacío "" = quitar reacción
}
Envío de multimedia
POST /api/messages/send-media
{
"jid": "...",
"url": "https://...", // URL pública del archivo
"type": "image" | "video" | "audio" | "document",
"caption": "...", // opcional (solo image/video/document)
"fileName": "...", // opcional (solo document)
"mimetype": "...", // opcional (override del content-type)
"ptt": false, // solo audio: true = voice note
"quotedChatJid": "...", // opcional
"quotedMsgId": "..." // opcional
}
Límites y guards:
- URL debe ser HTTP/HTTPS pública — localhost, 127.0.0.1, 10.x, 192.168.x, 172.16-31.x son bloqueadas (SSRF protection).
- Tamaño máximo ~100 MB (aunque WhatsApp limita más bajo según tipo).
- Timeout del fetch: 60s.
Envío de ubicación
POST /api/messages/send-location
{
"jid": "...",
"latitude": 14.123,
"longitude": -88.456,
"name": "...", // opcional (ej: "Beneficio Rio Frio")
"address": "...", // opcional (ej: "Santa Bárbara, Honduras")
"quotedChatJid": "...",
"quotedMsgId": "..."
}
Envío de encuesta (poll)
POST /api/messages/send-poll
{
"jid": "...",
"name": "Pregunta",
"values": ["Opción A", "Opción B", "Opción C"], // 2-12 opciones
"selectableCount": 1, // 1=single (default), 0=multi-choice, N=hasta N
"quotedChatJid": "...",
"quotedMsgId": "..."
}
Envío de contacto(s) (vCard)
POST /api/messages/send-contact
{
"jid": "...",
"contacts": [
{
"displayName": "Don Fulano",
"firstName": "Fulano", // opcional
"lastName": "Pérez", // opcional
"phone": "50499999999", // opcional, E.164 sin + → vinculable a WA
"phoneDisplay": "+504 9999-9999", // opcional, default = "+" + phone
"email": "...", // opcional
"organization": "...", // opcional
"title": "...", // opcional
"vcard": "BEGIN:VCARD..." // opcional, override total
}
],
"quotedChatJid": "...",
"quotedMsgId": "..."
}
Hasta 20 contactos por mensaje. Si phone está en formato E.164 (sin +), WhatsApp lo detecta y permite iniciar chat con un tap.
Descarga de media (desencriptado server-side)
Los mensajes con media incluyen media.downloadUrl en las respuestas. Ese path
apunta a un endpoint que, al llamarlo con el bearer, descarga el archivo del
CDN de WhatsApp, lo desencripta con la mediaKey del mensaje, y devuelve el
binario listo para usar.
GET /api/messages/media/:chatJid/:msgId[?disposition=inline|attachment]
Headers de respuesta:
Content-Type: MIME correcto (image/jpeg, video/mp4, etc.)Content-Length: tamaño en bytesContent-Disposition:inline(default) oattachmentX-Wa-Media-Type:image|video|audio|document|sticker
Solo metadata (sin bajar el binario):
GET /api/messages/media-info/:chatJid/:msgId
→ { chatJid, msgId, media: { type, mimetype, size, fileName, caption, ..., downloadUrl } }
Errores:
- 404 si el mensaje no existe o no tiene media
- 503 si el socket de WhatsApp está caído
- 500 si el CDN de WA ya no tiene el archivo (muy viejo → no se puede recuperar)
Eventos (no soportado todavía)
Baileys 6.17 no expone una API estable para crear event messages (los puede recibir/parsear pero no enviar via sendMessage). Sería necesario construir proto.Message.IEventMessage manualmente y usar relayMessage low-level — todavía no implementado en el bridge. Si lo necesitás urgente, abrir issue.
Errores típicos: 400 con fetch failed: ... si la URL no responde bien o es privada.
Formatos de JID
| JID | Tipo | Ejemplo |
|---|---|---|
<phone>@s.whatsapp.net |
chat individual | 50499999999@s.whatsapp.net |
<phone>-<timestamp>@g.us |
grupo | 50499999999-1614182736@g.us |
status@broadcast |
estados | (ignorado por el bridge) |
URL-encoding en path params: @ → %40, . se deja tal cual.
Códigos de error
| Status | Significado |
|---|---|
| 400 | parámetros inválidos (ver body con .error) |
| 401 | bearer ausente o inválido |
| 403 | endpoint gated (ej send cuando ENABLE_SEND=false) |
| 404 | chat/grupo no existe en storage |
| 500 | error interno (revisar logs del contenedor) |
| 503 | socket no listo (status != open) |