← Blog

·

Cómo recibir y descargar los archivos que el cliente envía por la API de WhatsApp

Actualizado el 28/10/2026

Cuando el cliente envía una foto, un PDF, un audio o un video, el webhook de la API de WhatsApp no trae el archivo: trae un media id. Para descargarlo, haces GET /{media-id} para obtener una URL temporal, que expira en 5 minutos, y luego un GET a esa URL con tu token de acceso en el encabezado Authorization. Como el media id recibido por webhook vale solo 7 días, lo correcto es descargarlo enseguida y guardar el archivo en tu propio almacenamiento.

Esta guía muestra el flujo completo, los campos de cada tipo de archivo, los límites de tamaño, cómo verificar la integridad y ejemplos en cURL, Node.js y Python, incluido el envío a Amazon S3.

El flujo en 4 pasos

  1. Webhook: llega un mensaje con type igual a image, document, audio, video o sticker, con el id del archivo.
  2. URL temporal: GET https://graph.facebook.com/v26.0/{media-id} devuelve url, mime_type, sha256 y file_size.
  3. Descarga: GET a la url con Authorization: Bearer <token>. Sin el token, la solicitud falla.
  4. Almacenamiento: guarda el archivo en tu storage (S3, GCS, disco) y registra en la base de datos su ruta, nunca la URL de Meta.

Los plazos vienen de la documentación de multimedia de Meta: las URL expiran en 5 minutos (después basta con consultar el media id otra vez para generar una nueva), los media id que llegan por webhook expiran en 7 días y los generados por carga (upload) expiran en 30 días.

Qué llega en el webhook

Este es el formato de una imagen recibida, según la referencia del webhook de imágenes (valores de ejemplo):

{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "WABA_ID",
    "changes": [{
      "field": "messages",
      "value": {
        "messaging_product": "whatsapp",
        "metadata": {
          "display_phone_number": "5215512345678",
          "phone_number_id": "PHONE_NUMBER_ID"
        },
        "contacts": [{ "profile": { "name": "María" }, "wa_id": "5215598765432" }],
        "messages": [{
          "from": "5215598765432",
          "id": "wamid.HBgNNTIx...",
          "timestamp": "1761650000",
          "type": "image",
          "image": {
            "caption": "Comprobante",
            "mime_type": "image/jpeg",
            "sha256": "SfInY0gGKTsJlUWbwxC1k+FAD0FZHvzwfpvO0zX0GUI=",
            "id": "1003383421387256",
            "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=..."
          }
        }]
      }
    }]
  }]
}

El objeto cambia de nombre según el tipo (image, document, audio, video, sticker), pero la estructura es parecida. Los documentos también traen filename, útil para guardar con el nombre original.

Fíjate en el campo url. Meta empezó a habilitarlo en el propio webhook de forma gradual desde el 12/11/2025, lo que permite saltar el paso 2 y descargar directamente con el token. Como la habilitación es gradual, tu código debe funcionar con y sin él: usa la url si viene y, si no viene o la descarga falla, consulta el media id.

CampoPara qué sirve
idMedia id; úsalo para generar la URL de descarga
mime_typeTipo de archivo (image/jpeg, application/pdf, audio/ogg…)
sha256Hash del archivo, para verificar integridad
captionTexto que envió el cliente con el archivo, si lo hay
filenameNombre original (documentos)
urlURL de descarga directa, en habilitación gradual

¿Quieres probar mientras lees? Crea tu cuenta en Vectalk y envía el primer mensaje por la API oficial. Crear cuenta gratis →

Tipos y límites de tamaño

La tabla resume los tipos admitidos y los tamaños máximos de la página de multimedia de Meta. Sirve para dimensionar el almacenamiento y el tiempo límite de descarga:

TipoFormatosTamaño máximo
AudioAAC, AMR, MP3, M4A, OGG (solo códec OPUS, mono)16 MB
DocumentoTXT, XLS, XLSX, DOC, DOCX, PPT, PPTX, PDF100 MB
ImagenJPEG, PNG (8 bits, RGB o RGBA)5 MB
StickerWebP500 KB animado, 100 KB estático
Video3GP, MP4 (H.264 y AAC)16 MB

Las notas de voz llegan como mensajes de tipo audio. Si vas a pasar el audio a un servicio de transcripción, revisa el mime_type recibido y si el servicio acepta ese formato; si no, conviértelo antes.

