Cómo descargar fotos y audios que te mandan por la API de WhatsApp
El webhook llega antes de que el archivo esté guardado. Después, GET /messages/:id trae mediaKey y GET /media/by-key devuelve una URL firmada. mediaId en ese GET queda null.
Respuesta corta: la foto o el audio que manda el cliente no vienen en el webhook. message.received sale cuando el mensaje se guarda, y en ese momento mediaId y mediaKey suelen ser null. No hay un webhook público de “archivo listo”. Unos segundos después, GET https://api.waspytech.com/api/v2/messages/:id trae mediaKey. Con esa clave pedís GET /api/v2/media/by-key (scope media:read) y bajás la URL firmada antes de que venza. El default es 900 segundos. GET /media/:id es para cuando ya tenés el UUID de Waspy: en el GET del mensaje, mediaId queda null.
Es el comprobante de transferencia, el audio de “¿tienen el talle M?” o el PDF que te reenvían. Mandar vos una foto es otro pedido, y está en enviar foto, PDF y audio. La referencia de la bajada está en media.
El orden real
- Llega
message.received. Sitypeesimage,video,audio,documentosticker, Waspy encola la bajada desde Meta. El webhook ya salió. - Esperá un par de segundos y leé
GET /messages/:idcon scopemessages:read. - Cuando
mediaKeyes un string (tenantId/uuid.ext), pedí la URL. - Descargá esa URL antes de
expiresIn. Si la necesitás de nuevo, pedí otra.
curl -sS -G "https://api.waspytech.com/api/v2/media/by-key" \
--data-urlencode "key=TENANT_ID/UUID.jpg" \
-H "Authorization: Bearer $WASPY_API_KEY"
{
"data": {
"id": "uuid-de-waspy",
"url": "https://storage.googleapis.com/...",
"expiresIn": 900,
"mimeType": "image/jpeg",
"fileName": null
},
"meta": { "requestId": "..." }
}
La url es de lectura, corta. No la publiques ni la guardes en un log que después se comparte. expiresIn es lo que vale para esa respuesta. El default del servidor es 900. El ejemplo de la documentación de media muestra 3600: no lo hardcodees.
Tres identificadores distintos
| Campo | Qué es | Para qué sirve |
|---|---|---|
content.id en el primer webhook | Id de media de Meta | No es el parámetro de GET /media/:id |
mediaKey | Clave del archivo en el almacenamiento, con el prefijo de tu cuenta | GET /media/by-key?key= |
data.id de by-key, o un mediaId que ya tengas | UUID de Waspy | GET /media/:id |
GET /media/:id exige un UUID. Si le pasás el content.id de Meta, la respuesta es 400 VALIDATION_ERROR.
En el GET del mensaje, el armado de la respuesta deja mediaId en null y copia mediaKey desde el contenido, cuando el worker ya lo escribió. El webhook sí intenta completar mediaId, pero lo hace en el mismo momento del alta, antes de que exista el archivo. Por eso el primer aviso llega vacío y releer el mensaje no llena mediaId.
La página de media todavía dice que, si mediaId viene null, esperes y releas el mensaje hasta que aparezca. Hoy el campo que aparece es mediaKey.
La URL firmada
curl -sS "https://api.waspytech.com/api/v2/media/UUID-DE-WASPY" \
-H "Authorization: Bearer $WASPY_API_KEY"
Hace falta media:read. Un id de otra cuenta, o una clave que no empieza con el id de tu cuenta, responde 404 MEDIA_NOT_FOUND. No es un 403 que nombre al otro tenant.
En by-key, si el archivo no está, también es 404. Si la fila de media no existe pero el objeto sí, id puede venir null y la url igual sirve. En una bajada normal de un mensaje entrante, id viene con el UUID.
Qué tipos se bajan
El worker baja image, video, audio, document y sticker cuando el mensaje trae un id de media de Meta. Un texto, una ubicación o un contacto no tienen archivo.
En un audio, transcriptionStatus llega pending. El texto aparece después en transcription, cuando el estado pasa a completed (empty si no había habla, failed si no se pudo). Eso es el texto para tu sistema. Waspy no le contesta solo al cliente. El alta del webhook está en webhooks y el catálogo, en eventos.
Cada cuánto releer el mensaje
La v2 permite 120 pedidos por minuto por cuenta, compartidos por todas las claves. No hagas un loop cerrado contra GET /messages/:id. Un par de segundos entre lecturas alcanza: la bajada tiene que salir a Meta y volver. Si a los pocos intentos mediaKey sigue null, frenate. Los headers y el 429 RATE_LIMITED están en rate limits.
Esto no sube archivos
Las limitaciones dicen que el upload de la API v2 no está expuesto. Para mandar una foto, un PDF o un audio, POST /messages lleva link (https público) o id (un media id de Meta, no el UUID de Waspy ni el mediaKey). El pedido y los topes de tamaño están en enviar foto, PDF y audio.
Qué cobra Meta por mirar ese archivo
Bajarlo a tu servidor no es un mensaje de Meta. Lo que cobra es lo que vos respondés. Si el cliente acaba de escribir, la respuesta es un mensaje de servicio: 1.000 por número y por mes sin cargo y, desde el siguiente, en Argentina alrededor de ARS 37,68 (~US$0,026). La rate card es la fuente. El desglose está en cuánto cuesta la API.
Waspy no suma un cargo por el archivo. La clave de API está en Scale (US$150/mes) y Enterprise. La suscripción va aparte, desde US$70, en Planes.
En resumen
- El webhook avisa el mensaje. El archivo llega unos segundos después, en
mediaKey. GET /media/by-keydevuelve la URL firmada.expiresInmanda (default 900).GET /media/:idpide el UUID de Waspy. El id de Meta no sirve ahí.- No hay
POST /media.
Si el número ya está en la API, la clave se crea en Configuración → Claves de API. Si todavía no, abrí la cuenta: 10 días, sin tarjeta. El mapa del recurso está en la API.
