Saltar al contenido principal

Integrar webhooks

Esta guía es para el equipo de desarrollo que va a recibir y procesar los webhooks de la plataforma en un sistema propio. Si solo necesitas dar de alta una suscripción desde el panel, consulta Administrar webhooks desde el panel.

Cómo funciona, en resumen

  1. Das de alta una suscripción (comercio + URL de tu endpoint + un secreto +, opcionalmente, qué tipos de evento quieres).
  2. Cuando ocurre un evento de ese comercio (una venta, un cambio de estado de terminal, etc.), la plataforma hace un POST a tu endpoint con el evento completo en el cuerpo de la solicitud.
  3. Tu endpoint debe responder con un código 2xx para confirmar que lo recibiste. Cualquier otra respuesta (incluyendo timeouts o errores de conexión) se considera una entrega fallida y se reintenta automáticamente.

Eventos disponibles

Si no seleccionas ningún evento al crear la suscripción, recibes todos los que aplican a tu comercio. Si seleccionas uno o más, solo recibes esos.

Transacciones

EventoCuándo ocurre
transaction.createdSe creó una transacción (venta, reembolso, cancelación o reversión).
transaction.status_changedLa transacción cambió de estado (p. ej. de pending a approved).
transaction.route_resolvedSe resolvió a qué procesador se va a enrutar la transacción.
transaction.risk_evaluatedSe evaluó el riesgo base de la transacción.
transaction.fraud_evaluatedSe evaluó el resultado final antifraude.
transaction.fraud_plugin_evaluatedEl motor antifraude (si está activo) emitió su decisión.
transaction.processor_completedEl procesador respondió (aprobada o declinada).
fee.calculatedSe calculó (o revirtió, en el caso de un reembolso/cancelación) la comisión de la transacción.
dispute.openedSe abrió una disputa sobre una transacción.
dispute.resolvedSe resolvió una disputa (a favor o en contra del comercio).

Terminales

EventoCuándo ocurre
terminal.createdSe registró una terminal nueva.
terminal.updatedCambió algún dato de la terminal (marca, modelo, modo de operación, etc.).
terminal.device_assignedSe asignó (o reactivó) un dispositivo físico a un comercio desde el backoffice.
terminal.secure_signinEl dispositivo completó el enrolamiento seguro que establece o renueva su llave de terminal.
terminal.health_signal_reportedEl dispositivo reportó su telemetría de salud/conectividad (batería, señal, red, etc.).

Comercios

EventoCuándo ocurre
merchant.createdSe dio de alta un comercio nuevo.
merchant.updatedCambió el perfil de un comercio.
branch.created / branch.updatedSe creó o actualizó una sucursal.
franchise.created / franchise.updatedSe creó o actualizó una franquicia.
Este catálogo puede crecer

La plataforma entrega automáticamente cualquier evento nuevo que se agregue en el futuro, siempre que pertenezca a un comercio - no necesitas hacer nada para empezar a recibirlos, salvo que hayas limitado tu suscripción a una lista específica de eventos (en cuyo caso solo verás los que ya seleccionaste, hasta que agregues el nuevo tipo).

Forma de la solicitud

Cada entrega es un POST con Content-Type: application/json. El cuerpo es el evento completo, tal como se generó internamente - no es un resumen ni una versión simplificada.

Headers:

HeaderQué contiene
X-PNX-SignatureLa firma HMAC-SHA256 del cuerpo exacto de la solicitud (ver Verificar la firma).
X-PNX-Event-TypeEl tipo de evento (p. ej. transaction.created).
X-PNX-Event-IdEl identificador único del evento origen - úsalo para deduplicar (ver Idempotencia).
X-PNX-Delivery-IdEl identificador único de este intento de entrega.

Ejemplo real (transaction.created):

{
"EventID": "evt_txn_1785427916_created",
"EventType": "transaction.created",
"EventVersion": "v1",
"TenantID": "tenant_demo",
"CorrelationID": "corr_txn_1785427916",
"OccurredAt": "2026-07-30T16:11:56.813160715Z",
"Payload": {
"TransactionID": "txn_1785427916",
"MerchantID": "merchant_1782289074",
"TerminalID": "terminal_1782289074",
"OperationType": "sale",
"Channel": "ecommerce",
"Amount": { "Amount": 2500, "Currency": "MXN" },
"Status": "pending"
},
"Metadata": { "command_id": "cmd_3b6709aa7b4665bd", "command_type": "transaction.create" }
}

Cada tipo de evento tiene su propia forma de Payload (una venta trae Amount/Channel, un cambio de terminal trae Brand/Model, etc.) - siempre incluye al menos MerchantID, que es lo que la plataforma usa para decidir a qué suscripciones entregarlo.

Verificar la firma

X-PNX-Signature es el HMAC-SHA256 del cuerpo exacto de la solicitud (los bytes crudos, antes de parsear el JSON), usando el secreto que definiste al crear la suscripción, codificado en hexadecimal. Recalcúlalo y compáralo antes de confiar en cualquier webhook - así confirmas que vino realmente de la plataforma y no de alguien más.

Node.js:

const crypto = require("crypto");

function isValidSignature(secret, rawBody, signatureHeader) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Python:

import hashlib
import hmac

def is_valid_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
Usa el cuerpo crudo, no el JSON re-serializado

Si tu framework ya parseó el body a un objeto antes de que puedas firmarlo tú mismo, y luego lo vuelves a convertir a texto para comparar, es fácil que el resultado no sea byte por byte igual al original (orden de llaves, espacios, etc.) y la firma no coincida aunque el webhook sea legítimo. Verifica contra los bytes crudos de la solicitud.

Reintentos y estados de una entrega

EstadoQué significa
pendingCreada, esperando su primer intento.
dispatchingIntento en curso.
deliveredTu endpoint respondió 2xx.
failedTu endpoint respondió algo distinto de 2xx, no respondió, o hubo un error de conexión.

Una entrega failed se reintenta automáticamente con espera creciente (1, 2, 4, 8... minutos, hasta un máximo de 2 horas entre intentos) hasta un límite de intentos, tras el cual se deja de reintentar. Tu endpoint debe responder rápido (unos segundos) y con 2xx en cuanto reciba la solicitud - si necesitas procesar el evento de forma lenta, hazlo de forma asíncrona después de responder.

Idempotencia

Como cualquier entrega puede reintentarse, tu endpoint debe ser capaz de recibir el mismo evento más de una vez sin duplicar su efecto (por ejemplo, sin registrar dos veces la misma venta). Usa X-PNX-Event-Id (o el campo EventID del cuerpo) como llave de deduplicación - la plataforma nunca reintenta el mismo evento hacia la misma suscripción como una entrega nueva, pero tu propio sistema también podría recibirlo dos veces si tu endpoint respondió 2xx pero la confirmación se perdió en tránsito.

Qué sigue