APIs & Integrações2 de mar. de 2026· 16 min leitura

Como Enviar Imagens, Vídeos e Documentos pela WhatsApp Business API

Guia técnico completo sobre envio de mídia via WhatsApp Cloud API: envio por URL pública, upload prévio com media_id, formatos suportados, limites de tamanho, exemplos em Node.js e Python, e tratamento de erros.

Enviar texto pelo WhatsApp é simples. Enviar mídia — imagens, vídeos, áudios, documentos — exige entender dois fluxos distintos que a WhatsApp Cloud API oferece, cada um com suas vantagens e limitações.

Este guia cobre tudo: formatos e limites por tipo de mídia, quando usar URL direta versus upload prévio, os headers obrigatórios, exemplos de código prontos para produção em Node.js e Python, e como tratar os erros mais comuns. No final, você terá um helper function reutilizável que detecta o tipo de mídia e roteia para o endpoint correto.

Os Dois Fluxos de Envio de Mídia

A Cloud API suporta duas formas de referenciar uma mídia em uma mensagem:

1. Por URL pública — Você passa uma URL acessível publicamente no payload da mensagem. A Meta busca o arquivo nessa URL no momento do envio.

2. Por media_id (upload prévio) — Você faz primeiro um upload do arquivo para os servidores da Meta via POST /media, recebe um media_id, e usa esse ID ao enviar a mensagem.

Quando usar cada fluxo

| Situação | Fluxo recomendado | |---|---| | Arquivo já hospedado em CDN/S3 acessível | URL pública | | Arquivo gerado dinamicamente (PDF de fatura, etc.) | Upload prévio | | Mesmo arquivo enviado para muitos usuários | Upload prévio (reaproveitamento do media_id) | | Arquivo sensível que não pode ficar público | Upload prévio | | Prototipagem e testes rápidos | URL pública |

O media_id tem validade de 30 dias a partir do upload. Depois disso, precisa ser re-enviado.

Formatos e Limites de Tamanho

Antes de qualquer código, é fundamental conhecer os limites da API. Tentar enviar um formato não suportado ou arquivo acima do limite resulta em erro imediato.

Imagens

| Formato | Limite de tamanho | |---|---| | JPEG | 5 MB | | PNG | 5 MB | | WebP | 100 KB (apenas stickers animados) |

Atenção: O limite de imagens via URL é 5 MB. Para o upload prévio, o limite é o mesmo. Redimensione ou comprima as imagens antes do envio para evitar rejeições.

Vídeos

| Formato | Codec de vídeo | Codec de áudio | Limite | |---|---|---|---| | MP4 | H.264 | AAC | 16 MB | | 3GP | H.264 | AAC | 16 MB |

Vídeos com codec diferente de H.264 (como VP9 ou AV1) são rejeitados mesmo que o container seja .mp4. Verifique o codec antes de enviar.

Documentos

| Formato | Limite | |---|---| | PDF | 100 MB | | DOC / DOCX | 100 MB | | XLS / XLSX | 100 MB | | PPT / PPTX | 100 MB | | TXT | 100 MB |

Áudios

| Formato | Limite | |---|---| | AAC | 16 MB | | MP4 (apenas áudio) | 16 MB | | MPEG | 16 MB | | AMR | 16 MB | | OGG (apenas Opus) | 16 MB |

Nota: Áudios em formato OGG com codec Opus são reproduzidos como mensagens de voz dentro do WhatsApp. Os outros formatos aparecem como arquivo de áudio.

Estrutura do Payload de Mensagem com Mídia

Independente do tipo de mídia ou do fluxo usado, a estrutura base do payload é sempre a mesma:

{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "5511999999999",
  "type": "image",
  "image": {
    "link": "https://seusite.com/imagem.jpg",
    "caption": "Legenda opcional da imagem"
  }
}

Para usar media_id em vez de URL:

