GET /events

Para: Dev — Conferido em 2026-10-18.

GET /v1/events devolve os eventos da sua operação, em ordem de criação, com o mesmo envelope do webhook. É a rede de segurança dos webhooks: com um job a cada 5 minutos você recupera qualquer evento perdido, esgotado na DLQ, não assinado ou anterior ao cadastro da URL.

O que é

Uma listagem paginada por cursor, só de leitura, com a mesma autenticação das demais rotas da API (Autenticação HMAC). Ela exige uma chave com escopo de leitura e devolve apenas eventos da operação da chave: um evento de outra operação nunca aparece.

GET /v1/events?after=evt_5a9c1f0e7b3d4c28a6e1f4b07d92c385&limit=200
GET /v1/events?object_id=dep_0198b7c24d5e7a1b8c2d3e4f5a6b7c8d
Parâmetro Regra
after Id de evento (evt_…) da sua operação. Devolve os eventos criados depois dele. Sem after, a listagem começa no primeiro evento da operação.
object_id Filtra os eventos de um objeto (dep_… ou wdr_…). Aceita letras, dígitos, _ e -, de 1 a 64 caracteres.
limit De 1 a 200; padrão 50.

A resposta é { "data": [Evento…], "next_cursor": "evt_…" | null }. next_cursor traz o id do último evento da página quando há mais eventos depois dele; use-o como after da próxima chamada. null significa que acabou. Veja o formato do evento em Envelope.

Quando usar

Rode um job a cada 5 minutos, a partir do último evento que você processou, e guarde esse cursor no seu banco. Esse job fecha as lacunas de:

  • webhook perdido por falha de rede ou queda do seu receptor;
  • evento na DLQ depois de 72 h sem 2xx;
  • evento que não chegou por webhook porque o tipo não está na sua assinatura (event_not_subscribed);
  • evento anterior ao cadastro da URL (no_endpoint);
  • evento de ambiente diferente do da URL cadastrada.

Use também object_id para investigar um objeto específico: "o que o Dracma registrou para este depósito?".

Eventos filtrados continuam aqui

A assinatura por tipo de evento só controla o que sai por webhook. GET /events devolve todos os eventos do catálogo, tenham ou não sido entregues. Se você assina só alguns tipos, o job precisa tratar todos.

Como funciona

Ordem, cursor e página

Os eventos vêm em ordem de criação (desempate estável por id). O cursor é o id de um evento: o Dracma busca a posição desse evento e devolve os posteriores, de modo que novos eventos criados enquanto você pagina entram no fim e não deslocam as páginas. Cada evento é gravado na mesma transação que muda o estado do objeto, então a lista não perde uma transição.

sequenceDiagram participant J as Seu job (a cada 5 min) participant A as API do Dracma J->>A: GET /v1/events com after = último processado, limit 200 A-->>J: data[] + next_cursor loop para cada evento J->>J: deduplica, consulta o objeto, aplica o efeito J->>J: grava o cursor (id do evento) end alt next_cursor presente J->>A: GET /v1/events com after = next_cursor else next_cursor nulo J->>J: fim da rodada end

Sem assinatura individual

Os eventos de GET /events não vêm assinados um a um: a autenticidade vem da sua chamada autenticada à API. Mesmo assim, passe cada evento pelo mesmo caminho do webhook, a partir da deduplicação: dedup por id, consulta do objeto (fetch-back), coerência, versão e efeito idempotente. O Dracma-Event-Id de um webhook é o id do mesmo evento em GET /events, então uma entrega por webhook e uma por listagem do mesmo evento são deduplicadas juntas.

Limite de taxa

GET /events está no balde de listagem: 5 requisições por segundo por chave. Pagine com limit=200 e uma cadência calma; não varra tudo a cada execução do job. Ao exceder o limite você recebe 429 com Retry-After; veja Limites e quotas.

Retenção

Não disponível

O Dracma não publica um prazo de retenção para os eventos de GET /events, e portanto não o garante. Mantenha o job de 5 minutos funcionando para nunca depender de reler eventos antigos, e guarde no seu banco o que precisar auditar.

Exemplos

Resposta com dois eventos do mesmo depósito (dados fictícios; a resposta de produção traz até limit eventos):

