Skip to content
WhatIsUp.dev

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

GET/v1/eventsBearer · API key

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.)

Stream SSE — mantenha a conexão aberta. A maioria das linguagens tem um cliente HTTP de streaming; os trechos abaixo assumem o mais simples disponível por ecossistema.
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:

EstreitamentoComo
Escopo de contaVocê só recebe eventos do seu próprio customer_id.
Escopo de canalUma 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 ping de 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-ID nã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 SSESignificadoEquivalente webhook
channel.status_changedEstado do ciclo de vida mudou. Carrega status.channel.status
channel.qrNovo QR disponível (qr_png_base64, expires_at).qr.updated
channel.connectedPareamento concluído.channel.connected
channel.disconnectedSessão encerrada (com reason).channel.disconnected
channel.needs_attentionCanal travado, precisa de humano (reason, at).(só SSE)
message.receivedEntrada vinda do WhatsApp.message.received
message.sentSaída entregue ao WhatsApp.message.sent
message.statusEstado 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_demotedMudanças de grupo.mesmos nomes
contact.resolvedContato só-LID resolveu para um JID de telefone.contact.resolved
pingKeepalive 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 429 com code: "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

QuerUse
App first-party: dashboard, ferramenta interna, sua própria UISSE — sem webhook público para manter
Third-party: endpoint HTTPS de outra pessoaWebhooks — durável, com retentativa, auditado
AmbosAmbos. 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).