Eventos (SSE)
Um stream Server-Sent Events de mão única para estado ao vivo. Use no lugar de polling para QR codes, mudanças de estado de canal e atividade de mensagens.
Endpoint
Autentique via Authorization: Bearer … ou query string ?token=…. (O EventSource do browser não consegue definir headers; o caminho via query-token é para esse caso.)
curl -s "$WHATISUP_API/v1/events" \
-H "Authorization: Bearer $WHATISUP_API_KEY"Para código de browser, EventSource cuida da reconexão por você. Todo frame
carrega um evento SSE nomeado, então addEventListener('message', …) nunca
dispara — escute por nome:
const es = new EventSource(`${WHATISUP_API}/v1/events?token=${WHATISUP_API_KEY}`);
for (const name of [
'channel.status_changed', 'channel.qr', 'channel.connected',
'channel.disconnected', 'channel.needs_attention',
'message.received', 'message.sent', 'message.status',
'contact.resolved',
]) {
es.addEventListener(name, (e) => {
const data = JSON.parse(e.data);
console.log(name, data.channel_id, data);
});
}
// Heartbeat. Ignore, ou use como checagem de liveness.
es.addEventListener('ping', () => {});
es.onerror = () => { /* o EventSource retenta sozinho */ };Filtragem
Não existe filtragem no servidor. /v1/events não aceita nenhum query param
de filtro — um channel_id= ou events= que você passe é ignorado, não
respeitado. Filtre no cliente pelo nome do frame e por data.channel_id.
Duas coisas estreitam o stream por você, ambas implícitas:
| Estreitamento | Como |
|---|---|
| Escopo de conta | Você só recebe eventos do seu próprio customer_id. |
| Escopo de canal | Uma chave de API presa a um canal recebe só os eventos daquele canal. |
Se você quer um canal só e tem uma chave de conta inteira, emita uma chave presa
ao canal (POST /v1/channels com issue_scoped_key: true) e faça o stream com ela.
Formato do frame
SSE padrão, com evento nomeado e id em todo frame:
: stream-open
event: channel.qr
id: 1
data: {"channel_id":"8d653c66-e4ff-43ee-97da-3de5ad5680d4","qr_png_base64":"iVBORw0KGgo...","expires_at":1779425257573}
event: channel.connected
id: 2
data: {"channel_id":"8d653c66-e4ff-43ee-97da-3de5ad5680d4","phone_number":"5511999999999"}
event: ping
id: 3
data: {"ts":1779425282573}
: stream-opené um comentário único enviado na abertura do stream.- O keepalive é um evento nomeado
pingde verdade a cada 25 segundos — não é linha de comentário. A maioria dos reverse-proxies fecha por ociosidade aos 30s. id:é um contador por conexão começando em 1. Não é cursor de retomada:Last-Event-IDnão é respeitado, e reconectar recomeça do 1.
O que vem na rede
O vocabulário do SSE não é igual ao dos webhooks. Três nomes existem só
aqui, vários eventos de webhook nunca aparecem aqui, e qr.updated (webhook)
se chama channel.qr no SSE. Não escreva um handler baseado nos nomes de
webhook e aponte para este stream.
| Evento SSE | Significado | Equivalente webhook |
|---|---|---|
channel.status_changed | Estado do ciclo de vida mudou. Carrega status. | channel.status |
channel.qr | Novo QR disponível (qr_png_base64, expires_at). | qr.updated |
channel.connected | Pareamento concluído. | channel.connected |
channel.disconnected | Sessão encerrada (com reason). | channel.disconnected |
channel.needs_attention | Canal travado, precisa de humano (reason, at). | (só SSE) |
message.received | Entrada vinda do WhatsApp. | message.received |
message.sent | Saída entregue ao WhatsApp. | message.sent |
message.status | Estado de entrega mudou (sent/delivered/read/played/failed). | message.status |
group.created, group.updated, group.participant_added, group.participant_removed, group.admin_promoted, group.admin_demoted | Mudanças de grupo. | mesmos nomes |
contact.resolved | Contato só-LID resolveu para um JID de telefone. | contact.resolved |
ping | Keepalive a cada 25s. | (só SSE) |
Não existe message.delivered nem message.failed neste stream — os dois são
frames message.status discriminados pelo campo status. Eventos de chat,
order, cart, story, call e presence são só de webhook; não aparecem no SSE.
O objeto data de todo frame carrega channel_id no topo e o payload do evento
achatado ao lado — não há aninhamento envelope/data e não há assinatura. Os
formatos de payload batem com Webhooks → Event payloads;
o invólucro não.
Limites
- 10 streams simultâneos por conta. O 11º recebe
429comcode: "sse_connection_limit". - Abrir um stream consome um token de rate limit como qualquer chamada autenticada.
- Sem buffer de replay: eventos emitidos enquanto você estava desconectado são perdidos. Use webhooks para o que não pode ser perdido.
Quando usar SSE vs webhooks
| Quer | Use |
|---|---|
| App first-party: dashboard, ferramenta interna, sua própria UI | SSE — sem webhook público para manter |
| Third-party: endpoint HTTPS de outra pessoa | Webhooks — durável, com retentativa, auditado |
| Ambos | Ambos. Eles não conflitam. |
Webhooks são o registro durável (com retentativa, logado, replayable). SSE é o feed ao vivo (best-effort, sem buffer — se você desconectar no meio de um evento, você o perde).