Referência

Retries e checklist

Como responder às entregas, lidar com reenvios, garantir idempotência e ir para produção com segurança.

Resposta esperada

HTTPEfeito
2xx (200–299)Entrega considerada sucesso
Outro / timeout / redeFalha → retry

Resposta ideal:

HTTP/1.1 200 OK
Content-Type: application/json

{"received":true}

Retries

A HyzePay tenta de novo automaticamente com backoff:

TentativaAtraso
1imediato
2~2 s
3~8 s

Timeout por tentativa: 15 segundos. Cada tentativa gera registro de auditoria no painel.

Boas práticas

  • Responda rápido(< 2 s se possível)
  • Processamento pesado: grave o evento e processe em background
  • Não retorne 5xx se o evento já foi processado — retorne 200 (idempotência)

Idempotência

Use como chave única, nesta ordem:

  1. event.id (v2) / event_id (v1)
  2. Ou o par (payment.id, type)

Guarde em uma tabela e ignore duplicatas — evita liberar o produto duas vezes se houver retry.

CREATE TABLE processed_webhook_events (
  event_id TEXT PRIMARY KEY,
  processed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

Exemplo completo mínimo (Node.js)

import express from "express";
import { createHmac, timingSafeEqual } from "crypto";

const SECRET = process.env.HYZEPAY_WEBHOOK_SECRET!;
const seen = new Set<string>(); // use Redis/DB em produção

function verify(raw: string, header: string) {
  // … igual seção Verificação e Segurança …
}

const app = express();
app.post("/webhooks/hyzepay", express.raw({ type: "application/json" }), async (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verify(raw, req.header("x-hyzepay-signature") || "")) {
    return res.status(401).end();
  }

  const event = JSON.parse(raw);
  const eventId = event.id || event.event_id;
  if (seen.has(eventId)) return res.json({ ok: true, duplicate: true });
  seen.add(eventId);

  const type = event.type || event.event;
  const obj = event.data?.object ?? event.data;

  if (type === "payment.paid" && obj?.status === "paid") {
    await fulfillOrder(obj.external_id || obj.id, obj);
  }

  return res.json({ received: true });
});
Para teste real use o botão Enviar teste no painel. Assinatura inválida deve ser rejeitada pelo seu código.

Checklist de produção

  • URL HTTPS pública (não localhost em produção)
  • Secret whsec_… no env (HYZEPAY_WEBHOOK_SECRET) — nunca no frontend
  • Validação de assinatura + tolerância de timestamp
  • Handler de payment.paid com checagem de status e valor
  • Idempotência por event.id
  • Resposta 2xx rápida
  • Logs de X-HyzePay-Event-Id e X-HyzePay-Delivery
  • Evento test ok no painel
  • Webhook inscrito em payment.paid (e outros que precisar)
  • Ambiente de homologação com secret separado

Erros comuns

ProblemaCausaSolução
Assinatura sempre inválidaBody re-serializadoUse o raw body original
Assinatura inválidaSecret errado / espaçosConfira o whsec_… na criação
Timestamp foraClock do servidor erradoNTP; tolerância só se necessário
Não recebe eventosHTTP, firewall, webhook pausadoHTTPS + endpoint público + ativo
Duplicou liberaçãoSem idempotênciaPersista event.id
Timeout / retriesHandler lento200 rápido + fila
payment.paid sem external_idNão enviou na criaçãoPasse external_id no POST /api/v1/payments

Referência rápida

Painel: Integração → Webhook
Método: POST
Auth: HMAC (X-HyzePay-Signature)
Versão: v2 recomendada
Evento-chave: payment.paid
Secret: whsec_… (só na criação)