Ejemplo con cURL

Paso 2, obtener la URL:

curl "https://graph.facebook.com/v26.0/1003383421387256" \
  -H "Authorization: Bearer $TOKEN"

Respuesta:

{
  "messaging_product": "whatsapp",
  "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=...",
  "mime_type": "image/jpeg",
  "sha256": "SfInY0gGKTsJlUWbwxC1k+FAD0FZHvzwfpvO0zX0GUI=",
  "file_size": 303833,
  "id": "1003383421387256"
}

Paso 3, descargar:

curl -L "URL_DEVUELTA" \
  -H "Authorization: Bearer $TOKEN" \
  -o comprobante.jpg

Opcionalmente, envía phone_number_id como parámetro de consulta en el paso 2: así Meta solo procesa la solicitud si el archivo pertenece a ese número, una protección útil cuando la misma aplicación atiende varios números.

Ejemplo en Node.js

import crypto from 'node:crypto';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const GRAPH = 'https://graph.facebook.com/v26.0';
const TOKEN = process.env.WHATSAPP_TOKEN;
const s3 = new S3Client({ region: process.env.AWS_REGION });

async function obtenerUrl(mediaId) {
  const res = await fetch(`${GRAPH}/${mediaId}`, {
    headers: { Authorization: `Bearer ${TOKEN}` },
  });
  if (!res.ok) throw new Error(`media ${mediaId}: HTTP ${res.status}`);
  return res.json(); // { url, mime_type, sha256, file_size, id }
}

async function descargar(url) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
  if (!res.ok) throw Object.assign(new Error('descarga fallida'), { status: res.status });
  return Buffer.from(await res.arrayBuffer());
}

export async function guardarArchivo(media, clave) {
  let info = { url: media.url, sha256: media.sha256, mime_type: media.mime_type };
  let archivo;
  try {
    if (!info.url) throw new Error('sin url en el webhook');
    archivo = await descargar(info.url);
  } catch {
    info = await obtenerUrl(media.id); // genera una URL nueva
    archivo = await descargar(info.url);
  }

  const hash = crypto.createHash('sha256').update(archivo).digest('base64');
  if (info.sha256 && hash !== info.sha256) throw new Error('el hash no coincide');

  await s3.send(new PutObjectCommand({
    Bucket: process.env.BUCKET,
    Key: clave,
    Body: archivo,
    ContentType: info.mime_type,
  }));
  return clave;
}

En los ejemplos de Meta, el sha256 viene en base64; por eso la comparación usa digest('base64'). Si en tu caso ves otro formato, registra ambos valores antes de activar la verificación.

Ejemplo en Python

import base64
import hashlib
import os

import boto3
import httpx