{
  "data": [
    {
      "id": "evt_686599cbb91280cbb34334f16f643047",
      "type": "deposit.pending",
      "created_at": "2026-10-18T14:00:02Z",
      "operation_id": "0198b7c2-3d4e-7f50-8a61-92b3c4d5e6f7",
      "object_id": "dep_0198b7c24d5e7a1b8c2d3e4f5a6b7c8d",
      "object_version": 2,
      "data": {
        "object": {
          "id": "dep_0198b7c24d5e7a1b8c2d3e4f5a6b7c8d",
          "external_id": "pedido-10482",
          "status": "pending",
          "version": 2,
          "amount": {
            "amount": "10000",
            "currency": "BRL"
          },
          "paid_amount": null,
          "method": "pix",
          "player_ref": "jogador-7731",
          "payer": null,
          "instructions": {
            "pix": {
              "copy_paste": "00020126580014br.gov.bcb.pix0136exemplo-chave-ficticia5204000053039865406100.005802BR5909EXEMPLO6009SAO PAULO62070503***6304ABCD",
              "expires_at": "2026-10-18T14:30:00Z"
            }
          },
          "failure_code": null,
          "review_reasons": [],
          "created_at": "2026-10-18T14:00:00Z",
          "updated_at": "2026-10-18T14:00:02Z",
          "paid_at": null,
          "expires_at": "2026-10-18T14:30:00Z"
        }
      }
    },
    {
      "id": "evt_e12d177892007213962d29c4b3b60580",
      "type": "deposit.paid",
      "created_at": "2026-10-18T14:05:13Z",
      "operation_id": "0198b7c2-3d4e-7f50-8a61-92b3c4d5e6f7",
      "object_id": "dep_0198b7c24d5e7a1b8c2d3e4f5a6b7c8d",
      "object_version": 3,
      "data": {
        "object": {
          "id": "dep_0198b7c24d5e7a1b8c2d3e4f5a6b7c8d",
          "external_id": "pedido-10482",
          "status": "paid",
          "version": 3,
          "amount": {
            "amount": "10000",
            "currency": "BRL"
          },
          "paid_amount": {
            "amount": "10000",
            "currency": "BRL"
          },
          "method": "pix",
          "player_ref": "jogador-7731",
          "payer": {
            "document": {
              "type": "CPF",
              "number": "12345678909",
              "country": "BR"
            },
            "name": "Maria Exemplo",
            "matches_player": true
          },
          "instructions": {
            "pix": {
              "copy_paste": "00020126580014br.gov.bcb.pix0136exemplo-chave-ficticia5204000053039865406100.005802BR5909EXEMPLO6009SAO PAULO62070503***6304ABCD",
              "expires_at": "2026-10-18T14:30:00Z"
            }
          },
          "failure_code": null,
          "review_reasons": [],
          "created_at": "2026-10-18T14:00:00Z",
          "updated_at": "2026-10-18T14:05:13Z",
          "paid_at": "2026-10-18T14:05:12Z",
          "expires_at": "2026-10-18T14:30:00Z"
        }
      }
    }
  ],
  "next_cursor": "evt_e12d177892007213962d29c4b3b60580"
}

Resposta de 400 para um after desconhecido ou de outra operação (o Dracma não confirma se o id existe em outra operação):

{
  "type": "https://docs.dracma.test/errors/validation_failed",
  "title": "validation_failed",
  "status": 400,
  "code": "validation_failed",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "errors": [
    {
      "field": "after",
      "message": "parâmetro inválido",
      "reason": "unknown"
    }
  ]
}

Esqueleto do job (a função aplicar_evento é a mesma do receptor, a partir da deduplicação):

def rodada(banco, api):
    cursor = banco.ler_cursor()                     # último evt processado, ou None
    while True:
        params = {"limit": 200}
        if cursor:
            params["after"] = cursor
        pagina = api.get("/v1/events", params)      # com a sua chave
        for ev in pagina["data"]:
            aplicar_evento(ev)                      # dedup, fetch-back, coerência, versão, efeito
            banco.gravar_cursor(ev["id"])           # só depois de aplicar
            cursor = ev["id"]
        if pagina["next_cursor"] is None:
            break

Erros e casos de borda

Resposta Quando
400 validation_failed, errors[0].reason = "format" after fora do formato evt_ mais letras e dígitos, ou object_id com caracteres inválidos.
400 validation_failed, errors[0].reason = "unknown" after de um evento que não existe ou que pertence a outra operação.
400 validation_failed, errors[0].reason = "range" limit fora de 1 a 200 ou não numérico.
401 Chave ou assinatura da chamada inválida.
429 Mais de 5 requisições por segundo na listagem. Respeite Retry-After.
503 Dependência indisponível. Tente de novo; a resposta traz Retry-After.
  • Cursor perdido: se você perdeu o cursor, recomece de um evento conhecido ou do início e deixe a deduplicação descartar o que já foi aplicado. É seguro, só mais lento.
  • Lista vazia com next_cursor nulo significa que não há eventos novos.
  • Eventos de objetos terminais continuam na lista; o job não deve tratá-los como novos efeitos se a versão já foi aplicada.
  • Timestamps: o created_at de eventos de saque pode trazer frações de segundo. Veja Envelope.

Veja também