Recibe webhooks firmados
Verifica las firmas HMAC antes de aplicar cambios al estado de los pagos.
Registra un endpoint
curl --request POST "${API_URL}/v1/webhooks" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"url": "https://merchant.example.com/webhooks/vexpay",
"events": ["payment.pending", "payment.completed", "payment.failed", "merchant.verified", "merchant.rejected", "merchant.deactivated", "merchant.reactivated", "payout.completed", "payout.failed"]
}'{
"event": "payout.completed",
"data": {
"payoutId": "00000000-0000-4000-8000-000000000000",
"externalRef": "po_a1",
"reference": "16142941",
"livemode": true
},
"timestamp": "2026-07-20T15:00:00.000Z"
}{
"event": "merchant.rejected",
"data": {
"merchantId": "292d478b-a1fb-4d27-ab5c-8701ed14da88",
"externalRef": "seller_47",
"payoutMethodId": "0064456b-7a6a-4b7e-b0b5-d9b27b0dfa5c",
"failureCode": "BE01",
"reason": "Datos del cliente no corresponden a la cuenta",
"action": "payout_method_deleted",
"livemode": true
},
"timestamp": "2026-07-23T18:12:05.000Z"
}Actualiza los eventos suscritos después con PATCH /v1/webhooks/:id — al enviar events se reemplaza la lista completa. También puedes cambiar url o isActive; el secreto de firma no se puede rotar vía PATCH.
curl --request PATCH "${API_URL}/v1/webhooks/ENDPOINT_ID" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"events": ["payment.completed", "payment.failed", "payout.completed", "payout.failed"]
}'Usa POST /v1/notifications/test después de registrar un endpoint. Las entregas incluyen los encabezados X-Webhook-Event y X-Webhook-Signature.
Catálogo de eventos
| Evento | Cuándo | Campos clave en data |
|---|---|---|
| payment.pending | Se creó un pago y espera completarse | paymentId, method, externalRef |
| payment.completed | El pago se confirmó y se acreditó a tu saldo | paymentId, method, vesAmount / amountCop / amountUsdt, fee |
| payment.failed | El cobro falló o venció | paymentId, failureCode |
| payment.canceled | Se canceló una intención (reemplazada, vencida o por ti) | paymentId, cancelReason |
| payment.reversed | Se reversó un pago completado: reverso C2P, reembolso COP, contracargo total de tarjeta o dinero que nunca llegó al banco | paymentId, reversalType (reversal | chargeback | bank_not_received), reversalRef. Un reembolso COP lleva method "COP", status "refunded", amountCop y feeCop, sin reversalType. |
| payment.chargeback | El banco aplicó un contracargo a un pago con tarjeta (VPOS); se debitó tu saldo VES | chargebackId, paymentId, amountVes, feeVes, reason, ledgerEntryIds |
| payment.chargeback_closed | Se resolvió un contracargo: ganado (se devuelve el dinero) o perdido | chargebackId, paymentId, outcome, ledgerEntryIds |
| payout.completed / payout.failed | Un retiro se pagó o falló (los fallidos devuelven el dinero) | payoutId, currency, failureReason |
| conversion.created | Empezó una conversión VES o COP→USDT (incluida la conversión automática de un pago COP) | conversionId |
| conversion.completed / conversion.canceled | Una conversión VES o COP→USDT terminó o se canceló | conversionId |
| merchant.* | Ciclo de vida y saldos de comercios (marketplaces) | merchantId, externalRef |
| tenant.* | Estado de tu cuenta, activación live y llaves API | — |
| customer.* · customer.subscription.* · invoice.* · payment_method.* | Suscripciones y cobros recurrentes: ver Suscripciones y facturación | El cliente, la suscripción, la factura o el método de pago |
| notification.test | POST /v1/notifications/test | — |
Cada movimiento de tu saldo VES — incluidos contracargos y ajustes que no son un pago o retiro normal — aparece en GET /v1/balance/transactions. Ver Concilia tu saldo.
Comportamiento de las entregas
- Cada evento se intenta entregar una sola vez. Las entregas fallidas no se reintentan.
- Tu endpoint tiene 10 segundos para responder.
- No se garantiza el orden de los eventos; compara el estado del pago en lugar del orden de llegada.
- Devuelve una respuesta 2xx solo después de que el evento sea aceptado para su procesamiento persistente.
- Concilia los eventos perdidos o inciertos mediante los endpoints de consulta de pagos.
Verifica la firma
Prefiere el encabezado VexPay-Signature (t=<unix>,v1=<hex>). El valor v1 es HMAC-SHA256 de <t>.<rawBody> con el secreto del endpoint. Rechaza entregas cuyo t esté fuera de tu tolerancia (los SDKs oficiales usan 300 segundos por defecto). Usa VexPay-Event-Id para deduplicar.
import VexPay from '@vexpay/node';
const vexpay = new VexPay(process.env.VEXPAY_API_KEY);
app.post('/webhooks/vexpay', express.raw({ type: 'application/json' }), (req, res) => {
const event = vexpay.webhooks.constructEvent(
req.body,
req.headers['vexpay-signature'],
process.env.VEXPAY_WEBHOOK_SECRET,
);
// cumple desde event.data — luego res.sendStatus(200)
});from vexpay import Webhook
event = Webhook.construct_event(
request.body, # bytes crudos
request.headers["VexPay-Signature"],
os.environ["VEXPAY_WEBHOOK_SECRET"],
)