{
  "messaging_product": "whatsapp",
  "recipient_type": "individual",
  "to": "5511999999999",
  "type": "image",
  "image": {
    "id": "1234567890123456",
    "caption": "Legenda opcional da imagem"
  }
}

O campo que muda é o tipo (image, video, document, audio) e o objeto correspondente com link ou id.

Endpoint de envio

POST https://graph.facebook.com/v21.0/{PHONE_NUMBER_ID}/messages

Autenticação via header Authorization: Bearer {TOKEN}.

Enviando Mídia por URL Pública

Node.js

const axios = require('axios')

const PHONE_NUMBER_ID = process.env.WHATSAPP_PHONE_NUMBER_ID
const TOKEN = process.env.WHATSAPP_TOKEN
const BASE_URL = `https://graph.facebook.com/v21.0/${PHONE_NUMBER_ID}/messages`

async function sendImage(to, imageUrl, caption = '') {
  const response = await axios.post(
    BASE_URL,
    {
      messaging_product: 'whatsapp',
      recipient_type: 'individual',
      to,
      type: 'image',
      image: {
        link: imageUrl,
        ...(caption && { caption }),
      },
    },
    {
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        'Content-Type': 'application/json',
      },
    }
  )
  return response.data
}

async function sendVideo(to, videoUrl, caption = '') {
  const response = await axios.post(
    BASE_URL,
    {
      messaging_product: 'whatsapp',
      recipient_type: 'individual',
      to,
      type: 'video',
      video: {
        link: videoUrl,
        ...(caption && { caption }),
      },
    },
    {
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        'Content-Type': 'application/json',
      },
    }
  )
  return response.data
}

async function sendDocument(to, documentUrl, filename, caption = '') {
  const response = await axios.post(
    BASE_URL,
    {
      messaging_product: 'whatsapp',
      recipient_type: 'individual',
      to,
      type: 'document',
      document: {
        link: documentUrl,
        filename, // Nome que aparece no WhatsApp do destinatário
        ...(caption && { caption }),
      },
    },
    {
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        'Content-Type': 'application/json',
      },
    }
  )
  return response.data
}

// Exemplos de uso
sendImage('5511999999999', 'https://seusite.com/produto.jpg', 'Novo produto disponível!')
sendVideo('5511999999999', 'https://seusite.com/demo.mp4', 'Veja como funciona')
sendDocument('5511999999999', 'https://seusite.com/fatura.pdf', 'fatura-janeiro-2026.pdf', 'Sua fatura de janeiro')

Python

import os
import requests

PHONE_NUMBER_ID = os.environ['WHATSAPP_PHONE_NUMBER_ID']
TOKEN = os.environ['WHATSAPP_TOKEN']
BASE_URL = f'https://graph.facebook.com/v21.0/{PHONE_NUMBER_ID}/messages'

HEADERS = {
    'Authorization': f'Bearer {TOKEN}',
    'Content-Type': 'application/json',
}

def send_image(to: str, image_url: str, caption: str = '') -> dict:
    payload = {
        'messaging_product': 'whatsapp',
        'recipient_type': 'individual',
        'to': to,
        'type': 'image',
        'image': {'link': image_url},
    }
    if caption:
        payload['image']['caption'] = caption

    response = requests.post(BASE_URL, json=payload, headers=HEADERS)
    response.raise_for_status()
    return response.json()

def send_video(to: str, video_url: str, caption: str = '') -> dict:
    payload = {
        'messaging_product': 'whatsapp',
        'recipient_type': 'individual',
        'to': to,
        'type': 'video',
        'video': {'link': video_url},
    }
    if caption:
        payload['video']['caption'] = caption

    response = requests.post(BASE_URL, json=payload, headers=HEADERS)
    response.raise_for_status()
    return response.json()

