Pular para o conteúdo

Polling de eventos

A API de eventos lista o histórico de eventos da sua conta, com paginação. Use o polling no lugar do webhook ou para recuperar eventos perdidos durante uma indisponibilidade do seu endpoint.

Quando usar polling

O webhook é a forma principal de receber eventos em tempo real. O polling é recomendado em dois cenários:

  • Sem webhook configurado: consulte eventos regularmente para processar mensagens recebidas, confirmações de envio e demais eventos.
  • Recuperação após falha: após uma interrupção do seu endpoint de webhook, use o polling para buscar os eventos perdidos e reconciliar o estado do seu sistema.

Endpoint

GET /v1/events

Escopo obrigatório: events:read

Parâmetros de consulta

ParâmetroTipoDescrição
types[]string[]Filtrar por tipos de evento (OR), até 20 valores. Ex: types[]=message.received&types[]=message.sent
typestringFiltrar por um tipo de evento com suporte a wildcard. Ex: type=message.*
request_idstringFiltrar pelos eventos gerados por uma única requisição assíncrona (req_…). Útil para correlacionar o resultado de um POST (envio, resposta, edição ou exclusão) usando o X-Request-ID retornado no header da resposta. Reações não são correlacionáveis por aqui: o header devolvido no POST .../react não é um req_… válido; use o message_id da mensagem-alvo (veja Enviar reação). Ex: request_id=req_01993c5a1b24d3e9f01a2b3c4d5e6f70
number_aliasstringFiltrar pelos eventos de um único número ("main" ou o alias de um número extra). Ausente = todos os números da organização. Apenas os eventos sem número único (storage.* e media.unavailable) são excluídos quando este filtro é informado; message.quota.* é por número e é filtrável.
number_idstringFiltrar pelos eventos de um único número, pelo id (num_…). Combina com number_alias. Continua achando os eventos do número depois de uma renomeação, porque o filtro compara o id e não o alias. Um valor que começa com num_ mas não é um id válido responde 400 invalid_number_id.
starting_afterstringCursor para paginação progressiva. Passe o id do último item da página anterior.
ending_beforestringCursor para paginação retroativa. Passe o id do primeiro item da página atual.
limitintegerNúmero de eventos por página. Intervalo: 1 a 100. Padrão: 25.
created[gte]ISO-8601Retornar eventos criados a partir desta data e hora.
created[lte]ISO-8601Retornar eventos criados até esta data e hora.

Formato de resposta

Os resultados são ordenados por created_at DESC (mais recentes primeiro).

Status HTTP: 200
{
  "object": "list",
  "data": [
    {
      "id": "evt_01993c5a1b24d3e9f01a2b3c4d5e6f70",
      "type": "message.received",
      "organization_id": "org_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
      "number_alias": "main",
      "number_id": "num_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
      "created_at": "2026-06-01T13:00:00Z",
      "data": {
        "id": "msg_01993c5a1b24d3e9f01a2b3c4d5e6f70",
        "from": {
          "name": "Carlos Lima",
          "number": "5511977776666"
        },
        "to": {
          "alias": "main",
          "number": "5511973117056"
        },
        "preview": "Olá, gostaria de saber sobre o pedido",
        "content_type": "text",
        "direction": "inbound",
        "channel": "whatsapp",
        "origin": "native",
        "at": "2026-06-01T13:00:00Z"
      }
    },
    {
      "id": "evt_c00a44c3fc774698b0f130d40bb5f104",
      "type": "message.received",
      "organization_id": "org_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
      "number_alias": "main",
      "number_id": "num_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
      "created_at": "2026-06-02T01:55:14Z",
      "data": {
        "id": "msg_e95500cc03c1492e81e46a77c1f0d420",
        "from": {
          "contact_id": "ct_45f2ece8a66a4eeaa661aeb9b8e58c77",
          "name": "Ana Souza",
          "number": "5511988887777"
        },
        "to": {
          "group_id": "grp_e442553a56b14e2695f6acfd7d651b1a",
          "name": "TNT"
        },
        "preview": "Bom dia pessoal",
        "content_type": "text",
        "direction": "inbound",
        "channel": "whatsapp",
        "origin": "native",
        "at": "2026-06-02T01:55:14Z"
      }
    }
  ],
  "has_more": true
}

Eventos de grupo usam to.group_id (formato grp_{hex32}) em vez de to.number; o seu número que participa do grupo é identificado pelo number_alias do envelope. Mensagens individuais (1:1) omitem to.group_id. Veja Webhooks para detalhes.

Envelope da lista

