All systems operational

VexaPay API

A single REST API to move money to 40+ countries. Real-time settlement over local rails — PIX, Bre-B, SPEI, ACH, SEPA, and more — with no intermediary banks in the chain.

REST · JSON
HTTPS only
Bearer token auth
Idempotency keys
Webhook events
Sandbox included

Quick start

Get from zero to a settled transfer in under 10 minutes. You'll need an API key — request one from your dashboard after KYB verification, or use a sandbox key to start immediately.

1. Install the SDK

Node.js Python Go PHP
npm install @vexapay/sdk
pip install vexapay
go get github.com/vexapay/vexapay-go
composer require vexapay/vexapay-php

2. Initialize the client

Node.js Python Go PHP
import VexaPay from '@vexapay/sdk'; const vxp = new VexaPay({ apiKey: process.env.VEXAPAY_API_KEY, // vxp_live_... or vxp_test_... });
import vexapay client = vexapay.Client( api_key=os.environ["VEXAPAY_API_KEY"] # vxp_live_... or vxp_test_... )
import "github.com/vexapay/vexapay-go" client := vexapay.NewClient(os.Getenv("VEXAPAY_API_KEY"))
$client = new \VexaPay\Client([ 'api_key' => getenv('VEXAPAY_API_KEY'), ]);

3. Create a transfer

Node.js Python cURL
const transfer = await vxp.transfers.create({ amount: 45_000, // USD cents ($450.00) currency: 'USD', destination: { country: 'MX', rail: 'SPEI', account: '112233445566778899001122', // CLABE name: 'Acme Mexico S.A. de C.V.', }, reference: 'invoice-8821', idempotency_key: 'inv-8821-attempt-1', }); console.log(transfer.id); // vxp_tr_01J9KM... console.log(transfer.status); // "processing"
transfer = client.transfers.create( amount=45_000, currency="USD", destination={ "country": "MX", "rail": "SPEI", "account": "112233445566778899001122", "name": "Acme Mexico S.A. de C.V.", }, reference="invoice-8821", ) print(transfer.id) # vxp_tr_01J9KM...
curl -X POST https://api.vexapay.com/v1/transfers \ -H "Authorization: Bearer vxp_live_sk_..." \ -H "Content-Type: application/json" \ -d '{ "amount": 45000, "currency": "USD", "destination": { "country": "MX", "rail": "SPEI", "account": "112233445566778899001122", "name": "Acme Mexico S.A. de C.V." }, "reference": "invoice-8821" }'

Response

JSON · 201 Created
{ "id": "vxp_tr_01J9KM4RPN8X2HTZQCBV", "status": "processing", "amount": 45000, "currency": "USD", "fee": 540, // 1.2% "fx_rate": 17.12, "amount_local": 760584, // MXN cents "rail": "SPEI", "reference": "invoice-8821", "created_at": "2026-06-28T14:22:01Z", "estimated_settlement": "2026-06-28T14:23:05Z", "settled_at": null }

Settlement is asynchronous. Listen for the transfer.settled webhook event, or poll GET /transfers/{id} until status is settled.

Authentication

All requests are authenticated with an API key passed in the Authorization header as a Bearer token. Keys are scoped to your organization and available in the dashboard after KYB verification.

Use vxp_live_ keys in production and vxp_test_ keys in the sandbox. Never expose live keys in client-side code or version control.
HTTP HEADER
Authorization: Bearer vxp_live_sk_AbCdEfGhIjKlMnOpQrStUvWxYz

Idempotency keys

All mutating requests (POST) accept an Idempotency-Key header. Repeating a request with the same key within 24 hours returns the original response without creating a duplicate transfer — safe to retry on network errors.

HTTP HEADER
Idempotency-Key: your-unique-key-per-request

Sandbox & testing

The VexaPay sandbox is a fully isolated environment with real API behavior but no actual money movement. All endpoints, webhooks, and settlement flows work identically to production.

Sandbox base URL: https://sandbox.api.vexapay.com/v1

Test scenarios

reference valueoutcome
test-successSettles in ~3 seconds. Good for happy-path tests.
test-failureFails with invalid_account error. Test your failure handling.
test-slowSettles after 60 seconds. Test timeout/polling logic.
test-fx-shiftFX rate shifts 2% mid-flight. Test FX risk flows.
test-funds-heldTransfer goes to funds_held for compliance review.

POST /transfers

Create a new outbound transfer. Returns a processing transfer object. Settlement is asynchronous — monitor status via webhook or polling.

POSThttps://api.vexapay.com/v1/transfers

Request parameters

parametertyperequireddescription
amountintegerrequiredAmount in the smallest currency unit (cents for USD).
currencystringrequiredISO 4217 code. Currently USD only for outbound.
destination.countrystringrequiredISO 3166-1 alpha-2 destination country code.
destination.railstringrequiredPayment rail. See rails reference: SPEI, PIX, BREP, ACH, SEPA_INSTANT, SWIFT
destination.accountstringrequiredBank account number, CLABE, IBAN, or PIX key depending on rail.
destination.namestringrequiredBeneficiary legal name.
referencestringYour internal reference. Passed to the beneficiary bank where supported.
metadataobjectUp to 20 key-value pairs. Returned as-is on the transfer object.

GET /transfers/{id}

Retrieve a single transfer by its ID. Use this to poll for settlement status if you prefer not to use webhooks.

GEThttps://api.vexapay.com/v1/transfers/{id}

Transfer status values: processing · settled · failed · funds_held · cancelled

GET /transfers

Returns a paginated list of transfers for your organization, ordered by creation time descending. Use limit (max 100) and starting_after (transfer ID) for cursor-based pagination.

GEThttps://api.vexapay.com/v1/transfers?limit=20

GET /balances

Returns real-time balances across all currencies held in your VexaPay account. available is spendable now; pending is in-flight from incoming transfers.

GEThttps://api.vexapay.com/v1/balances
JSON · 200 OK
{ "balances": [ { "currency": "USD", "available": 2840500, "pending": 45000 }, { "currency": "MXN", "available": 120000, "pending": 0 }, { "currency": "BRL", "available": 54000, "pending": 0 } ] }

Webhooks

VexaPay sends HTTPS POST requests to your webhook endpoint when transfer state changes. Configure your endpoint in the dashboard under Settings → Webhooks.

Events

eventdescription
transfer.settledTransfer settled at the destination bank. settled_at is populated.
transfer.failedTransfer failed. Check failure_reason in the payload.
transfer.funds_heldTransfer paused for compliance review. Contact support.
balance.lowAccount balance dropped below your configured threshold.
kyb.approvedYour KYB verification was approved — live access unlocked.
Always verify the X-VexaPay-Signature header (HMAC-SHA256 of the raw request body, signed with your webhook secret) before processing any event.
NODE.JS · Webhook verification
const isValid = vxp.webhooks.verify({ payload: req.rawBody, signature: req.headers['x-vexapay-signature'], secret: process.env.VEXAPAY_WEBHOOK_SECRET, }); if (!isValid) return res.status(401).end();

SDK Libraries

Official SDKs are available for all major server-side runtimes. All are open source, maintained by the VexaPay team, and handle authentication, retries with exponential back-off, and webhook signature verification out of the box.

Node.js
npm i @vexapay/sdk
🐍
Python
pip install vexapay
🔵
Go
go get .../vexapay-go
🐘
PHP
composer require vexapay/sdk

Looking for a runtime not listed? The API is standard REST + JSON — any HTTP client works. Raw integration typically takes under an hour.

Error handling

VexaPay uses standard HTTP status codes. All errors return a JSON body with a machine-readable code and a human-readable message.

HTTP statuscodedescription
400invalid_paramsRequest body missing required fields or has invalid values.
401unauthorizedAPI key missing, malformed, or revoked.
402insufficient_fundsAccount balance too low to cover amount + fee.
422invalid_accountDestination account number or CLABE is invalid.
429rate_limitedToo many requests. Back off and retry with exponential delay.
5xxserver_errorVexaPay-side error. Safe to retry with idempotency key.