def send_document(to: str, doc_url: str, filename: str, caption: str = '') -> dict:
    payload = {
        'messaging_product': 'whatsapp',
        'recipient_type': 'individual',
        'to': to,
        'type': 'document',
        'document': {
            'link': doc_url,
            'filename': filename,
        },
    }
    if caption:
        payload['document']['caption'] = caption

    response = requests.post(BASE_URL, json=payload, headers=HEADERS)
    response.raise_for_status()
    return response.json()

# Exemplos de uso
send_image('5511999999999', 'https://seusite.com/produto.jpg', 'Novo produto disponível!')
send_video('5511999999999', 'https://seusite.com/demo.mp4', 'Veja como funciona')
send_document('5511999999999', 'https://seusite.com/fatura.pdf', 'fatura-janeiro-2026.pdf', 'Sua fatura de janeiro')

Upload Prévio de Mídia (POST /media)

Quando você não tem uma URL pública ou quer reutilizar o arquivo em múltiplos envios, o fluxo correto é fazer o upload primeiro e obter um media_id.

Endpoint de upload

POST https://graph.facebook.com/v21.0/{PHONE_NUMBER_ID}/media

Headers obrigatórios

O upload usa multipart/form-data, não JSON. Os campos obrigatórios são:

  • messaging_product: sempre "whatsapp"
  • type: MIME type do arquivo (ex: image/jpeg, video/mp4, application/pdf)
  • file: o arquivo em si

Importante: O Content-Type do header HTTP deve ser multipart/form-data com o boundary correto. Bibliotecas como axios e requests gerenciam isso automaticamente quando você usa FormData.

Node.js — Upload de arquivo

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')
const path = require('path')

const PHONE_NUMBER_ID = process.env.WHATSAPP_PHONE_NUMBER_ID
const TOKEN = process.env.WHATSAPP_TOKEN

const MIME_TYPES = {
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.png': 'image/png',
  '.webp': 'image/webp',
  '.mp4': 'video/mp4',
  '.3gp': 'video/3gpp',
  '.pdf': 'application/pdf',
  '.doc': 'application/msword',
  '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  '.xls': 'application/vnd.ms-excel',
  '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  '.aac': 'audio/aac',
  '.mp3': 'audio/mpeg',
  '.ogg': 'audio/ogg',
}

async function uploadMedia(filePath) {
  const ext = path.extname(filePath).toLowerCase()
  const mimeType = MIME_TYPES[ext]

  if (!mimeType) {
    throw new Error(`Formato não suportado: ${ext}`)
  }

  const form = new FormData()
  form.append('messaging_product', 'whatsapp')
  form.append('type', mimeType)
  form.append('file', fs.createReadStream(filePath), {
    filename: path.basename(filePath),
    contentType: mimeType,
  })

  const response = await axios.post(
    `https://graph.facebook.com/v21.0/${PHONE_NUMBER_ID}/media`,
    form,
    {
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        ...form.getHeaders(),
      },
    }
  )

  return response.data.id // Retorna o media_id
}

async function sendMediaById(to, mediaId, mediaType, options = {}) {
  const typeMap = {
    'image/jpeg': 'image',
    'image/png': 'image',
    'video/mp4': 'video',
    'application/pdf': 'document',
    'audio/ogg': 'audio',
  }

  const whatsappType = typeMap[mediaType] || 'document'

  const payload = {
    messaging_product: 'whatsapp',
    recipient_type: 'individual',
    to,
    type: whatsappType,
    [whatsappType]: {
      id: mediaId,
      ...options,
    },
  }

  const response = await axios.post(
    `https://graph.facebook.com/v21.0/${PHONE_NUMBER_ID}/messages`,
    payload,
    {
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        'Content-Type': 'application/json',
      },
    }
  )

  return response.data
}