CampoTipoDescrição
objectstringSempre "list".
dataarrayLista de eventos (no máximo limit itens).
has_morebooleantrue quando existem mais eventos além da página atual.

Envelope de cada evento

CampoTipoDescrição
idstringIdentificador único (evt_…). Use como chave de idempotência.
typestringTipo do evento (ex.: message.received).
organization_idstringIdentificador público da sua organização (org_{hex32}).
number_aliasstringIdentificador do número Turbo Notify ao qual o evento pertence: "main" para o número principal ou o alias de um número extra. É a string do alias, nunca um UUID. Presente em eventos ligados a um número específico: message.*, number.*, group.* e todos os contact.* (inclusive contact.quota.*) e message.quota.* (a cota de mensagens é por número). Omitido em storage.* e em media.unavailable: os eventos que não pertencem a um número único.
number_idstringIdentificador do número (num_…), logo depois do number_alias. Presente exatamente quando number_alias está presente. Nunca muda: é a chave para guardar. O number_alias é o alias que o número tinha quando o evento aconteceu.
created_atstringMomento de emissão do evento pelo Turbo Notify (quando o registro foi criado/publicado). ISO 8601 em UTC com sufixo Z e precisão de segundos (ex.: 2026-06-01T13:00:05Z). Distinto de data.at (o momento da ação no WhatsApp), quando presente.
dataobjectConteúdo específico do evento, idêntico ao entregue via webhook. Quando há uma ação real no WhatsApp, data.at traz esse momento (valor distinto de created_at), inclusive para eventos de mensagens enviadas (message.sent/replied/edited/deleted) e os number.* de ação real, cujo at é o momento real no WhatsApp, em geral anterior ao created_at. Nos eventos de falha, data.at é o momento em que a falha foi detectada. Eventos que não correspondem a uma ação no WhatsApp não têm at.

Nos eventos number.*, o data traz o id do número como primeiro campo (data.id), igual ao number_id do envelope.

Paginação por cursor

A paginação usa cursor, então as páginas não mudam quando chegam eventos novos. O cursor é o id de um evento.

Os eventos vêm do mais novo para o mais antigo, e é isso que decide qual cursor usar:

Você querUseDireção
Acompanhar o que acabou de acontecerending_beforeEventos mais novos que o cursor
Percorrer o históricostarting_afterEventos mais antigos que o cursor

Acompanhar eventos novos: peça o que chegou depois do último evento processado e, se a página vier cheia, desça até alcançá-lo.

acompanhar.sh
# Primeira consulta: sem cursor, traz os mais recentes
curl "https://api.turbonotify.com/v1/events?limit=100" \
-H "Authorization: Bearer SUA_CHAVE"
# Consultas seguintes: só o que chegou depois do último que você processou
curl "https://api.turbonotify.com/v1/events?limit=100&ending_before=evt_01993c5a1b24d3e9f01a2b3c4d5e6f70" \
-H "Authorization: Bearer SUA_CHAVE"
# Se essa resposta veio com 100 eventos, ainda há mais: continue a partir
# do ÚLTIMO evento dela até reencontrar o cursor que você já tinha.
curl "https://api.turbonotify.com/v1/events?limit=100&starting_after=evt_01993c5a1b24d3e9f01a2b3c4d5e6f99" \
-H "Authorization: Bearer SUA_CHAVE"

Percorrer o histórico: passe o id do último evento retornado em starting_after:

polling.sh
# Primeira página
curl "https://api.turbonotify.com/v1/events?limit=20" \
-H "Authorization: Bearer SUA_CHAVE"
# Próxima página (starting_after = id do último evento retornado)
curl "https://api.turbonotify.com/v1/events?limit=20&starting_after=evt_01993c5a1b24d3e9f01a2b3c4d5e6f70" \
-H "Authorization: Bearer SUA_CHAVE"

Filtrar por tipo de evento

Filtre os eventos que seu sistema precisa processar usando o parâmetro types[]:

polling-filtrado.sh
curl "https://api.turbonotify.com/v1/events?types[]=message.received&types[]=message.sent" \
-H "Authorization: Bearer SUA_CHAVE"

Filtrar por número

Como mensagens, contatos e grupos pertencem sempre a um número, você pode restringir o polling aos eventos de um único número via number_alias. Se o parâmetro estiver ausente, a resposta inclui os eventos de todos os números da organização.

polling-por-numero.sh
# Só os eventos do número principal
curl "https://api.turbonotify.com/v1/events?number_alias=main&limit=20" \
-H "Authorization: Bearer SUA_CHAVE"
# Só os eventos de um número extra
curl "https://api.turbonotify.com/v1/events?number_alias=vendas-sp&limit=20" \
-H "Authorization: Bearer SUA_CHAVE"

