Como implementar idempotência em Webhooks

Implemente idempotência em Webhooks

Os Webhooks do Asaas utilizam o modelo de entrega at least once. Por isso, o mesmo evento pode ser enviado mais de uma vez.

Use o campo id do evento para identificar duplicidades e impedir que uma nova entrega execute novamente a mesma regra de negócio.

Como funciona

Quando o Asaas reenvia o mesmo evento, o id permanece igual.

Exemplo:

{
  "id": "evt_05b708f961d739ea7eba7e4db318f621&368604920",
  "event": "PAYMENT_RECEIVED",
  "payment": {
    "id": "pay_080225913252"
  }
}

Sua aplicação deve tratar esse identificador como uma chave única.

%%{init: {"flowchart": {"nodeSpacing": 28,"rankSpacing": 32,"diagramPadding": 8,"padding": 10}}}%%
flowchart TD
    A["Receber o evento"] --> B{"ID já foi persistido?"}

    B --> BSim(("Sim"))
    B --> BNao(("Não"))

    BSim --> C["Responder HTTP 200"]
    BNao --> D["Persistir o evento"]

    D --> E["Responder HTTP 200"]
    E --> F["Processar em segundo plano"]
    F --> G["Marcar como processado"]

    classDef inicio fill:#DBEAFE,stroke:#2563EB,color:#1E3A8A,stroke-width:3px,font-size:17px
    classDef decisao fill:#FEF3C7,stroke:#D97706,color:#78350F,stroke-width:3px,font-size:17px
    classDef validacao fill:#E0F2FE,stroke:#0284C7,color:#0C4A6E,stroke-width:2px,font-size:17px
    classDef sucesso fill:#DCFCE7,stroke:#16A34A,color:#14532D,stroke-width:3px,font-size:17px

    classDef respostaSim fill:#22C55E,stroke:#15803D,color:#FFFFFF,stroke-width:3px,font-size:16px
    classDef respostaNao fill:#EF4444,stroke:#B91C1C,color:#FFFFFF,stroke-width:3px,font-size:16px

    class A inicio
    class B decisao
    class C,D,E,F validacao
    class G sucesso

    class BSim respostaSim
    class BNao respostaNao

    linkStyle default stroke:#94A3B8,stroke-width:2px
    linkStyle 1 stroke:#22C55E,stroke-width:4px
    linkStyle 2 stroke:#EF4444,stroke-width:4px

Estratégia recomendada: persista antes de processar

Crie uma estrutura que registre o id recebido e aplique uma restrição de unicidade nesse campo.

Exemplo em PostgreSQL:

CREATE TABLE asaas_events (
    id BIGSERIAL PRIMARY KEY,
    asaas_event_id TEXT UNIQUE NOT NULL,
    payload JSONB NOT NULL,
    status VARCHAR(16) NOT NULL
        CHECK (status IN ('PENDING', 'DONE'))
);

Ao receber um evento:

  1. tente persistir o id e o payload;
  2. se o id já existir, trate a requisição como duplicada;
  3. após confirmar a persistência, responda HTTP 200;
  4. processe os registros com status = 'PENDING' em segundo plano;
  5. após concluir a regra de negócio, altere o status para DONE.
⚠️

Atenção

Responda HTTP 200 somente após confirmar a persistência do evento.

Depois que a entrega for confirmada, sua aplicação não deve depender de um novo envio para recuperar um evento que não foi armazenado.

Exemplo em Node.js

const express = require('express');
const app = express();

app.post(
  '/asaas/webhooks/payments',
  express.json({ type: 'application/json' }),
  async (request, response) => {

    const body = request.body;
    const eventId = body.id;

    await client.query(
      `
      INSERT INTO asaas_events (
        asaas_event_id,
        payload,
        status
      )
      VALUES ($1, $2, 'PENDING')
      ON CONFLICT (asaas_event_id) DO NOTHING
      `,
      [eventId, body]
    );

    return response.status(200).json({
      received: true
    });
  }
);

app.listen(8000, () => console.log('Running on port 8000'));

Com a restrição UNIQUE, uma nova entrega do mesmo id não cria outro registro.

A regra de negócio deve ser executada por um Worker, Cron Job ou outro processo responsável por consumir os eventos pendentes.

Evite processar antes de persistir

Outra abordagem é executar a regra de negócio durante a própria requisição e registrar o evento como processado depois.

Esse fluxo aumenta o risco de:

  • timeout antes da resposta;
  • processamento simultâneo do mesmo evento;
  • execução da regra de negócio sem registro confiável de que o evento foi tratado.

Prefira reservar ou persistir o id de forma atômica antes de executar a regra de negócio.

Processamento em alto volume

Para integrações com grande volume, utilize uma fila ou broker adequado à arquitetura da aplicação, como:

  • Amazon SQS;
  • RabbitMQ;
  • Kafka.

O princípio permanece o mesmo: o id do evento deve impedir que uma nova entrega gere outro processamento da mesma operação.

Quando a ordem dos eventos for necessária

Idempotência evita duplicidades, mas não define a ordem de entrega.

Se a lógica da aplicação depender da sequência em que os eventos ocorreram, utilize o tipo de envio adequado e preserve essa ordem também durante o processamento interno.

Consulte os Tipos de envio.

Valide a implementação

Teste o endpoint enviando ou provocando duas entregas com o mesmo id.

O resultado esperado é:

  • apenas um registro para o id;
  • as duas requisições recebem HTTP 200;
  • a regra de negócio é executada uma única vez.

Use os Logs de Webhooks para acompanhar as tentativas de entrega.

Próximos passos


Did this page help you?