// Exemplo: faz upload e envia
async function uploadAndSend(to, filePath, caption = '') {
  const mediaId = await uploadMedia(filePath)
  console.log(`Media uploaded: ${mediaId}`)

  const ext = path.extname(filePath).toLowerCase()
  const mimeType = MIME_TYPES[ext]

  return sendMediaById(to, mediaId, mimeType, {
    ...(caption && { caption }),
    ...(mimeType === 'application/pdf' && { filename: path.basename(filePath) }),
  })
}

// Uso
uploadAndSend('5511999999999', './fatura-jan-2026.pdf', 'Sua fatura de janeiro')

Python — Upload de arquivo

import os
import mimetypes
import requests
from pathlib import Path

PHONE_NUMBER_ID = os.environ['WHATSAPP_PHONE_NUMBER_ID']
TOKEN = os.environ['WHATSAPP_TOKEN']

# mimetypes às vezes não detecta corretamente — mapa manual mais seguro
MIME_MAP = {
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.png': 'image/png',
    '.webp': 'image/webp',
    '.mp4': 'video/mp4',
    '.3gp': 'video/3gpp',
    '.pdf': 'application/pdf',
    '.doc': 'application/msword',
    '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    '.xls': 'application/vnd.ms-excel',
    '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    '.aac': 'audio/aac',
    '.mp3': 'audio/mpeg',
    '.ogg': 'audio/ogg',
}

WHATSAPP_TYPE_MAP = {
    'image/jpeg': 'image',
    'image/png': 'image',
    'image/webp': 'image',
    'video/mp4': 'video',
    'video/3gpp': 'video',
    'application/pdf': 'document',
    'audio/aac': 'audio',
    'audio/mpeg': 'audio',
    'audio/ogg': 'audio',
}

def upload_media(file_path: str) -> str:
    """Faz upload de um arquivo para a API e retorna o media_id."""
    file_path = Path(file_path)
    ext = file_path.suffix.lower()
    mime_type = MIME_MAP.get(ext)

    if not mime_type:
        raise ValueError(f'Formato não suportado: {ext}')

    upload_url = f'https://graph.facebook.com/v21.0/{PHONE_NUMBER_ID}/media'

    with open(file_path, 'rb') as f:
        files = {
            'file': (file_path.name, f, mime_type),
        }
        data = {
            'messaging_product': 'whatsapp',
            'type': mime_type,
        }
        response = requests.post(
            upload_url,
            headers={'Authorization': f'Bearer {TOKEN}'},
            files=files,
            data=data,
        )

    response.raise_for_status()
    return response.json()['id']


def send_media_by_id(
    to: str,
    media_id: str,
    mime_type: str,
    caption: str = '',
    filename: str = '',
) -> dict:
    """Envia uma mensagem usando um media_id já obtido."""
    whatsapp_type = WHATSAPP_TYPE_MAP.get(mime_type, 'document')

    media_object = {'id': media_id}
    if caption:
        media_object['caption'] = caption
    if filename and whatsapp_type == 'document':
        media_object['filename'] = filename

    payload = {
        'messaging_product': 'whatsapp',
        'recipient_type': 'individual',
        'to': to,
        'type': whatsapp_type,
        whatsapp_type: media_object,
    }

    messages_url = f'https://graph.facebook.com/v21.0/{PHONE_NUMBER_ID}/messages'
    response = requests.post(
        messages_url,
        json=payload,
        headers={
            'Authorization': f'Bearer {TOKEN}',
            'Content-Type': 'application/json',
        },
    )
    response.raise_for_status()
    return response.json()


def upload_and_send(to: str, file_path: str, caption: str = '') -> dict:
    """Faz upload e envia em uma única chamada."""
    file_path = Path(file_path)
    ext = file_path.suffix.lower()
    mime_type = MIME_MAP.get(ext, 'application/octet-stream')

    media_id = upload_media(str(file_path))
    print(f'Upload concluído. media_id: {media_id}')

    return send_media_by_id(
        to=to,
        media_id=media_id,
        mime_type=mime_type,
        caption=caption,
        filename=file_path.name,
    )

