Pular para o conteúdo principal

Entrega e retentativas

As regras​

ParâmetroValor
SucessoQualquer resposta 2xx
Timeout20 segundos
Tentativas6 — uma imediata e cinco retentativas
Intervalos60s · 5min · 30min · 2h · 6h
Desativação automáticaApós 50 falhas consecutivas

Qualquer coisa que não seja 2xx conta como falha: 3xx, 4xx, 5xx, timeout, erro de DNS, TLS inválido. Redirecionamentos não são seguidos — um 301 é falha.

Esgotadas as seis tentativas, a entrega fica em failed e não é mais tentada automaticamente. O tempo total entre a primeira e a última é de cerca de 9 horas.

Por que responder rápido​

O timeout vale para a resposta inteira, headers e corpo. Um handler que processa antes de responder tem 20 segundos para tudo — e um pico de tráfego derruba isso fácil.

// Errado
app.post('/webhooks', async (req, res) => {
await salvarNoBanco(req.body);
await chamarCrm(req.body);
await notificarEquipe(req.body);
res.sendStatus(200); // pode nunca chegar a tempo
});

// Certo
app.post('/webhooks', (req, res) => {
res.sendStatus(200);
fila.enfileirar(req.body);
});

Idempotência​

Como uma entrega pode ser repetida, o mesmo evento vai chegar mais de uma vez. Não é exceção: é o comportamento normal quando o seu servidor responde devagar ou oscila.

Duas chaves de deduplicação:

  • X-Joinotify-Delivery — estável entre as retentativas daquela entrega
  • O wamid da mensagem — estável mesmo se você reenviar manualmente
const jaProcessado = await redis.set(`webhook:${deliveryId}`, '1', { NX: true, EX: 86400 });
if (!jaProcessado) {
return; // repetição, ignore
}

Desativação automática​

Cada falha incrementa failureCount; qualquer sucesso zera. Chegando a 50 falhas consecutivas, o endpoint é desativado: active: false, disabledAt preenchido, e as entregas seguintes são marcadas como failed com "Endpoint is inactive.".

Isso protege os dois lados — nós paramos de bater num servidor que claramente não está lá, e você não recebe uma avalanche quando ele voltar.

# Reativar: o contador zera junto
curl -X PATCH https://api.joinotify.com/webhook-endpoints/whe_01J8ZK... \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "active": true }'

Monitore failureCount antes de chegar a 50. Um endpoint com 30 falhas já está com problema.

Investigando​

curl 'https://api.joinotify.com/webhook-endpoints/deliveries?status=failed&limit=20' \
-H 'Authorization: Bearer sk_live_xxx'
{
"data": [
{
"id": "whd_01J8ZM...",
"endpointId": "whe_01J8ZK...",
"field": "messages",
"status": "failed",
"attempt": 6,
"responseStatus": 500,
"responseBody": "Internal Server Error",
"error": null,
"durationMs": 18412,
"nextRetryAt": null,
"createdAt": "2026-07-27T14:02:11.482Z"
}
]
}
CampoO que revela
responseStatusO que o seu servidor devolveu — null significa que nem respondeu
responseBodyO corpo da resposta, truncado em 2000 caracteres
errorFalha de transporte: DNS, TLS, timeout
durationMsPerto de 20000 indica timeout
attemptEm qual das seis tentativas parou

responseStatus: null com error preenchido é problema de rede ou certificado, não de código. durationMs perto de 20000 é handler lento.

Reenviando​

curl -X POST https://api.joinotify.com/webhook-endpoints/deliveries/whd_01J8ZM.../redeliver \
-H 'Authorization: Bearer sk_live_xxx'

Cria uma entrega nova em vez de reciclar a antiga, preservando o histórico da falha original. O ciclo de seis tentativas recomeça.

Para reprocessar em massa depois de um incidente, liste as falhas do período e reenvie uma a uma:

curl 'https://api.joinotify.com/webhook-endpoints/deliveries?status=failed&limit=200' \
-H 'Authorization: Bearer sk_live_xxx' \
| jq -r '.data[].id' \
| while read id; do
curl -sS -X POST "https://api.joinotify.com/webhook-endpoints/deliveries/$id/redeliver" \
-H 'Authorization: Bearer sk_live_xxx' > /dev/null
done

Rede de segurança​

Mesmo com retentativas, um evento pode se perder — endpoint fora do ar por mais de nove horas, por exemplo. Para dados críticos, vale uma reconciliação periódica contra GET /events, que guarda o histórico bruto independentemente das entregas.