Saltar al contenido
    Eventos en Tiempo Real
    Notificaciones Push

    Webhooks

    Recibe notificaciones HTTP POST inmediatas en tu servidor cuando se confirme un pago móvil, una transferencia bancaria o una renovación de suscripción.

    Firma de webhook: HMAC-SHA256

    Los webhooks salientes de enlaces de pago, incluidas las pruebas, llevan X-Pabilo-Timestamp (segundos Unix) y X-Pabilo-Signature (prefijo sha256= y 64 caracteres hexadecimales en minúsculas).

    Calcula HMAC-SHA256 usando como clave el texto del secreto y como mensaje el timestamp, un punto y los bytes exactos del cuerpo HTTP. No decodifiques el secreto como hexadecimal ni vuelvas a serializar el JSON.

    Obtener el secreto del titular
    curl https://api.pabilo.app/me/webhook-secret -H "appKey: TU_API_KEY"
    json
    {
      "message": "Webhook secret fetched successfully",
      "webhook_secret": "SECRETO_DEL_TITULAR"
    }

    Requiere lectura de integrations. El secreto pertenece al titular; los clientes no pueden consultarlo. Consérvalo únicamente en el servidor receptor. Rechaza firmas ausentes o inválidas, compara en tiempo constante y aplica una ventana de antigüedad (por ejemplo, cinco minutos con el reloj sincronizado).

    Una entrega 2xx se considera exitosa. El emisor registra los fallos y avisa al dueño; no se garantiza una política automática de reintentos. La firma no sustituye la idempotencia: registra la identidad del pago y su estado para evitar entregar dos veces una orden.

    Instantáneo

    Pabilo intenta entregar el evento una vez que el banco valida la transferencia, sin necesidad de polling.

    Idempotente

    Cada evento incluye identificadores únicos de pago y referencia bancaria para procesar cada cobro una sola vez.

    Suscripciones

    El webhook se propaga automáticamente a cada cobro y ciclo recurrente de tus suscripciones activas.

    1. ¿Cómo Funciona la Configuración?

    Puedes configurar tu webhook_url al crear un Link de Pago (vía API o Dashboard) o al registrar una Suscripción:

    1. Creas una orden o enlace indicando tu endpoint: "webhook_url": "https://tu-tienda.com/api/webhooks/pabilo".
    2. El cliente realiza el pago móvil o transferencia e ingresa su referencia en el checkout.
    3. Pabilo verifica los fondos directamente con la entidad bancaria en tiempo real.
    4. Pabilo despacha una petición HTTP POST con el cuerpo JSON a tu servidor con todos los detalles de la operación.
    5. Tu servidor verifica primero la firma y después valida el estado status: "paid", despacha la orden y responde con un código HTTP 200 OK.

    2. Estructura del Payload

    La petición enviada por Pabilo a tu URL contiene los datos completos de la transacción bancaria y el enlace correspondiente.

    Evento: Pago Confirmado (status: "paid")

    Payload POST recibido
    {
      "payment_link_id": "675725869e6febc736848bf1",
      "status": "paid",
      "credit_balance": 150.5,
      "payment_link": {
        "id": "675725869e6febc736848bf1",
        "name": "Pago de Orden #1042",
        "amount": 50,
        "currency": "USD",
        "description": "2x Camisas Premium + Envío",
        "webhook_url": "https://miapp.com/webhook/pabilo",
        "status": "paid",
        "url": "https://pabilo.app/pay/675725869e6febc736848bf1",
        "redirect_url": "https://miapp.com/gracias",
        "with_subscription_id": null,
        "type": "api"
      },
      "user_bank_payment": {
        "id": "675725869e6febc736848bf4",
        "bank_reference_id": "0025513902095",
        "amount": 3122.5,
        "status": "paid",
        "credit_cost": 1,
        "created_at": "2026-08-03T15:30:00Z"
      }
    }

    Descripción de Campos Clave

    CampoTipoDescripción
    payment_link_idstringID único del link de pago que originó el cobro.
    statusstringEstado final de la transacción (paid o failed).
    credit_balancenumberSaldo de créditos restante en tu cuenta de Pabilo tras la validación.
    payment_linkobjectObjeto completo del link de pago con monto, moneda, descripción y URLs de retorno.
    user_bank_paymentobject | nullInformación de la transacción bancaria validada (referencia bancaria emitida, monto real en Bs acreditado y costo en créditos).

    Evento: Pago Rechazado o Fallido (status: "failed")

    Payload POST en caso de fallo
    {
      "payment_link_id": "675725869e6febc736848bf1",
      "status": "failed",
      "credit_balance": 149.5,
      "payment_link": {
        "id": "675725869e6febc736848bf1",
        "status": "failed",
        "status_detail": "payment not found: movement not found with bank reference 00123"
      },
      "user_bank_payment": null
    }

    3. Ejemplo de Receptor en tu Servidor (Node.js / Express)

    Asegúrate de responder rápidamente con código 200 OK después de verificar la firma y persistir el evento; procesa las tareas prolongadas desde tu cola:

    webhook-handler.js (Express)
    const express = require("express");
    const { createHmac, timingSafeEqual } = require("node:crypto");
    
    // saveEventOnce debe persistir el evento de forma durable e idempotente.
    // Monta este router ANTES de cualquier express.json() global.
    function webhookRouter(saveEventOnce) {
      const router = express.Router();
      const secret = process.env.PABILO_WEBHOOK_SECRET;
      if (!secret) throw new Error("Falta PABILO_WEBHOOK_SECRET");
      router.post("/api/webhooks/pabilo", express.raw({ type: "application/json" }), async (req, res) => {
        const timestamp = req.get("X-Pabilo-Timestamp") || "";
        const signature = req.get("X-Pabilo-Signature") || "";
        if (!/^\d+$/.test(timestamp) || !/^sha256=[a-f0-9]{64}$/.test(signature) || !Buffer.isBuffer(req.body)) {
          return res.sendStatus(401);
        }
        // 300 segundos es una política del receptor, no un valor enviado por Pabilo.
        if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(401);
        const expected = createHmac("sha256", secret)
          .update(timestamp + ".").update(req.body).digest();
        const received = Buffer.from(signature.slice(7), "hex");
        if (received.length !== expected.length || !timingSafeEqual(received, expected)) return res.sendStatus(401);
        let event;
        try { event = JSON.parse(req.body.toString("utf8")); }
        catch { return res.sendStatus(400); }
        try {
          await saveEventOnce(event);
          return res.sendStatus(204);
        } catch {
          return res.sendStatus(500);
        }
      });
      return router;
    }
    module.exports = webhookRouter;

    4. Reglas de Entrega y Buenas Prácticas

    Tiempo de Respuesta

    Tu endpoint debe responder con código HTTP 200 OK en menos de 10 segundos (timeout del emisor). Recomendamos responder inmediatamente y procesar envíos o correos en segundo plano.

    Manejo de Idempotencia

    Registra payment_link_id y bank_reference_id en tu base de datos para asegurar que reintentos de red no procesen la entrega de un pedido más de una vez.