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-Typedo header HTTP deve sermultipart/form-datacom o boundary correto. Bibliotecas comoaxioserequestsgerenciam isso automaticamente quando você usaFormData.
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-datacom os camposmessaging_product,typeefile - O
media_idtem 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.