Guia completo para envio e recebimento de imagens, vídeos, áudios e documentos via WhatsApp API.
✅ Sistema Otimizado:
Mídias são armazenadas em disco (redução de 99% no uso de memória), com cache automático e download único.
image/jpeg - JPG, JPEGimage/png - PNGimage/gif - GIFimage/webp - WebPTamanho máximo: ~16 MB
video/mp4 - MP4 (recomendado)video/avi - AVIvideo/quicktime - MOVTamanho máximo: ~64 MB
audio/mpeg - MP3audio/ogg - OGGaudio/mp4 - M4Aaudio/wav - WAVTamanho máximo: ~16 MB
application/pdf - PDFapplication/msword - DOCapplication/vnd.openxmlformats-officedocument.wordprocessingml.document - DOCXapplication/vnd.ms-excel - XLSapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet - XLSXTamanho máximo: ~100 MB
⚠️ ATENÇÃO: Para enviar mídia, você DEVE usar o endpoint POST /api/messages/media.
NÃO envie URLs de mídia através de POST /api/messages/text - isso resultará em links ao invés de arquivos.
Veja a seção Erro Comum para mais detalhes.
Envia mídia (imagem, vídeo, áudio ou documento) para um número via WhatsApp.
❌ ERRO COMUM: Não envie URLs de mídia através de POST /api/messages/text!
Se você enviar uma URL de mídia como mensagem de texto, o destinatário receberá um link ao invés do arquivo.
SEMPRE use este endpoint (/api/messages/media) para enviar mídias.
{
"sessionId": "sua-sessao",
"to": "5511999999999",
"mediaUrl": "https://exemplo.com/imagem.jpg",
"caption": "Legenda opcional",
"mimetype": "image/jpeg", // opcional, será detectado automaticamente
"filename": "imagem.jpg" // opcional
}
💡 URLs Relativas Suportadas:
Você pode usar URLs relativas (ex: /api/media/arquivo.jpg).
A API converte automaticamente para URL absoluta.
{
"sessionId": "sua-sessao",
"to": "5511999999999",
"mediaBase64": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
"caption": "Legenda opcional",
"mimetype": "image/jpeg",
"filename": "imagem.jpg"
}
{
"success": true,
"messageId": "true_5511999999999@c.us_ABC123",
"to": "5511999999999@c.us",
"type": "media",
"mimetype": "image/jpeg",
"timestamp": "2024-11-27T19:00:00.000Z"
}
⚠️ Importante:
mediaUrlMídias recebidas são armazenadas em disco e você recebe uma URL para acessá-las. Isso reduz o uso de memória em 99% e melhora significativamente a performance.
✅ Vantagens do Sistema Otimizado:
Quando uma mensagem é recebida, a API verifica automaticamente se contém mídia usando a propriedade msg.hasMedia do WhatsApp Web.js.
if (msg.hasMedia) {
// Mensagem contém mídia (imagem, vídeo, áudio, documento)
mediaInfo = await getMediaWithCache(msg, sessionId);
}
A mídia é baixada uma única vez e salva em disco no diretório /api/media/ com um nome único baseado no ID da mensagem.
session-{sessionId}_{messageId}.{extensao}session-abc123_true_1234567890@lid_ABC.jpg
A mensagem é salva na tabela messages com os seguintes campos:
{
"direction": "received",
"message_type": "image", // ou "video", "audio", "document"
"body": "Legenda da mídia (se houver)",
"media_url": "/api/media/session-abc123_ABC123.jpg",
"media_mimetype": "image/jpeg",
"media_filename": "foto.jpg" // nome original (se disponível)
}
⚠️ IMPORTANTE:
O campo message_type indica o tipo de mídia:
"image" ou "image/jpeg", "image/png", etc. → Imagem"video" ou "video/mp4", etc. → Vídeo"audio" ou "audio/mpeg", etc. → Áudio"document" ou "application/pdf", etc. → Documento"text" → Mensagem de texto (sem mídia)Se um webhook estiver configurado, a API envia um payload JSON com a URL da mídia (não Base64):
{
"sessionId": "sua-sessao",
"from": "5511999999999@c.us",
"body": "Legenda",
"type": "image",
"mediaUrl": "/api/media/session-abc123_ABC123.jpg",
"mediaMimetype": "image/jpeg",
"mediaFilename": "foto.jpg",
"timestamp": 1701108000
}
// JavaScript/Node.js
if (payload.mediaUrl) {
console.log('Mensagem tem mídia!');
console.log('Tipo:', payload.mediaMimetype);
console.log('URL:', payload.mediaUrl);
}
-- SQL: Buscar mensagens com mídia
SELECT * FROM messages
WHERE direction = 'received'
AND media_url IS NOT NULL
AND message_type != 'text'
ORDER BY timestamp DESC;
-- Verificar tipo específico
SELECT * FROM messages
WHERE message_type LIKE 'image%' -- Imagens
OR message_type LIKE 'video%' -- Vídeos
OR message_type LIKE 'audio%'; -- Áudios
// JavaScript
const response = await fetch(
'https://app.autowhats.com.br/api/sessions/sua-sessao/messages',
{ headers: { 'X-API-Key': 'sua_api_key' } }
);
const data = await response.json();
data.messages.forEach(msg => {
if (msg.mediaUrl) {
console.log('Mensagem com mídia:', msg.type);
console.log('URL:', msg.mediaUrl);
}
});
Quando uma mídia é recebida, o webhook envia um payload com a URL do arquivo:
{
"sessionId": "sua-sessao",
"from": "5511999999999@c.us",
"to": "5511888888888@c.us",
"body": "Legenda da imagem (se houver)",
"type": "image",
"timestamp": 1701108000,
"mediaUrl": "/api/media/session-001_ABC123.jpg",
"mediaMimetype": "image/jpeg",
"mediaFilename": "foto.jpg",
"isGroup": false
}
📥 Como Acessar a Mídia:
O campo mediaUrl contém uma URL relativa. Para baixar a mídia, faça:
GET https://app.autowhats.com.br/api/media/session-001_ABC123.jpg
Use GET /api/sessions/:id/messages para buscar mensagens com mídia:
GET https://app.autowhats.com.br/api/sessions/sua-sessao/messages
Headers: X-API-Key: sua_api_key
Resposta:
{
"success": true,
"messages": [
{
"id": "msg_123",
"from": "5511999999999@c.us",
"body": "Legenda",
"type": "image",
"mediaUrl": "/api/media/session-001_ABC123.jpg",
"mediaMimetype": "image/jpeg",
"mediaFilename": "foto.jpg",
"timestamp": 1701108000000
}
]
}
Serve mídias armazenadas em disco. Este endpoint é público (não requer API Key) e pode ser usado diretamente em tags HTML ou para download.
:filename - Nome do arquivo de mídia (ex: session-001_ABC123.jpg)Retorna o arquivo binário com os headers apropriados:
Content-Type: Tipo MIME detectado automaticamenteContent-Length: Tamanho do arquivoCache-Control: Cache por 1 ano (melhor performance)Content-Disposition: inline (exibe no navegador)HTML (Exibir Imagem):
<img src="https://app.autowhats.com.br/api/media/session-001_ABC123.jpg" alt="Mídia">
JavaScript (Baixar Arquivo):
const mediaUrl = '/api/media/session-001_ABC123.jpg';
const fullUrl = `https://app.autowhats.com.br${mediaUrl}`;
const response = await fetch(fullUrl);
const blob = await response.blob();
// Criar URL para exibir
const imageUrl = URL.createObjectURL(blob);
document.getElementById('image').src = imageUrl;
// Ou baixar arquivo
const a = document.createElement('a');
a.href = imageUrl;
a.download = 'imagem.jpg';
a.click();
cURL (Baixar Arquivo):
curl https://app.autowhats.com.br/api/media/session-001_ABC123.jpg -o imagem.jpg
⚠️ Nota de Segurança: Este endpoint é público. Nomes de arquivo são validados para prevenir path traversal.
async function enviarImagem(sessionId, numero, urlImagem, legenda) {
const response = await fetch('https://app.autowhats.com.br/api/messages/media', {
method: 'POST',
headers: {
'X-API-Key': 'sua_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: sessionId,
to: numero,
mediaUrl: urlImagem,
caption: legenda,
mimetype: 'image/jpeg'
})
});
const data = await response.json();
if (data.success) {
console.log('Imagem enviada!', data.messageId);
}
return data;
}
// Uso
enviarImagem('sua-sessao', '5511999999999', 'https://exemplo.com/foto.jpg', 'Olha essa foto!');
curl -X POST https://app.autowhats.com.br/api/messages/media \
-H "X-API-Key: sua_api_key" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "sua-sessao",
"to": "5511999999999",
"mediaUrl": "https://exemplo.com/video.mp4",
"caption": "Assista este vídeo!",
"mimetype": "video/mp4"
}'
import requests
import base64
def enviar_audio(session_id, numero, caminho_arquivo, legenda=''):
# Ler arquivo e converter para base64
with open(caminho_arquivo, 'rb') as f:
audio_base64 = base64.b64encode(f.read()).decode('utf-8')
data_uri = f"data:audio/mpeg;base64,{audio_base64}"
response = requests.post(
'https://app.autowhats.com.br/api/messages/media',
headers={'X-API-Key': 'sua_api_key'},
json={
'sessionId': session_id,
'to': numero,
'mediaBase64': data_uri,
'caption': legenda,
'mimetype': 'audio/mpeg',
'filename': 'audio.mp3'
}
)
return response.json()
# Uso
resultado = enviar_audio('sua-sessao', '5511999999999', 'audio.mp3', 'Escute isso!')
print(resultado)
Configure o webhook da sua sessão para receber notificações em tempo real:
POST /api/sessions/sua-sessao/webhook
Headers: X-API-Key: sua_api_key
Body: { "webhookUrl": "https://seu-servidor.com/webhook" }
Quando uma mídia é recebida, você receberá um POST no seu webhook:
{
"sessionId": "sua-sessao",
"from": "5511999999999@c.us",
"body": "Legenda (se houver)",
"type": "image",
"mediaUrl": "/api/media/session-abc123_ABC123.jpg",
"mediaMimetype": "image/jpeg",
"mediaFilename": "foto.jpg",
"timestamp": 1701108000
}
Sempre verifique se mediaUrl existe antes de processar:
if (payload.mediaUrl) {
// Mensagem tem mídia!
// Processar mídia...
} else {
// Mensagem de texto normal
}
Converta a URL relativa para absoluta e baixe o arquivo:
const fullUrl = `https://app.autowhats.com.br${payload.mediaUrl}`;
const response = await fetch(fullUrl);
const blob = await response.blob();
Use o arquivo conforme sua necessidade (salvar, processar, exibir, etc.)
const express = require('express');
const fetch = require('node-fetch');
const fs = require('fs');
const app = express();
app.use(express.json());
app.post('/webhook', async (req, res) => {
const payload = req.body;
// Responder rapidamente para evitar timeout
res.status(200).json({ received: true });
// Verificar se mensagem tem mídia
if (payload.mediaUrl) {
console.log('📥 Mídia recebida!');
console.log('Tipo:', payload.mediaMimetype);
console.log('De:', payload.from);
try {
// Converter URL relativa para absoluta
const fullUrl = `https://app.autowhats.com.br${payload.mediaUrl}`;
// Baixar mídia
const response = await fetch(fullUrl);
if (!response.ok) {
throw new Error(`Erro ao baixar: ${response.status}`);
}
const buffer = await response.buffer();
const filename = payload.mediaFilename || `midia_${Date.now()}.${getExtension(payload.mediaMimetype)}`;
// Salvar arquivo
fs.writeFileSync(`./midias/${filename}`, buffer);
console.log(`✅ Mídia salva: ${filename}`);
// Processar conforme tipo
if (payload.mediaMimetype.startsWith('image/')) {
console.log('🖼️ Processando imagem...');
// Ex: redimensionar, aplicar filtros, etc.
} else if (payload.mediaMimetype.startsWith('video/')) {
console.log('🎬 Processando vídeo...');
// Ex: extrair thumbnail, converter formato, etc.
} else if (payload.mediaMimetype.startsWith('audio/')) {
console.log('🎵 Processando áudio...');
// Ex: transcrever, analisar, etc.
}
} catch (error) {
console.error('❌ Erro ao processar mídia:', error.message);
}
} else {
// Mensagem de texto normal
console.log('💬 Mensagem de texto:', payload.body);
}
});
function getExtension(mimetype) {
const map = {
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/webp': 'webp',
'video/mp4': 'mp4',
'audio/mpeg': 'mp3',
'application/pdf': 'pdf'
};
return map[mimetype] || 'bin';
}
app.listen(3000, () => {
console.log('Webhook rodando na porta 3000');
});
from flask import Flask, request, jsonify
import requests
import os
from pathlib import Path
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.json
# Responder rapidamente
response = jsonify({'received': True})
# Verificar se mensagem tem mídia
if payload.get('mediaUrl'):
print('📥 Mídia recebida!')
print(f'Tipo: {payload.get("mediaMimetype")}')
print(f'De: {payload.get("from")}')
try:
# Converter URL relativa para absoluta
media_url = payload['mediaUrl']
if media_url.startswith('/'):
full_url = f'https://app.autowhats.com.br{media_url}'
else:
full_url = media_url
# Baixar mídia
response = requests.get(full_url)
response.raise_for_status()
# Criar diretório se não existir
os.makedirs('midias', exist_ok=True)
# Salvar arquivo
filename = payload.get('mediaFilename') or f'midia_{int(time.time())}.{get_extension(payload.get("mediaMimetype"))}'
filepath = Path('midias') / filename
with open(filepath, 'wb') as f:
f.write(response.content)
print(f'✅ Mídia salva: {filepath}')
# Processar conforme tipo
mimetype = payload.get('mediaMimetype', '')
if mimetype.startswith('image/'):
print('🖼️ Processando imagem...')
# Ex: redimensionar, aplicar filtros, etc.
elif mimetype.startswith('video/'):
print('🎬 Processando vídeo...')
# Ex: extrair thumbnail, converter formato, etc.
elif mimetype.startswith('audio/'):
print('🎵 Processando áudio...')
# Ex: transcrever, analisar, etc.
except Exception as e:
print(f'❌ Erro ao processar mídia: {str(e)}')
else:
# Mensagem de texto normal
print(f'💬 Mensagem de texto: {payload.get("body")}')
return response
def get_extension(mimetype):
map_ext = {
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/webp': 'webp',
'video/mp4': 'mp4',
'audio/mpeg': 'mp3',
'application/pdf': 'pdf'
}
return map_ext.get(mimetype, 'bin')
if __name__ == '__main__':
app.run(port=3000, debug=True)
true]);
flush();
// Verificar se mensagem tem mídia
if (isset($payload['mediaUrl']) && !empty($payload['mediaUrl'])) {
error_log('📥 Mídia recebida!');
error_log('Tipo: ' . ($payload['mediaMimetype'] ?? 'desconhecido'));
error_log('De: ' . ($payload['from'] ?? 'desconhecido'));
try {
// Converter URL relativa para absoluta
$mediaUrl = $payload['mediaUrl'];
if (strpos($mediaUrl, '/') === 0) {
$fullUrl = 'https://app.autowhats.com.br' . $mediaUrl;
} else {
$fullUrl = $mediaUrl;
}
// Baixar mídia
$binaryData = file_get_contents($fullUrl);
if ($binaryData === false) {
throw new Exception('Erro ao baixar mídia');
}
// Criar diretório se não existir
if (!is_dir('midias')) {
mkdir('midias', 0755, true);
}
// Salvar arquivo
$filename = $payload['mediaFilename'] ?? 'midia_' . time() . '.' . getExtension($payload['mediaMimetype'] ?? '');
$filepath = 'midias/' . $filename;
file_put_contents($filepath, $binaryData);
error_log('✅ Mídia salva: ' . $filepath);
// Processar conforme tipo
$mimetype = $payload['mediaMimetype'] ?? '';
if (strpos($mimetype, 'image/') === 0) {
error_log('🖼️ Processando imagem...');
// Ex: redimensionar, aplicar filtros, etc.
} elseif (strpos($mimetype, 'video/') === 0) {
error_log('🎬 Processando vídeo...');
// Ex: extrair thumbnail, converter formato, etc.
} elseif (strpos($mimetype, 'audio/') === 0) {
error_log('🎵 Processando áudio...');
// Ex: transcrever, analisar, etc.
}
} catch (Exception $e) {
error_log('❌ Erro ao processar mídia: ' . $e->getMessage());
}
} else {
// Mensagem de texto normal
error_log('💬 Mensagem de texto: ' . ($payload['body'] ?? ''));
}
function getExtension($mimetype) {
$map = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp',
'video/mp4' => 'mp4',
'audio/mpeg' => 'mp3',
'application/pdf' => 'pdf'
];
return $map[$mimetype] ?? 'bin';
}
?>
💡 Dica Importante:
Sempre responda ao webhook rapidamente (200 OK) antes de processar a mídia. Isso evita timeouts e garante que a API saiba que a mensagem foi recebida.
true]);
?>
const express = require('express');
const fetch = require('node-fetch');
const fs = require('fs');
const app = express();
app.use(express.json({ limit: '50mb' }));
app.post('/webhook', async (req, res) => {
const { mediaUrl, mediaMimetype, mediaFilename, from, body } = req.body;
if (mediaUrl) {
// NOVO: mediaUrl agora é uma URL, não Base64
// Baixar arquivo da API
const fullUrl = 'https://app.autowhats.com.br' + mediaUrl;
const response = await fetch(fullUrl);
const buffer = await response.buffer();
// Determinar extensão
const ext = mediaMimetype.split('/')[1];
const filename = mediaFilename || `midia_${Date.now()}.${ext}`;
// Salvar arquivo
fs.writeFileSync(`uploads/${filename}`, buffer);
console.log(`Mídia recebida de ${from}: ${filename}`);
}
res.status(200).send('OK');
});
app.listen(3001);
Alguns aplicativos estão enviando URLs de mídia como mensagens de texto através do endpoint
POST /api/messages/text, resultando em:
message_type='text'
Para enviar mídia, você DEVE usar o endpoint POST /api/messages/media:
// ❌ ERRADO - NÃO FAÇA ISSO
fetch('https://app.autowhats.com.br/api/messages/text', {
method: 'POST',
headers: {
'X-API-Key': 'sua_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'sua-sessao',
to: '5511999999999',
message: '📎 https://supabase.co/storage/.../imagem.png'
})
});
// Resultado: Link enviado como texto ❌
// ✅ CORRETO - USE ESTE MÉTODO
fetch('https://app.autowhats.com.br/api/messages/media', {
method: 'POST',
headers: {
'X-API-Key': 'sua_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: 'sua-sessao',
to: '5511999999999',
mediaUrl: 'https://supabase.co/storage/.../imagem.png',
caption: 'Legenda opcional'
})
});
// Resultado: Arquivo enviado corretamente ✅
⚠️ Importante:
/api/messages/text/api/messages/media para enviar mídiasmediaUrl deve conter apenas a URL, não texto adicional📊 Como Verificar no Banco de Dados:
✅ Mensagens enviadas corretamente terão:
message_type = tipo de mídia (ex: 'image/png', 'video/mp4')media_url = URL da mídia (se aplicável)body = caption/legenda (não a URL completa)❌ Mensagens enviadas incorretamente terão:
message_type = 'text' ❌media_url = NULL ❌body = URL completa da mídia (ex: "📎 https://supabase.co/.../imagem.png") ❌Exemplo Real do Erro (ID 45):
message_type: 'text'
body: '📎 https://mgmrqskvwztsmcoksbgq.supabase.co/storage/.../imagem.png'
media_url: NULL
media_mimetype: NULL
Deveria ser:
message_type: 'image/png'
body: 'Legenda' (ou vazio)
media_url: 'https://mgmrqskvwztsmcoksbgq.supabase.co/...'
media_mimetype: 'image/png'
Como verificar se uma mensagem recebida contém mídia consultando diretamente o banco de dados MySQL.
messagesCampos importantes para identificar mídias recebidas:
| Campo | Tipo | Descrição |
|---|---|---|
direction |
VARCHAR | 'received' = recebida, 'sent' = enviada |
message_type |
VARCHAR | Tipo da mensagem: 'text', 'image', 'video', 'audio', 'document' ou MIME type completo |
media_url |
VARCHAR | URL da mídia (ex: /api/media/session-abc123_ABC123.jpg) ou NULL |
media_mimetype |
VARCHAR | Tipo MIME da mídia (ex: 'image/jpeg') ou NULL |
media_filename |
VARCHAR | Nome original do arquivo (se disponível) ou NULL |
body |
TEXT | Texto da mensagem ou legenda da mídia |
Uma mensagem recebida com mídia terá os seguintes valores:
direction: 'received'
message_type: 'image' OU 'image/jpeg' OU 'image/png' etc.
media_url: '/api/media/session-abc123_ABC123.jpg' (NÃO NULL)
media_mimetype: 'image/jpeg' (NÃO NULL)
media_filename: 'foto.jpg' (pode ser NULL)
body: 'Legenda da imagem' (ou vazio se não houver legenda)
Query para buscar mensagens com mídia:
SELECT * FROM messages
WHERE direction = 'received'
AND media_url IS NOT NULL
AND message_type != 'text'
ORDER BY timestamp DESC;
Uma mensagem de texto normal terá:
direction: 'received'
message_type: 'text'
media_url: NULL
media_mimetype: NULL
media_filename: NULL
body: 'Texto da mensagem'
A mensagem ID 51 foi enviada incorretamente como texto quando deveria ser mídia:
id: 51
direction: 'sent'
message_type: 'text' ❌ (deveria ser 'image/webp')
media_url: NULL ❌ (deveria ter URL)
media_mimetype: NULL ❌ (deveria ser 'image/webp')
body: '? https://supabase.co/.../imagem.webp' ❌ (URL no body)
Problema: A URL de mídia foi enviada como mensagem de texto ao invés de usar o endpoint /api/messages/media.
Solução: A API agora detecta automaticamente URLs de mídia em mensagens de texto e converte para envio de mídia.
Buscar todas as mídias recebidas:
SELECT id, session_id, from_number, message_type,
media_url, media_mimetype, media_filename,
body, timestamp
FROM messages
WHERE direction = 'received'
AND media_url IS NOT NULL
ORDER BY timestamp DESC;
Contar mídias por tipo:
SELECT
CASE
WHEN message_type LIKE 'image%' THEN 'Imagem'
WHEN message_type LIKE 'video%' THEN 'Vídeo'
WHEN message_type LIKE 'audio%' THEN 'Áudio'
WHEN message_type LIKE 'application%' THEN 'Documento'
ELSE 'Outro'
END as tipo,
COUNT(*) as total
FROM messages
WHERE direction = 'received'
AND media_url IS NOT NULL
GROUP BY tipo
ORDER BY total DESC;
Buscar mídias de uma sessão específica:
SELECT * FROM messages
WHERE session_id = 'sua-sessao'
AND direction = 'received'
AND media_url IS NOT NULL
ORDER BY timestamp DESC
LIMIT 50;
curl -I https://sua-url.com/imagem.jpg
Causa mais comum: Você está enviando a URL através de /api/messages/text ao invés de /api/messages/media.
/api/messages/mediapm2 logs whatsapp-api | grep -i "mídia\|enviando"mediaBase64 como alternativa se a URL não funcionarmimetype está corretoExemplo do Erro no Banco:
message_type='text', body='📎 https://supabase.co/.../imagem.png'
Deveria ser: message_type='image/png', body='Legenda'
mimetype - deve ser exato (ex: image/jpeg, não image/jpg)pm2 logs whatsapp-apiGET /api/sessions/:id/statuswebhookUrl está configurado corretamentemediaUrl agora contém uma URL, não Base64Para arquivos > 5MB, sempre use mediaUrl ao invés de mediaBase64
Para arquivos < 1MB, mediaBase64 é mais simples e direto
Embora opcional, especificar o mimetype garante melhor compatibilidade
Webhooks são mais eficientes que polling para receber mídias em tempo real
Mídias são armazenadas em disco. Baixe apenas quando precisar processar