# Uso
upload_and_send('5511999999999', './fatura-jan-2026.pdf', 'Sua fatura de janeiro')

Helper Function Reutilizável

Para quem quer uma interface unificada que detecta automaticamente o tipo de mídia e escolhe o fluxo correto, aqui está um helper completo em Node.js:

const axios = require('axios')
const FormData = require('form-data')
const fs = require('fs')
const path = require('path')

const PHONE_NUMBER_ID = process.env.WHATSAPP_PHONE_NUMBER_ID
const TOKEN = process.env.WHATSAPP_TOKEN

const MEDIA_CONFIG = {
  'image/jpeg':   { type: 'image',    maxMB: 5,   ext: ['.jpg', '.jpeg'] },
  'image/png':    { type: 'image',    maxMB: 5,   ext: ['.png'] },
  'image/webp':   { type: 'image',    maxMB: 0.1, ext: ['.webp'] },
  'video/mp4':    { type: 'video',    maxMB: 16,  ext: ['.mp4'] },
  'video/3gpp':   { type: 'video',    maxMB: 16,  ext: ['.3gp'] },
  'application/pdf': { type: 'document', maxMB: 100, ext: ['.pdf'] },
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
                  { type: 'document', maxMB: 100, ext: ['.docx'] },
  'audio/aac':    { type: 'audio',    maxMB: 16,  ext: ['.aac'] },
  'audio/mpeg':   { type: 'audio',    maxMB: 16,  ext: ['.mp3'] },
  'audio/ogg':    { type: 'audio',    maxMB: 16,  ext: ['.ogg'] },
}

const EXT_TO_MIME = Object.entries(MEDIA_CONFIG).reduce((acc, [mime, cfg]) => {
  cfg.ext.forEach((e) => (acc[e] = mime))
  return acc
}, {})

/**
 * Envia mídia via WhatsApp Cloud API.
 *
 * @param {Object} options
 * @param {string} options.to          - Número do destinatário (com DDI, sem +)
 * @param {string} [options.url]       - URL pública do arquivo
 * @param {string} [options.filePath]  - Caminho local do arquivo (para upload prévio)
 * @param {string} [options.caption]   - Legenda da mídia (opcional)
 * @param {string} [options.filename]  - Nome do arquivo para documentos (opcional)
 */
