Medios
Hay dos formas de enviar un archivo, y la elección cambia bastante el rendimiento.
Por link
Meta descarga el archivo de tu servidor en cada envío.
curl -X POST https://api.joinotify.com/v1/106540352242922/messages \n -H 'Authorization: Bearer sk_live_xxx' \n -H 'Content-Type: application/json' \n -d '{
"messaging_product": "whatsapp",
"to": "5541987111527",
"type": "image",
"image": { "link": "https://exemplo.com/catalogo.png" }
}'
Bueno para un archivo único, generado bajo demanda — una factura, un recibo. La URL debe
ser pública, con el Content-Type correcto y sin redirección a una página de inicio de
sesión.
Por id de medio
Subes el archivo una vez y reutilizas el id.
curl -X POST https://api.joinotify.com/v1/106540352242922/media \
-H 'Authorization: Bearer sk_live_xxx' \
-F 'messaging_product=whatsapp' \
{ "id": "1234567890" }
curl -X POST https://api.joinotify.com/v1/106540352242922/messages \n -H 'Authorization: Bearer sk_live_xxx' \n -H 'Content-Type: application/json' \n -d '{
"messaging_product": "whatsapp",
"to": "5541987111527",
"type": "image",
"image": { "id": "1234567890" }
}'
Para envíos masivos, siempre el id del medio. Con link, mil envíos son mil descargas en tu
servidor. El archivo permanece en Meta 30 días.
La subida tiene su propio límite: 60 peticiones por minuto.
Límites por tipo
| Tipo | Formatos aceptados | Tamaño |
|---|---|---|
| Imagen | JPEG, PNG | 5 MB |
| Vídeo | MP4, 3GPP — H.264 + AAC | 16 MB |
| Audio | AAC, AMR, MP3, MP4 Audio, OGG (solo OPUS) | 16 MB |
| Documento | cualquiera | 100 MB |
| Sticker | WebP | 100 KB estático · 500 KB animado |
Un vídeo con otro códec se acepta en la subida y falla en la entrega, sin error claro. Si un vídeo «desaparece», comprueba el códec antes que nada.
Recibir medios
Cuando el cliente envía un archivo, el webhook trae solo el id — no el contenido:
{
"messages": [
{
"from": "5541999999999",
"id": "wamid.HBgN...",
"type": "image",
"image": { "id": "9876543210", "mime_type": "image/jpeg", "sha256": "..." }
}
]
}
Descargarlo son dos pasos.
curl https://api.joinotify.com/v1/9876543210 \
-H 'Authorization: Bearer sk_live_xxx'
{
"url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=...",
"mime_type": "image/jpeg",
"file_size": 184320,
"id": "9876543210"
}
curl -L 'https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=...' \
-H 'Authorization: Bearer sk_live_xxx' \
-o recibido.jpg
Abrir el enlace en el navegador devuelve error, y ponerlo en un <img src> no funciona.
Solo responde con la cabecera Authorization.
No guardes esa URL en tu base de datos como si fuera permanente: caduca. Descarga el archivo, guárdalo en tu propio almacenamiento y conserva tu URL.
Flujo recomendado al recibir
async function descargarMedio(mediaId) {
const headers = { Authorization: `Bearer ${process.env.JOINOTIFY_TOKEN}` };
const meta = await fetch(`https://api.joinotify.com/v1/${mediaId}`, { headers });
const { url, mime_type } = await meta.json();
const archivo = await fetch(url, { headers });
const buffer = Buffer.from(await archivo.arrayBuffer());
// Guárdalo en tu almacenamiento y devuelve TU url.
return storage.guardar(buffer, { mimeType: mime_type });
}
Hazlo de forma asíncrona, fuera del handler del webhook — tu endpoint debe responder 2xx
en pocos segundos, o la entrega se considera fallida y se reintenta.
Eliminar
curl -X DELETE https://api.joinotify.com/v1/1234567890 \
-H 'Authorization: Bearer sk_live_xxx'
Los mensajes ya entregados con ese archivo siguen funcionando para el cliente. Eliminarlo solo libera el almacenamiento e impide nuevos envíos con ese id.