Skip to content

Webhooks

Los eventos se entregan por HTTP POST a sus endpoints activos.

Catálogo de eventos

typecuándo
payment.succeededpago confirmado
payment.settledpago liquidado — planeado, aún no se dispara (hoy no hay confirmación automática de liquidación; ver Liquidación)
payment.cancelledpago cancelado
payment.failedpago rechazado

Internamente, payment.captured se entrega públicamente como payment.succeeded. payment.authorized no genera webhook.

Envelope

json
{
  "id": "evt_9f8c...", "type": "payment.succeeded", "created": 1789990000,
  "data": {
    "payment": {
      "id": "pay_9f8c...",
      "amount": 510000,
      "currency": "BRL",
      "gross_amount": 510000,
      "gross_currency": "BRL",
      "fee_amount": 25000,
      "fee_currency": "BRL",
      "net_amount": 95000,
      "net_currency": "USD",
      "payment_method": "pix",
      "status": "succeeded",
      "reference": "pedido-123",
      "failure_code": null,
      "fx": {
        "rate": 5.1,
        "base": "USD",
        "quote": "BRL",
        "locked_at": 1800000000,
        "valid_until": null,
        "rate_history_id": 123
      },
      "settlement": {
        "estimated_date": "2026-09-08",
        "reason": [
          { "type": "cutoff", "note": "..." },
          { "type": "weekend", "date": "2026-09-05" },
          { "type": "holiday", "date": "2026-09-07", "country": "BR", "name": "..." }
        ]
      }
    }
  }
}

El payload público nunca incluye spot_rate, cobertura_centibps, source, median_3_stablecoin, percent_centibps ni porcentajes de regla.

Liquidación (settlement)

Presente solo en payment.succeeded. Dice cuándo debe caer el dinero y por qué esa fecha — para que usted cierre la caja sin recalcular el calendario bancario (BR/Febraban + US/Fedwire), el corte de las 15h (America/Sao_Paulo) y el rodaje de fin de semana por su cuenta.

  • estimated_date (AAAA-MM-DD, America/Sao_Paulo) es una ESTIMACIÓN, no una garantía. Refleja las reglas de calendario vigentes al momento de la confirmación.
  • reason[] es el rastro cronológico de lo que movió la fecha: cutoff (pagado después de las 15h), weekend y holiday (con country y name). Un array vacío significa que la fecha cayó directo, sin ajustes.
  • No hay campo de "D+N". Use estimated_date; el reason explica el camino. No sume días por su cuenta.
  • Fuera de la cobertura del calendario, el objeto settlement se omite — nunca enviamos una fecha sin cobertura. Trate la ausencia como "sin previsión", no como error.
  • No existe evento de confirmación de liquidación. payment.succeeded trae la previsión; la plataforma no emite hoy un evento que confirme que el crédito efectivamente ocurrió en estimated_date. No asuma confirmación automática — si necesita conciliar el crédito real, hágalo desde su extracto bancario.

Firma

Cada entrega lleva el header:

Pagooz-Signature: t=1789990000,v1=<hex>

v1 es el HMAC-SHA256, en hex, de "{t}.{body}" (el timestamp, un punto y el cuerpo bruto de la solicitud), usando el secret del endpoint. Verifique y rechace entregas cuyo t esté a más de 5 minutos del momento actual (tolerancia de replay).

Ejemplo funcional (Node):

js
const crypto = require("crypto");

function verifyPagoozSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 min
  const expected = crypto.createHmac("sha256", secret)
    .update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Entrega, retry e idempotencia del consumidor

  • Una entrega solo tiene éxito con una respuesta 2xx.
  • Una falla dispara 5 intentos con backoff: inmediato, 1 minuto, 5 minutos, 30 minutos y 2 horas.
  • Después de la 5ª falla, la entrega queda dead_lettered y visible en el cockpit.
  • Entrega al menos una vez. El mismo evento puede llegar más de una vez (retry, renotificación, reentrega). Su consumidor debe ser idempotente por event.id — procese cada id una sola vez.

Configuración en el cockpit

Una URL por tenant por entorno (sandbox y live). El cockpit lee el modo global de la sesión y guarda la URL de ese entorno. El secret de firma se muestra una sola vez, al crear/rotar. La tabla webhook_endpoints no se usa en esta versión; queda reservada para múltiples destinos en el futuro.


Conciliación y renotificación

Reenvío manual en el cockpit

El merchant puede reenviar una entrega fallida desde el cockpit. El payload reenviado mantiene el mismo event_id; el nuevo intento se registra en webhook_deliveries con origin = "manual".

Historial de entregas

El cockpit muestra las últimas entregas por entorno. Cada objeto:

json
{
  "id": "whd_9f8c...", "event_id": "evt_9f8c...",
  "attempt": 3, "status": "failed", "http_status": 503,
  "response_body": "...",
  "origin": "auto", "created": 1789990000, "next_attempt_at": 1789990300
}

Le permite diagnosticar por su cuenta por qué una notificación no llegó.