Documentação de Eventos
WebSocket & Redis Streams
Visão Geral
A API oferece três formas de receber eventos em tempo real das instâncias do WhatsApp:
WebSocket
Conexão bidirecional em tempo real
Redis Streams
Sistema de mensagens em tempo real
Webhooks
HTTP POST para seu servidor
WebSocket
Conexão
Cada instância possui seu próprio endpoint WebSocket:
ws://SEU_HOST:PORTA/websocket/NOME_DA_INSTANCIA?token=SUA_API_KEY
wss://SEU_HOST:PORTA/websocket/NOME_DA_INSTANCIA?token=SUA_API_KEY # Para HTTPS
Autenticação obrigatória: O parâmetro ?token= deve conter a API Key ou um JWT Bearer token válido.
Exemplo: ws://localhost:3003/websocket/minha-instancia?token=SUA_API_KEY
Formato dos Eventos
Ao conectar, a API envia {"type": "connected"}. Depois, cada evento chega neste formato, com o tipo no final de routingKey:
{
"type": "event",
"routingKey": "minha-instancia.messages_upsert",
"data": {
"instance": "minha-instancia",
"type": "notify",
"message": {
"key": {
"remoteJid": "5511999999999@s.whatsapp.net",
"fromMe": false,
"id": "3EB0C7F8..."
},
"messageTimestamp": 1234567890,
"pushName": "Maria",
"status": "RECEIVED",
"message": {
"conversation": "Olá, mundo!"
}
},
"timestamp": 1710000000000
},
"timestamp": 1710000000000
}
Exemplo JavaScript (Node.js)
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:3003/websocket/minha-instancia?token=SUA_API_KEY');
ws.on('open', () => {
console.log('✅ Conectado ao WebSocket');
});
ws.on('message', (data) => {
const event = JSON.parse(data);
if (event.type !== 'event') return; // ignora {type: 'connected'}
const eventType = event.routingKey.split('.').slice(1).join('.');
console.log('📨 Evento recebido:', eventType);
console.log('📦 Dados:', event.data);
// Processar diferentes tipos de eventos
switch(eventType) {
case 'messages_upsert':
console.log('Nova mensagem:', event.data.message);
break;
case 'qrcode_updated':
console.log('QR Code:', event.data.qrcode);
break;
case 'connection_update':
console.log('Status:', event.data.state);
break;
}
});
ws.on('error', (error) => {
console.error('❌ Erro:', error);
});
ws.on('close', () => {
console.log('🔌 Desconectado');
});
Exemplo Python
import asyncio
import websockets
import json
async def connect_websocket():
uri = "ws://localhost:3003/websocket/minha-instancia?token=SUA_API_KEY"
async with websockets.connect(uri) as websocket:
print("✅ Conectado ao WebSocket")
async for message in websocket:
event = json.loads(message)
if event['type'] != 'event':
continue # ignora {"type": "connected"}
event_type = event['routingKey'].split('.', 1)[1]
print(f"📨 Evento recebido: {event_type}")
if event_type == 'messages_upsert':
print(f"Nova mensagem: {event['data']['message']}")
elif event_type == 'qrcode_updated':
print(f"QR Code atualizado")
elif event_type == 'connection_update':
print(f"Status: {event['data']['state']}")
asyncio.run(connect_websocket())
Redis Streams
Os eventos são publicados em Redis Streams com o formato de chave:
stream:NOME_DA_INSTANCIA
Exemplos de streams:
stream:minha-instancia- Eventos habilitados da instânciastream:instancia-2- Eventos habilitados da instancia-2
Cada mensagem contém os campos: event_type, payload e timestamp
Configuração
Configure o Redis no arquivo .env:
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=""
Exemplo Node.js (ioredis)
const Redis = require('ioredis');
const redis = new Redis({
host: 'localhost',
port: 6379
});
async function consumeEvents(instanceName) {
const streamKey = `stream:${instanceName}`;
const groupName = `consumers:${instanceName}`;
const consumerName = 'consumer-1';
// Criar consumer group se não existir
try {
await redis.xgroup('CREATE', streamKey, groupName, '0', 'MKSTREAM');
} catch (err) {
// Group já existe
}
console.log(`✅ Consumindo eventos de ${streamKey}...`);
while (true) {
const messages = await redis.xreadgroup(
'GROUP', groupName, consumerName,
'BLOCK', 5000,
'COUNT', 10,
'STREAMS', streamKey, '>'
);
if (messages) {
for (const [stream, streamMessages] of messages) {
for (const [id, fields] of streamMessages) {
// A ordem dos campos não é garantida: converta em objeto
const data = {};
for (let i = 0; i < fields.length; i += 2) data[fields[i]] = fields[i + 1];
const eventType = data.event_type;
const payload = JSON.parse(data.payload);
console.log('📨 Event Type:', eventType);
console.log('📦 Payload:', payload);
// Confirmar processamento
await redis.xack(streamKey, groupName, id);
}
}
}
}
}
consumeEvents('minha-instancia').catch(console.error);
Exemplo Python (redis-py)
import redis
import json
import time
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
instance_name = 'minha-instancia'
stream_key = f'stream:{instance_name}'
group_name = f'consumers:{instance_name}'
consumer_name = 'consumer-1'
# Criar consumer group
try:
r.xgroup_create(stream_key, group_name, '0', mkstream=True)
except redis.exceptions.ResponseError:
pass # Group já existe
print(f'✅ Consumindo eventos de {stream_key}...')
while True:
messages = r.xreadgroup(group_name, consumer_name, {stream_key: '>'}, count=10, block=5000)
for stream, msgs in messages:
for msg_id, fields in msgs:
event_type = fields['event_type']
payload = json.loads(fields['payload'])
print(f'📨 Event Type: {event_type}')
print(f'📦 Payload: {payload}')
# Confirmar processamento
r.xack(stream_key, group_name, msg_id)
Redis Streams vs RabbitMQ
RabbitMQ (Antigo)
Queue: minhainstancia.messages_upsert
Queue: minhainstancia.connection_update
Queue: minhainstancia.qrcode_updated
...
➡️ 1 queue por tipo de evento
Redis Streams (Atual)
Stream: stream:minhainstancia
├─ event_type: messages_upsert
├─ event_type: connection_update
├─ event_type: qrcode_updated
...
➡️ 1 stream por instância
✅ Vantagens:
- • Menos overhead: 1 stream vs 30+ queues por instância
- • Mais performático: Redis é in-memory
- • Consumer Groups: Múltiplos consumers paralelos
- • Persistência: Mensagens ficam no stream (cerca de 10 mil ou
MEDIA_RETENTION_DAYS, padrão 7 dias, o que ocorrer primeiro) - • Replay: Pode reprocessar mensagens antigas pelo ID
Consumindo Eventos Específicos
Para consumir apenas eventos específicos (como messages_upsert),
você filtra pelo campo event_type dentro do consumer:
Node.js - Consumer com Filtro
const Redis = require('ioredis');
const redis = new Redis({ host: 'localhost', port: 6379 });
const instanceName = 'minhainstancia';
const streamKey = `stream:${instanceName}`;
const groupName = `consumer:${instanceName}`;
const consumerName = 'consumer-1';
// Criar consumer group (apenas uma vez)
async function createConsumerGroup() {
try {
await redis.xgroup('CREATE', streamKey, groupName, '0', 'MKSTREAM');
console.log('✅ Consumer group criado');
} catch (err) {
if (err.message.includes('BUSYGROUP')) {
console.log('ℹ️ Consumer group já existe');
}
}
}
// Consumir apenas messages_upsert
async function consumeMessages() {
while (true) {
try {
const results = await redis.xreadgroup(
'GROUP', groupName, consumerName,
'BLOCK', 5000, // Aguardar 5 segundos
'COUNT', 10, // Máximo de 10 mensagens
'STREAMS', streamKey, '>'
);
if (!results) continue;
for (const [stream, messages] of results) {
for (const [id, fields] of messages) {
// Converter array [key, value, key, value] para objeto
const data = {};
for (let i = 0; i < fields.length; i += 2) {
data[fields[i]] = fields[i + 1];
}
const eventType = data.event_type;
const payload = JSON.parse(data.payload);
const timestamp = data.timestamp;
// 🔍 Filtrar apenas messages_upsert
if (eventType === 'messages_upsert') {
console.log('📨 Nova mensagem recebida:', {
id, eventType, timestamp, payload
});
// Processar a mensagem
await handleMessage(payload);
}
// Acknowledge (marcar como processada)
await redis.xack(streamKey, groupName, id);
}
}
} catch (err) {
console.error('❌ Erro ao consumir:', err);
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
}
async function handleMessage(payload) {
console.log('Processando mensagem:', payload);
// Sua lógica aqui
}
// Iniciar
createConsumerGroup().then(consumeMessages);
Python - Consumer com Filtro
import redis
import json
import time
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
instance_name = 'minhainstancia'
stream_key = f'stream:{instance_name}'
group_name = f'consumer:{instance_name}'
consumer_name = 'consumer-1'
# Criar consumer group
try:
r.xgroup_create(stream_key, group_name, id='0', mkstream=True)
print('✅ Consumer group criado')
except redis.exceptions.ResponseError as e:
if 'BUSYGROUP' in str(e):
print('ℹ️ Consumer group já existe')
# Consumir apenas messages_upsert
def consume_messages():
while True:
try:
messages = r.xreadgroup(
groupname=group_name,
consumername=consumer_name,
streams={stream_key: '>'},
count=10,
block=5000
)
if not messages:
continue
for stream, message_list in messages:
for message_id, data in message_list:
event_type = data['event_type']
payload = json.loads(data['payload'])
timestamp = data['timestamp']
# 🔍 Filtrar apenas messages_upsert
if event_type == 'messages_upsert':
print(f'📨 Nova mensagem: {payload}')
handle_message(payload)
# Acknowledge
r.xack(stream_key, group_name, message_id)
except Exception as e:
print(f'❌ Erro: {e}')
time.sleep(5)
def handle_message(payload):
print(f'Processando: {payload}')
# Sua lógica aqui
# Iniciar
consume_messages()
Go - Consumer com Filtro
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
instanceName := "minhainstancia"
streamKey := fmt.Sprintf("stream:%s", instanceName)
groupName := fmt.Sprintf("consumer:%s", instanceName)
consumerName := "consumer-1"
// Criar consumer group
err := rdb.XGroupCreateMkStream(ctx, streamKey, groupName, "0").Err()
if err != nil && err.Error() != "BUSYGROUP Consumer Group name already exists" {
log.Fatalf("Erro ao criar group: %v", err)
}
log.Println("✅ Consumindo eventos de", streamKey)
// Consumir mensagens
for {
entries, err := rdb.XReadGroup(ctx, &redis.XReadGroupArgs{
Group: groupName,
Consumer: consumerName,
Streams: []string{streamKey, ">"},
Count: 10,
Block: 5 * time.Second,
}).Result()
if err != nil {
if err == redis.Nil {
continue
}
log.Printf("Erro ao ler: %v", err)
time.Sleep(5 * time.Second)
continue
}
for _, entry := range entries {
for _, message := range entry.Messages {
eventType := message.Values["event_type"].(string)
payloadStr := message.Values["payload"].(string)
// 🔍 Filtrar apenas messages_upsert
if eventType == "messages_upsert" {
var payload map[string]interface{}
json.Unmarshal([]byte(payloadStr), &payload)
fmt.Printf("📨 Nova mensagem: %v\n", payload)
handleMessage(payload)
}
// Acknowledge
rdb.XAck(ctx, streamKey, groupName, message.ID)
}
}
}
}
func handleMessage(payload map[string]interface{}) {
fmt.Printf("Processando: %v\n", payload)
// Sua lógica aqui
}
Consumir Múltiplos Tipos de Evento
Para processar vários tipos de evento da mesma instância:
// Filtrar múltiplos tipos
const allowedEvents = [
'messages_upsert',
'connection_update',
'qrcode_updated'
];
if (allowedEvents.includes(eventType)) {
console.log(`📨 Evento ${eventType}:`, payload);
switch(eventType) {
case 'messages_upsert':
await handleMessage(payload);
break;
case 'connection_update':
await handleConnection(payload);
break;
case 'qrcode_updated':
await handleQRCode(payload);
break;
}
await redis.xack(streamKey, groupName, id);
}
Dicas Importantes
- • Consumer Groups: Permitem múltiplos consumers processarem mensagens em paralelo
- • XACK: Sempre confirme o processamento com
xack()para não reprocessar - • Blocking: Use
BLOCKpara aguardar novas mensagens (evita busy-wait) - • Retry: Mensagens não confirmadas podem ser reprocessadas por outros consumers
- • MAXLEN ~10000: Streams mantêm cerca de 10 mil entradas ou
MEDIA_RETENTION_DAYS(padrão 7 dias), o que ocorrer primeiro
Webhooks
Webhooks enviam eventos via HTTP POST para URLs configuradas. Configure através do Dashboard em Configurações da Instância → Webhooks.
Formato da Requisição
POST https://seu-servidor.com/webhook
Content-Type: application/json
User-Agent: WAGO-API/1.0
X-Instance-Name: minha-instancia
X-Event-Type: messages_upsert
{
"instance": "minha-instancia",
"event": "messages_upsert",
"data": { ... },
"timestamp": 1710000000000
}
Exemplo Express.js
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const { instance, event, data } = req.body;
console.log(`📨 Evento: ${event} da instância: ${instance}`);
// Processar o evento
if (event === 'messages_upsert') {
console.log('Nova mensagem:', data.message);
}
// IMPORTANTE: Responder em até 10 segundos
res.status(200).json({ success: true });
});
app.listen(3000, () => {
console.log('✅ Webhook server rodando na porta 3000');
});
failed e não são reenviadas automaticamente; use POST /instances/{name}/webhooks/events/{eventId}/retry. No reenvio, a requisição traz também X-Retry-Count.Tipos de Eventos
Por padrão, Redis Streams e WebSocket publicam connection_update, qrcode_updated, messages_upsert, messages_update, status_upsert, conversation.update, waba.revoked, waba.recovered e webhook.stale. Os demais precisam ser habilitados em PUT /instances/{name}/config/streams-events ou websocket-events. Em webhooks, nenhum evento selecionado significa todos.
A lista de eventos configuráveis também está em GET /webhook/event-types.
| Evento | Quando ocorre |
|---|---|
messages_upsert | Nova mensagem recebida ou enviada |
messages_update | Status ou edição de mensagem |
undecryptable_message | Mensagem que não pôde ser descriptografada |
conversation.update | Conversa atualizada |
status_upsert | Novo status publicado |
connection_update | Mudança de conexão. No logout, vem com state: "logout" |
qrcode_updated | Novo QR Code disponível |
stream_replaced | Sessão substituída por outro dispositivo |
temporary_ban | Banimento temporário |
client_outdated | Cliente desatualizado |
keepalive_timeout / keepalive_restored | Queda e retorno do keepalive |
history_sync | Sincronização de histórico |
offline_sync_preview / offline_sync_completed | Sincronização de mensagens offline |
chat_presence | Contato digitando |
presence | Online ou offline |
user_about / picture | Recado e foto de perfil alterados |
media_retry | Nova tentativa de mídia |
joined_group | Entrada em grupo |
newsletter_join / newsletter_leave / newsletter_mute_change / newsletter_live_update / newsletter_message_meta | Eventos de canais |
identity_change | Identidade do contato alterada |
blocklist / privacy_settings | Bloqueios e privacidade alterados |
pairing_code | Código de pareamento gerado |
message_read / message_deleted / message_edited | Leitura, exclusão e edição de mensagem |
token.expired / waba.revoked / waba.recovered / webhook.stale | Saúde da conta oficial |
Exemplos Práticos
Bot de Auto-Resposta
// WebSocket
ws.on('message', async (data) => {
const event = JSON.parse(data);
if (event.routingKey?.endsWith('.messages_upsert')) {
const msg = event.data.message;
// Ignorar mensagens enviadas por mim
if (msg.key.fromMe) return;
const text = msg.message?.conversation || '';
const from = msg.key.remoteJid;
// Responder "oi" quando receber "olá"
if (text.toLowerCase().includes('olá')) {
await fetch('http://localhost:3003/instances/minha-instancia/send/text', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'SUA_API_KEY'
},
body: JSON.stringify({
to: from,
message: 'Oi! Como posso ajudar?'
})
});
}
}
});
Salvar Mensagens no Banco de Dados
import asyncio
import websockets
import json
import sqlite3
# Conectar ao banco de dados
conn = sqlite3.connect('mensagens.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS mensagens
(id TEXT, remetente TEXT, texto TEXT, timestamp INTEGER)''')
async def salvar_mensagens():
uri = "ws://localhost:3003/websocket/minha-instancia?token=SUA_API_KEY"
async with websockets.connect(uri) as websocket:
async for message in websocket:
event = json.loads(message)
if event.get('routingKey', '').endswith('.messages_upsert'):
data = event['data']['message']
msg_id = data['key']['id']
from_jid = data['key']['remoteJid']
text = data['message'].get('conversation', '')
timestamp = data['messageTimestamp']
c.execute(
"INSERT INTO mensagens VALUES (?, ?, ?, ?)",
(msg_id, from_jid, text, timestamp)
)
conn.commit()
print(f"💾 Mensagem salva: {text[:30]}...")
asyncio.run(salvar_mensagens())
Precisa de ajuda?
Acesse o Dashboard para configurar eventos ou consulte a documentação da API.