Developer guide
VEXPay siteAPI reference
Browse documentation

Receive signed webhooks

Verify HMAC signatures before applying payment state changes.

Register an endpoint

curlbash
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"]
  }'
Envelopejson
{
  "event": "payout.completed",
  "data": {
    "payoutId": "00000000-0000-4000-8000-000000000000",
    "externalRef": "po_a1",
    "reference": "16142941",
    "livemode": true
  },
  "timestamp": "2026-07-20T15:00:00.000Z"
}
merchant.rejectedjson
{
  "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"
}

Update subscribed events later with PATCH /v1/webhooks/:id — sending events replaces the full list. You can also change url or isActive; the signing secret cannot be rotated via PATCH.

curlbash
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"]
  }'

Use POST /v1/notifications/test after registering an endpoint. Deliveries include `VexPay-Signature`, `VexPay-Event-Id`, and the legacy `X-Webhook-Event` / `X-Webhook-Signature` headers.

Event catalog

EventWhenKey fields in data
payment.pendingA payment was created and awaits completionpaymentId, method, externalRef
payment.completedThe payment was confirmed and credited to your balancepaymentId, method, vesAmount / amountCop / amountUsdt, fee
payment.failedThe charge failed or expiredpaymentId, failureCode
payment.canceledAn intent was canceled (superseded, expired or by you)paymentId, cancelReason
payment.reversedA completed payment was reversed: a C2P reversal, a COP refund, or a full card chargebackpaymentId, reversalType (reversal | chargeback), reversalRef
payment.chargebackThe bank charged back a card (VPOS) payment; your VES balance was debitedchargebackId, paymentId, amountVes, feeVes, reason, ledgerEntryIds
payment.chargeback_closedA chargeback was decided: won (money returned) or lostchargebackId, paymentId, outcome, ledgerEntryIds
payout.completed / payout.failedA payout was paid or failed (failed payouts return the money)payoutId, currency, failureReason
conversion.createdA VES or COP→USDT conversion started (including auto-convert of a COP payment)conversionId
conversion.completed / conversion.canceledA VES or COP→USDT conversion finished or was canceledconversionId
merchant.*Merchant lifecycle and balances (marketplaces)merchantId, externalRef
tenant.*Your account status, live activation and API keys—
notification.testPOST /v1/notifications/test—

Every movement of your VES balance — including chargebacks and adjustments that are not a normal payment or payout — is listed in `GET /v1/balance/transactions`. See Reconcile your balance.

Delivery behavior

  • Each event is attempted once. Failed deliveries are not retried.
  • Your endpoint has 10 seconds to respond.
  • Event ordering is not guaranteed; compare payment status instead of arrival order.
  • Return a 2xx response only after the event is accepted for durable processing.
  • Reconcile missed or uncertain events through the payment lookup endpoints.

Verify the signature

Prefer the `VexPay-Signature` header (`t=<unix>,v1=<hex>`). The `v1` value is HMAC-SHA256 of `<t>.<rawBody>` with your endpoint secret. Reject deliveries whose `t` is outside your tolerance (official SDKs default to 300 seconds). Use `VexPay-Event-Id` to deduplicate.

Node (@vexpay/node)javascript
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,
  );
  // fulfill from event.data — then res.sendStatus(200)
});
Python (vexpay)python
from vexpay import Webhook

event = Webhook.construct_event(
    request.body,  # raw bytes
    request.headers["VexPay-Signature"],
    os.environ["VEXPAY_WEBHOOK_SECRET"],
)