async function sendMedia({ to, url, filePath, caption, filename }) {
  if (!url && !filePath) {
    throw new Error('Forneça "url" ou "filePath".')
  }

  // --- Fluxo por URL pública ---
  if (url) {
    const ext = path.extname(new URL(url).pathname).toLowerCase()
    const mime = EXT_TO_MIME[ext]
    if (!mime) throw new Error(`Extensão não suportada: ${ext}`)

    const { type } = MEDIA_CONFIG[mime]
    const mediaObject = { link: url }
    if (caption) mediaObject.caption = caption
    if (filename && type === 'document') mediaObject.filename = filename

    const res = await axios.post(
      `https://graph.facebook.com/v21.0/${PHONE_NUMBER_ID}/messages`,
      {
        messaging_product: 'whatsapp',
        recipient_type: 'individual',
        to,
        type,
        [type]: mediaObject,
      },
      { headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' } }
    )
    return res.data
  }

  // --- Fluxo por upload prévio ---
  const ext = path.extname(filePath).toLowerCase()
  const mime = EXT_TO_MIME[ext]
  if (!mime) throw new Error(`Extensão não suportada: ${ext}`)

  const { type, maxMB } = MEDIA_CONFIG[mime]
  const stats = fs.statSync(filePath)
  const fileMB = stats.size / (1024 * 1024)

  if (fileMB > maxMB) {
    throw new Error(`Arquivo excede o limite de ${maxMB}MB para ${type} (${fileMB.toFixed(2)}MB)`)
  }

  const form = new FormData()
  form.append('messaging_product', 'whatsapp')
  form.append('type', mime)
  form.append('file', fs.createReadStream(filePath), {
    filename: path.basename(filePath),
    contentType: mime,
  })

  const uploadRes = await axios.post(
    `https://graph.facebook.com/v21.0/${PHONE_NUMBER_ID}/media`,
    form,
    { headers: { Authorization: `Bearer ${TOKEN}`, ...form.getHeaders() } }
  )

  const mediaId = uploadRes.data.id
  const mediaObject = { id: mediaId }
  if (caption) mediaObject.caption = caption
  mediaObject.filename = filename || path.basename(filePath)

  const sendRes = await axios.post(
    `https://graph.facebook.com/v21.0/${PHONE_NUMBER_ID}/messages`,
    {
      messaging_product: 'whatsapp',
      recipient_type: 'individual',
      to,
      type,
      [type]: mediaObject,
    },
    { headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' } }
  )

  return { mediaId, message: sendRes.data }
}

module.exports = { sendMedia }

// --- Exemplos ---
// Envio por URL:
// sendMedia({ to: '5511999999999', url: 'https://cdn.seusite.com/foto.jpg', caption: 'Confira!' })

// Envio por upload:
// sendMedia({ to: '5511999999999', filePath: './relatorio.pdf', filename: 'relatorio-marco-2026.pdf' })

Tratamento de Erros Comuns

A API retorna erros com código HTTP 4xx ou 5xx e um objeto error no corpo. Os erros de mídia mais frequentes:

Erro 131053 — Formato de mídia inválido

{
  "error": {
    "code": 131053,
    "message": "Media type not supported.",
    "error_data": { "details": "The media format is not supported." }
  }
}

Causa: Arquivo com extensão correta mas codec incompatível (vídeo com codec VP9, por exemplo), ou arquivo corrompido.

Solução: Converta o arquivo antes do envio. Para vídeo:

ffmpeg -i input.mov -vcodec h264 -acodec aac output.mp4

Erro 131052 — URL inacessível

{
  "error": {
    "code": 131052,
    "message": "Media URL is not accessible.",
    "error_data": { "details": "Failed to download the media." }
  }
}

Causa: A URL passada retornou 401, 403, 404 ou timeout. A Meta tenta buscar o arquivo no momento do envio.

Solução: Certifique-se de que a URL é pública, não requer autenticação e está acessível externamente. Teste com curl -I <URL> antes de enviar.

Erro 131051 — Arquivo acima do limite de tamanho

{
  "error": {
    "code": 131051,
    "message": "Media file size too large.",
    "error_data": { "details": "Media file size exceeds the limit." }
  }
}

Causa: O arquivo ultrapassa os limites descritos anteriormente.

Solução: Comprima ou redimensione antes de enviar. Para imagens, use sharp (Node.js) ou Pillow (Python). Para PDFs, use ghostscript.

Tratamento centralizado de erros

async function sendMediaSafe(options) {
  try {
    return await sendMedia(options)
  } catch (error) {
    if (error.response) {
      const { code, message } = error.response.data?.error || {}
      switch (code) {
        case 131051:
          throw new Error(`Arquivo muito grande. Comprima antes de enviar. (${message})`)
        case 131052:
          throw new Error(`URL inacessível pela Meta. Verifique se é pública. (${message})`)
        case 131053:
          throw new Error(`Formato não suportado. Converta o arquivo. (${message})`)
        default:
          throw new Error(`Erro da API (${code}): ${message}`)
      }
    }
    throw error
  }
}
def send_media_safe(**kwargs):
    """Wrapper com tratamento de erros estruturado."""
    error_messages = {
        131051: 'Arquivo muito grande. Comprima antes de enviar.',
        131052: 'URL inacessível pela Meta. Verifique se é pública.',
        131053: 'Formato não suportado. Converta o arquivo.',
    }
    try:
        return upload_and_send(**kwargs)
    except requests.exceptions.HTTPError as e:
        error_data = e.response.json().get('error', {})
        code = error_data.get('code')
        api_msg = error_data.get('message', '')
        friendly = error_messages.get(code, f'Erro da API ({code})')
        raise RuntimeError(f'{friendly} — {api_msg}') from e

Gerenciando Media IDs

Se você reutiliza o mesmo arquivo para múltiplos usuários (por exemplo, um PDF de catálogo enviado para toda a base), faz sentido fazer o upload uma vez e guardar o media_id.

// Exemplo simples de cache de media_id em memória
// Em produção, use Redis ou banco de dados
const mediaCache = new Map()

async function getOrUploadMedia(filePath) {
  if (mediaCache.has(filePath)) {
    return mediaCache.get(filePath)
  }

  const mediaId = await uploadMedia(filePath)
  // media_id válido por 30 dias — em produção, armazene com timestamp de expiração
  mediaCache.set(filePath, mediaId)
  return mediaId
}

// Envio em lote para múltiplos destinatários
async function broadcastDocument(recipients, filePath, caption) {
  const mediaId = await getOrUploadMedia(filePath)
  const filename = path.basename(filePath)

  const results = await Promise.allSettled(
    recipients.map((to) =>
      sendMediaById(to, mediaId, 'application/pdf', caption, filename)
    )
  )

  const successes = results.filter((r) => r.status === 'fulfilled').length
  const failures = results.filter((r) => r.status === 'rejected').length
  console.log(`Enviado: ${successes} ✅ | Falhou: ${failures} ❌`)
  return results
}

Boas Práticas

Valide antes de enviar. Cheque o tamanho e o formato do arquivo no seu servidor antes de chamar a API. Evita round-trips desnecessários e melhora a experiência de erro para o usuário final.

Para URLs, use CDN com HTTPS. A Meta rejeita URLs HTTP (sem SSL). Use CDN (Cloudflare, AWS CloudFront, etc.) para garantir disponibilidade e velocidade no download.

Prefira upload prévio para envios em escala. Se você vai enviar o mesmo arquivo para centenas ou milhares de usuários, um único upload + reutilização do media_id é muito mais eficiente do que fazer a Meta buscar o arquivo em cada envio.

Legenda é opcional, mas útil. Para imagens e vídeos, a legenda (campo caption) aparece como texto abaixo da mídia. Para documentos, use o campo filename para dar um nome amigável — o usuário verá esse nome antes de baixar.

Monitore os erros 131052 ativamente. URLs que funcionam hoje podem parar de funcionar amanhã (arquivo removido, CDN fora do ar, expiração de URL assinada). Implemente logs e alertas para esse erro.

Resumo

Enviar mídia pela WhatsApp Cloud API é direto quando você entende os dois fluxos e os limites de cada tipo de arquivo.

Os pontos essenciais:

  • URL pública para arquivos já hospedados; upload prévio para arquivos locais, sensíveis ou reutilizados
  • Imagens: JPEG/PNG até 5 MB; Vídeos: MP4 com H.264 até 16 MB; Documentos: PDF e Office até 100 MB
  • O upload usa multipart/form-data com os campos messaging_product, type e file
  • O media_id tem validade de 30 dias — vale guardar em cache para envios em lote
  • Erros 131051, 131052 e 131053 são os mais comuns — trate-os explicitamente no seu código

Com o helper function apresentado acima, você tem uma interface única que valida tamanho, detecta o tipo de mídia e roteia para o fluxo correto — pronto para integrar em qualquer aplicação Node.js.

Artigos Relacionados

  • APIs & Integrações

    Template WhatsApp Rejeitado ou Pausado: Como Entender e Corrigir

  • APIs & Integrações

    Como Criar e Gerenciar Templates na WhatsApp Business API: Guia Completo

  • APIs & Integrações

    Como Configurar Webhooks na WhatsApp Business API: Guia Técnico Completo

.