HibouPay Docs API
← Documentação da API

Webhooks (notificação à loja)

A HibouPay envia notificações de eventos da sessão para a URL de callback da sua loja (platformCallbackUrl na criação da sessão). Schema completo: WebhookEvent na referência Redoc.

Eventos

event outcome Quando dispara
payment.approved APPROVED Somente depois que o pagamento foi liquidado. Nunca antecipado. authorizationId presente (referência do desembolso).
payment.denied DENIED Pagamento negado (ex.: margem insuficiente, recusa de proposta/assinatura).
session.cancelled CANCELLED A sessão não segue: cancelamento pela loja (POST /v1/sessions/{id}/cancel), valor fora da faixa permitida, ou indisponibilidade temporária do fluxo de margem. O envelope é o mesmo (event=session.cancelled); use a consulta de status se precisar do detalhe do status da sessão.
session.expired EXPIRED O tempo de vida da sessão esgotou.

Envelope (WebhookEvent):

{
  "event": "payment.approved",
  "sessionId": "22222222-2222-4222-8222-222222222222",
  "platformPaymentId": "order-42",
  "outcome": "APPROVED",
  "authorizationId": "auth-xyz",
  "occurredAt": "2026-07-14T17:12:34.000Z"
}

authorizationId e reason são opcionais — authorizationId aparece em payment.approved; reason pode acompanhar payment.denied / session.cancelled.

Entrega: at-least-once, retry, dedupe

A entrega é at-least-once: em resposta não-2xx (ou falha de rede), a HibouPay tenta de novo com backoff. O mesmo evento pode chegar mais de uma vez — sua rota deve ser idempotente.

Verificação de assinatura

Headers em toda entrega:

A assinatura é HMAC-SHA256, calculada sobre "{t}.{rawBody}" — os bytes exatos do corpo recebido, concatenados com o timestamp por um ponto. t é o mesmo valor do componente t= do header de assinatura (e de X-HibouPay-Timestamp).

Crítico: se o framework faz parse do JSON antes do handler (ex.: express.json()) e você tenta verificar re-serializando o objeto, a assinatura sempre falha — o corpo re-serializado nunca é idêntico byte a byte. Você precisa do corpo cru.

Com o SDK (@hiboupay/api-client) — Express

import express from 'express';
import { constructEvent, WebhookSignatureError } from '@hiboupay/api-client';

const app = express();

app.post(
  '/webhooks/hiboupay',
  // express.raw preserva o Buffer original — NÃO use express.json() nesta rota.
  express.raw({ type: 'application/json' }),
  (req, res) => {
    try {
      const event = constructEvent(
        req.body,
        req.headers,
        process.env.HIBOUPAY_WEBHOOK_SECRET!,
      );

      // dedupe por req.headers['x-hiboupay-delivery-id'] antes de agir

      switch (event.event) {
        case 'payment.approved':
          // somente depois que o pagamento foi liquidado
          break;
        case 'payment.denied':
        case 'session.cancelled':
        case 'session.expired':
          break;
      }

      res.status(200).end();
    } catch (err) {
      if (err instanceof WebhookSignatureError) {
        res.status(400).send('invalid signature');
        return;
      }
      throw err;
    }
  },
);

constructEvent verifica a assinatura e faz o JSON.parse do corpo cru, devolvendo o WebhookEvent tipado — ou lança WebhookSignatureError.

Alternativa: verifyWebhookSignature(rawBody, signatureHeader, secret, opts?) retorna boolean.

Verificação manual (sem SDK)

1. Extraia t e v1 de X-HibouPay-Signature (formato "t=<t>,v1=<hex>").
2. payload = `${t}.${rawBody}`   // bytes exatos recebidos, sem parse/re-serialização
3. expected = hex(HMAC-SHA256(secret, payload))
4. Compare expected com v1 em tempo constante.
5. Rejeite se |now - t| exceder a tolerância anti-replay (default do SDK: 300s).

Anti-replay

Assinaturas com t fora da janela de tolerância (mais de 300 segundos no passado ou futuro, por default no SDK) são rejeitadas.

Configurar o segredo e o callback

Configure a URL de callback e o segredo HMAC no painel HibouPay (ou no onboarding comercial). Sem segredo configurado, a HibouPay não envia webhook não assinado — a entrega falha por design.

O platformCallbackUrl informado na criação da sessão indica para onde notificar; o segredo permanece no painel/onboarding.

Segurança