Intervalo recomendado

O polling usa o mesmo limite geral da API: 1000 requisições por segundo por organização. Acima disso a resposta é 429 Too Many Requests (rate_limit_exceeded). Consultar a cada poucos segundos fica muito abaixo desse teto.

Com volume alto, prefira webhooks para receber eventos em tempo real e reserve o polling para reconciliação.

Paridade com webhooks

O objeto data de cada evento retornado pela API é idêntico, byte a byte, ao data entregue via webhook para o mesmo evento. O envelope é o mesmo em ambos os canais:

CampoWebhookPolling
idpresentepresente
typepresentepresente
organization_idpresentepresente
number_aliaspresente (em eventos ligados a um número)presente (em eventos ligados a um número)
number_idpresente (em eventos ligados a um número)presente (em eventos ligados a um número)
created_atpresentepresente (mesmo valor)
dataidênticoidêntico

Na prática:

  • Sistemas que usam apenas polling processam os mesmos dados que sistemas baseados em webhook.
  • Sistemas híbridos podem usar a API para reconciliar eventos perdidos reaproveitando o mesmo código de processamento.
  • Catálogo completo de type e data: consulte a referência de eventos no documento de webhook.
  • As respostas dos convidados a um evento de calendário chegam como message.calendar_responded, também com type=message.*: veja Eventos de calendário.
  • Exemplos de payload por content_type (texto, mídia, localização, calendário): Exemplos por content_type.

A paridade inclui o campo data.mentions: quando uma mensagem menciona alguém, os eventos message.received/message.replied/message.sent/message.edited retornados pelo polling carregam exatamente a mesma lista mentions e o mesmo preview (cada token de menção conta como 1 caractere) entregues via webhook, byte a byte.

Recuperação após falha de webhook

Para recuperar eventos perdidos durante uma indisponibilidade do seu endpoint:

  1. Identifique a data e a hora aproximadas do início da falha.
  2. Consulte GET /v1/events?created[gte]=<timestamp_da_falha>&limit=100 e, enquanto has_more for true, continue com starting_after igual ao id do último evento da página (veja Paginação por cursor). Uma única página traz só os eventos mais recentes do período.
  3. Processe apenas os eventos não vistos usando o id de cada evento como chave de idempotência.

Uma mensagem específica também dá para recuperar lendo-a: a listagem e a consulta de uma mensagem trazem os mesmos campos do evento message.* daquela mensagem (id, number_alias, number_id, from, to, direction, content_type, channel, origin, at). Se você perdeu um webhook, leia a mensagem por Consultar mensagem ou filtre Listar mensagens por grupo, contato ou direção e obtenha a mesma informação.

recuperacao.sh
curl "https://api.turbonotify.com/v1/events?created[gte]=2026-06-01T08:00:00-03:00" \
-H "Authorization: Bearer SUA_CHAVE"

Retenção de eventos

GET /v1/events lista os eventos pelo horizonte do próprio registro de eventos, 90 dias, fixo na plataforma: o prazo de retenção de conteúdo do seu plano não esconde eventos. O que expira nesse prazo é o conteúdo dos eventos message.*: data.preview passa a trazer um marcador de conteúdo removido e data.transcription vira {"kind": "unavailable", "reason": "content_retention_expired"}, em até 15 minutos depois do prazo (veja Retenção das mensagens).

Erros possíveis

StatusCódigo de erroDescrição
400invalid_event_idO cursor fornecido em starting_after ou ending_before não é um id de evento válido (evt_ + 32 hex). Um cursor bem formado mas inexistente não gera erro. O filtro é ignorado.
400invalid_request_idO request_id informado não é um id de requisição válido (req_ + 32 hex). O X-Request-ID devolvido em respostas GET é um UUID de correlação, não um req_…. Só o header devolvido pelos POST assíncronos serve como filtro aqui.
401not_authenticatedA chave de API está inválida ou ausente.
403insufficient_scopeA chave de API não possui o escopo events:read (resposta inclui required_scope).
422validation_errorUm parâmetro de consulta não passou na validação (por exemplo limit fora do intervalo de 1 a 100 ou created[gte]/created[lte] fora do formato ISO 8601). A resposta traz details.errors com os campos rejeitados.
429rate_limit_exceededLimite de requisições ultrapassado. Aguarde antes de tentar novamente.
500internal_errorErro interno inesperado no servidor.