Skip to main content
Importante (v0.11.0): mudou o Idempotency-Key em reenvios manuais.Toda entrega outbound da Garu manda um header Idempotency-Key. Em entregas normais e em auto-retries, o valor é o payload.id do evento (evt_…). Em reenvios manuais (dashboard “Reenviar” ou POST /api/v1/webhook-events/:uuid/resend), o valor passa a ser resend_<uuid> — uma chave HTTP distinta da entrega original.Dois cenários para o seu handler:
  • Você deduplica pelo valor bruto do header Idempotency-Key (padrão recomendado): não precisa mudar código. resend_… é uma chave nova, passa naturalmente pelo seu cache de dedup como entrega fresca. Mas atenção: você vai começar a receber reenvios manuais que, pré-v0.11.0, eram silenciosamente descartados pelo seu próprio dedup. Sua idempotência de negócio (orderId, transactionId) precisa absorver isso.
  • Você deduplica pelo payload.id (ou normaliza/strippa o header antes de comparar): precisa atualizar. O payload.id é idêntico entre original e clone, então um reenvio manual seria descartado como duplicata. Veja o código antes/depois abaixo.

Visão Geral

Acontece: o cliente jura que não recebeu o webhook do pagamento, ou seu endpoint ficou meia hora fora do ar e perdeu uma rajada de eventos. A Garu guarda todo evento de webhook emitido — entregue ou não — e você pode listar, inspecionar e reenviar qualquer um pela API, SDK, CLI ou direto pelo seu agente de IA. Hoje existem dois endpoints de reenvio. Use /resend para tudo a partir de agora — o /retry segue funcionando mas tem dois problemas que /resend resolve.
Por que dois endpoints? O /retry veio antes (v0.10.3) e mutava o evento em lugar. Quando começamos a expor o reenvio para automação e agentes, virou claro que perder o histórico de tentativas do original é ruim para auditoria, e que o Idempotency-Key precisava distinguir reenvio manual de auto-retry. /resend (v0.11.0) resolve as duas coisas. Mantemos /retry ativo por compatibilidade — se você já automatizou em cima dele, não quebra.

Endpoints

Os endpoints aceitam chave de API do seller (sk_test_… / sk_live_…) ou sessão JWT do dashboard. Use a chave de API para automação; o dashboard usa o JWT por baixo dos panos.

Status do evento

Toda entrega de webhook tem um destes três status:
É success, não delivered. Se você consumia delivered em alguma versão preview, ajuste — a API e os tipos do SDK usam success em todos os lugares.

Mudança no Idempotency-Key

A Garu sempre mandou um header Idempotency-Key nos POSTs para o seu endpoint. O que mudou na v0.11.0: O ponto crítico: /resend e o botão “Reenviar” agora mandam uma chave HTTP distinta da chave da entrega original. Três caminhos no seu handler:
  • Dedup pelo valor bruto do header: já trata resend_<uuid> como entrega nova naturalmente — resend_X nunca colide com evt_Y. Nenhuma mudança de código. Mas pré-v0.11.0 o resend chegava com a mesma chave do original e seu dedup descartava silenciosamente; agora ele passa, e sua idempotência de negócio precisa segurar.
  • Dedup pelo payload.id: descarta o resend, porque payload.id é idêntico entre original e clone. Atualize.
  • Dedup com normalização do header (strip de prefixo, regex, comparações truncadas): pode esconder a distinção entre evt_… e resend_…. Atualize.
Os exemplos abaixo cobrem o caso payload.id — o mais comum entre handlers que efetivamente quebram com a mudança.

Código do receiver — antes/depois

Idempotência de header ≠ idempotência de negócio. O dedup de header (qualquer um dos fixes acima) protege contra retries de rede e auto-retries. Não protege contra dois operadores clicando “Reenviar” em sequência — cada clique gera um clone novo com Idempotency-Key: resend_<uuid> distinto, então passam os dois. A regra de “não creditar o mesmo pedido duas vezes” precisa de uma chave do seu domínio (orderId, transactionId, etc.) num passo separado, depois do dedup de header.

Fluxo: listar com falha → escolher → reenviar

Limite de chamadas

/resend e /retry têm rate limit de 20 requisições por minuto por IP cada. É o suficiente para reprocessar um lote considerável sem pesar nos seus endpoints — e impede que um script em loop infinito martele um receiver que está fora do ar.
Listou 200 eventos para reprocessar? Coloque um setTimeout/sleep curto entre as chamadas (≥ 3s entre cada uma mantém você bem abaixo do limite) ou processe em lotes de 20 por minuto.

O que /resend faz por dentro

  1. Cria uma linha nova na tabela de eventos, com manualResendOf apontando para o original.
  2. Copia o payload exatamente como foi gerado — payload.id (evt_…) é preservado.
  3. Calcula o Idempotency-Key outbound como resend_<uuid_do_clone> antes do POST para seu endpoint.
  4. Dispara a entrega imediatamente, em background. Se falhar, aplica a retry policy padrão (1min, 5min, 30min, 2h, 8h, 16h até failed).
  5. Não toca no original. Ele fica para sempre no status terminal (success / failed), com todas as tentativas e respostas históricas preservadas. É o que você quer para auditoria.

Quando reenviar (e quando não)

Janelas de indisponibilidade curtas (deploy, restart, k8s drain) frequentemente passam dos 26h da retry policy se forem aninhadas a outros incidentes. Reenviar é a forma de recuperar a fila sem precisar de plumbing manual.
Suporte clássico. Localiza o evento via filtro eventType + janela de tempo, confirma que o status está failed (ou success mas o cliente não processou), reenvia com /resend.
/resend funciona em eventos success também. Dispara um clone para o mesmo endpoint, útil para validar que mudanças no handler ainda processam o payload corretamente.
Reenviar duplica a notificação. Sua idempotência de negócio (chave do seu domínio, não do header) deve absorver, mas é trabalho à toa.
Se seu endpoint devolve 4xx por bug ou validação, o reenvio vai falhar de novo. Corrija o handler primeiro e depois reenvie.

Permissões

Por papel padrão da Garu:
  • Owner / Administrator / Developer: listar, ver e reenviar.
  • Support: listar e ver; reenvio depende de permissão webhook:edit.
  • View Only: somente listar e ver.
Chaves de API herdam as permissões do seller que as criou.

Próximos passos

API: /resend

Referência completa do endpoint de reenvio por clone

API: /retry (legado)

Endpoint legado que muta o original in place

Detalhes do evento

Inspecione manualResendOf, tentativas e payload

Códigos de falha

Roteie falhas de pagamento pelo enum normalizado