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:
- tente persistir o
ide o payload; - se o
idjá existir, trate a requisição como duplicada; - após confirmar a persistência, responda
HTTP 200; - processe os registros com
status = 'PENDING'em segundo plano; - após concluir a regra de negócio, altere o status para
DONE.
AtençãoResponda
HTTP 200somente 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.
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
Updated 3 days ago