GRAPH = "https://graph.facebook.com/v26.0"
TOKEN = os.environ["WHATSAPP_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
s3 = boto3.client("s3")


def obtener_url(client: httpx.Client, media_id: str) -> dict:
    r = client.get(f"{GRAPH}/{media_id}", headers=HEADERS)
    r.raise_for_status()
    return r.json()


def guardar_archivo(media: dict, clave: str) -> str:
    with httpx.Client(timeout=60, follow_redirects=True) as client:
        info = obtener_url(client, media["id"])
        r = client.get(info["url"], headers=HEADERS)
        if r.status_code == 404:  # URL vencida: genera otra y reintenta
            info = obtener_url(client, media["id"])
            r = client.get(info["url"], headers=HEADERS)
        r.raise_for_status()

    calculado = base64.b64encode(hashlib.sha256(r.content).digest()).decode()
    if info.get("sha256") and calculado != info["sha256"]:
        raise ValueError("el hash no coincide")

    s3.put_object(
        Bucket=os.environ["BUCKET"],
        Key=clave,
        Body=r.content,
        ContentType=info["mime_type"],
    )
    return clave

Para documentos de hasta 100 MB, cargar todo en memoria puede pesar. En ese caso, descarga en stream a un archivo temporal y usa upload_fileobj de boto3, o Upload del paquete @aws-sdk/lib-storage en Node, que suben por partes.

Dónde va esto en la arquitectura

No descargues el archivo dentro de la solicitud del webhook. El endpoint debe validar la firma, encolar el evento y responder 200 rápido; un worker aparte hace la descarga y la carga. Así, un video grande no bloquea el webhook ni provoca reintentos de Meta. Sobre la validación, consulta cómo validar la firma del webhook.

Algunas buenas prácticas:

  • Clave de almacenamiento predecible, por ejemplo whatsapp/{phone_number_id}/{wa_id}/{wamid}.{ext}, para encontrar el archivo a partir del mensaje.
  • Idempotencia: antes de descargar, verifica si ese mensaje ya se procesó; el webhook puede llegar repetido.
  • Bucket privado y enlaces firmados para quien necesite ver el archivo, ya que comprobantes y documentos contienen datos personales. Aplica la ley de protección de datos de tu país (en Brasil, la LGPD, Ley 13.709/2018) en seguridad y tiempo de retención. Esto no sustituye la orientación de un abogado.
  • Reintento con plazo: si la descarga falla, tienes los 7 días de validez del media id para volver a intentarlo.

Errores comunes

404 al descargar. La página de multimedia de Meta indica solicitar una nueva URL y reintentar; si sigue fallando, renueva el token de acceso. En la práctica, la causa más común es usar una URL guardada hace más de 5 minutos.

401 o 403 en la descarga. El GET a la URL se hizo sin el encabezado Authorization, o con un token sin acceso a la cuenta. Usa un token permanente de usuario del sistema; consulta token permanente de la API de WhatsApp.

Error 131052 en el webhook. La tabla de errores de Meta lo define como «no se pudo descargar el archivo enviado por el usuario». Lee el campo error_data.details del webhook y pide al cliente que reenvíe el archivo por otro medio.

Media id vencido. Después de 7 días no hay forma de recuperar el archivo por la API. Por eso la descarga debe ocurrir justo después del webhook.

Cómo hacerlo con Vectalk

En Vectalk, los webhooks son los mismos de Meta, así que el media id llega en el mismo campo. Para multimedia, la API de Vectalk ofrece GET https://apis.vectalk.com.br/api/media/{mediaId}, que devuelve la URL temporal (válida por unos 5 minutos), y GET https://apis.vectalk.com.br/api/media/{mediaId}/download, que devuelve directamente el archivo binario con el Content-Type correcto, eliminando el segundo paso. Ambas rutas usan el token de instancia. Crea tu cuenta en apps.vectalk.com.br/register.

Para el camino inverso, enviar archivos al cliente, consulta cómo enviar multimedia por la API de WhatsApp. Si tu bot recibe muchos archivos, los ejemplos completos están en los tutoriales de bot en Python y bot en Node.js. El panorama general está en nuestra guía de WhatsApp Cloud API.

Preguntas frecuentes

¿Cuánto tiempo es válida la URL del archivo?

5 minutos. Después, consulta el media id otra vez para generar una nueva URL. El media id recibido por webhook vale 7 días.

¿Puedo guardar la URL de Meta en la base de datos y abrirla después?

No. La URL expira en 5 minutos y exige el token para descargar. Guarda el archivo en tu almacenamiento y registra su ruta en la base de datos.

¿Necesito el token para descargar desde la URL?

Sí. La documentación de Meta indica que la solicitud falla si omites el token. Envía Authorization: Bearer <token> tanto al consultar el media id como al descargar.

¿Cuál es el tamaño máximo de un archivo recibido?

Según la tabla de tipos admitidos de Meta, las imágenes llegan hasta 5 MB, audio y video hasta 16 MB y documentos hasta 100 MB. Los stickers tienen límites menores.

¿Cómo sé si el archivo descargado está íntegro?

Calcula el SHA-256 del contenido y compáralo con el sha256 del webhook o de la respuesta del media id. En los ejemplos de Meta, el valor viene en base64.


Fuentes oficiales consultadas el 28/10/2026: Media (Meta), Webhook de mensajes de imagen (Meta), Webhook de mensajes de documento (Meta), Códigos de error (Meta), Multimedia en la API de Vectalk, PutObjectCommand (AWS SDK for JavaScript v3), upload_fileobj (boto3), LGPD, Ley 13.709/2018 (en portugués).

Prueba hoy la API oficial de WhatsApp

Crea tu cuenta, genera el token y envía el primer mensaje con la guía de la documentación. Los mismos payloads y webhooks de la Cloud API de Meta.