What is Paynexus
Paynexus is a PSP orchestration platform. You integrate once with our REST API, and we route transactions across 100+ payment providers — crypto and fiat — with smart routing, automatic failover, idempotency, and verified callbacks.
What you get
- REST API — the primary interface. Language-agnostic. Covers deposits, withdrawals, balances, payways, and webhooks.
- PHP SDK — framework-agnostic (PHP 7.4+). Spec-driven provider pattern. Use it when you need direct PSP access or want to package a new provider.
- Admin Cabinet — multi-tenant panel with RBAC for merchants, routing rules, API keys, and transaction monitoring.
- Sandbox — deterministic test environment with the Debug module. Every scenario (success / pending / failed / declined / transport_error) is replayable.
Base URLs
| Environment | Base URL |
|---|---|
| Production | https://api.paynexus.io |
| Sandbox | https://api-sandbox.paynexus.io |
Jump to the Quickstart — you'll have a working deposit in under 10 minutes.
From zero to first deposit
This walkthrough connects a merchant, accepts a test payment, and verifies a webhook — end-to-end in five steps.
1. Sign up and create a merchant
Sign up in the Cabinet and create your first merchant. You'll choose a rate-limit tier (Standard / Premium / Unlimited) and one or more currencies.
2. Issue API keys
In Cabinet → Settings → API Keys, generate a key pair. Store both values in your environment:
3. Create your first deposit
Send a POST /api/v1/deposits with the merchant's key pair. We return a transaction ID, status, and a redirect_url for the customer.
4. Receive the webhook
Once the customer completes the payment (or the sandbox scenario resolves), we POST to your callback_url with the final transaction state. See Webhooks for verification.
5. Go live
- Swap base URL from
api-sandbox.paynexus.iotoapi.paynexus.io. - Replace
pk_test_*keys withpk_live_*. - Configure your production routing rules and PSP connections in the Cabinet.
- Whitelist Paynexus callback IPs on your webhook endpoint.
API keys & headers
Every request requires two headers:
Key pairs per environment
Sandbox keys are prefixed pk_test_* / sk_test_*. Production uses pk_live_* / sk_live_*. A merchant can hold multiple active key pairs — useful for zero-downtime rotation.
Rotation
Generate a new pair in Cabinet → Settings → API Keys. Both the old and new pair work for a 24-hour overlap window; after that, the old pair is revoked automatically.
Never commit sk_* values to git. Use environment variables or a secret manager. Five failed auth attempts per five minutes will IP-block the source for one hour.
POST /api/v1/deposits
Creates a deposit transaction and routes it to the optimal PSP per your merchant's routing rules. Returns a redirect URL (for redirect flows) or host-to-host payment details (for crypto and some card flows).
Request body
| Field | Type | Description |
|---|---|---|
amount required | string | Decimal amount. Always a string to avoid float precision issues. |
currency required | string | ISO 4217 code (USD, EUR, RUB...). |
payway required | string | Payment method code: CARD, SBP, CRYPTO_BTC, CRYPTO_USDT_TRC20, etc. Fetch available values from GET /api/v1/payways. |
bill_id required | string | Your internal order ID. Must be unique per merchant. |
callback_url required | string | Where Paynexus POSTs the webhook when the status changes. |
customer optional | object | { id, email, ip, country } — used by routing and fraud rules. |
metadata optional | object | Up to 10 custom key-value pairs. Echoed back in webhooks. |
Response
Flows
- Redirect flow — we return a
redirect_url. Redirect the customer; they pay on the PSP's page; PSP calls us; we call your webhook. - Host-to-host (H2H) — for crypto and some P2P card flows. Instead of
redirect_url, the response includes apayment_detailsobject with crypto address, QR code data, or bank details to render inline.
POST /api/v1/withdrawals
Creates a withdrawal request. The request enters pending status and our orchestration daemon processes it asynchronously, running it through a validation chain before selecting a PaymentAccount and calling the PSP.
Request body
| Field | Type | Description |
|---|---|---|
amount required | string | Decimal amount. |
currency required | string | ISO 4217 code. |
payway required | string | Payment method code for the payout. |
destination required | object | Beneficiary account — card number, crypto address, SBP phone, bank details. |
bill_id required | string | Your internal payout ID. |
callback_url required | string | Webhook target for status updates. |
cascade_interrupt optional | boolean | When true, abort instead of trying fallback accounts on first failure. Default false. |
Validation chain
Before dispatching to a PSP, the daemon validates the request against a configurable chain of rules: merchant balance, request limits (per-request / per-day), blacklists, country restrictions, payway availability, currency match, and KYC status. Any validator failure returns 422 Unprocessable Entity with a specific error code.
Cascade failover
If the primary PSP declines, we automatically try the next account in your routing chain. Each attempt is logged in transaction_attempts and visible in the Cabinet. Opt out per-withdrawal by setting cascade_interrupt: true.
Status check: GET /api/v1/withdrawals/{id}. Poll at most once every 30 seconds — webhooks are the recommended mechanism.
Handle transaction events
When a transaction changes state, Paynexus POSTs a JSON event to your callback_url. Verify every webhook before processing it.
Event payload
Event types
deposit.pending·deposit.completed·deposit.failedwithdrawal.pending·withdrawal.completed·withdrawal.failedrefund.completed·chargeback.opened
Signature verification
Each webhook includes an X-Paynexus-Signature header. Compute HMAC-SHA256 over the raw request body using your webhook secret (Cabinet → Settings → Webhook Secrets).
Retry policy
If your endpoint doesn't return 2xx within 5 seconds, we retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | +2 seconds |
| 3 | +8 seconds |
| 4 | +32 seconds |
| 5 | +2 minutes |
After 5 failed attempts, delivery is marked failed. You can replay any failed webhook from the Cabinet.
Safe retries by default
All mutating endpoints (POST /deposits, POST /withdrawals) accept an optional X-Idempotency-Key header. We cache the response for 24 hours keyed on that value.
Behavior
- TTL — 24 hours.
- Payload match — SHA-256 hash of the body must match the original request. Different body with same key →
409 Conflict. - Replay safety — network errors mid-flight can be retried with the same key without creating a duplicate transaction.
- Recommended format — your internal order ID or a UUIDv4.
Predictable failures
All 4xx and 5xx responses share the same envelope:
Common error codes
| Code | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | Key pair is unknown, revoked, or expired. |
validation_failed | 422 | Request body failed field validation. See details. |
idempotency_conflict | 409 | Same key, different payload. |
insufficient_balance | 422 | Merchant balance too low. |
routing_failed | 503 | No PSP available for this payway/currency. |
upstream_error | 502 | PSP returned an unexpected response. |
rate_limit_exceeded | 429 | Too many requests. See Retry-After header. |
Rate limits
| Tier | Per merchant | Concurrent |
|---|---|---|
| Standard (Free) | 60 req/min | 5 |
| Premium | 300 req/min | 20 |
| Unlimited | 10,000 req/min | 100 |
Per-endpoint caps: deposits 30 req/min, withdrawals 20 req/min. Per-IP cap: 300 req/min. On a 429 response, respect the Retry-After header.
Circuit breaker
If a specific PSP returns 5+ failures within 60 seconds, our circuit breaker opens for that provider for 60 seconds and reroutes to the next account in your chain. The breaker transitions CLOSED → OPEN → HALF_OPEN → CLOSED. This is transparent — you'll see it reflected in attempt logs but your request flow is unaffected.
Spec-driven provider integration
The payment-core/sdk package is a framework-agnostic PHP library (PHP 7.4+). Use it when you need direct PSP access without going through the REST API, or when you want to package a new PSP as a reusable module.
Install
Basic usage
Facade methods
| Method | Returns |
|---|---|
createDepositInvoice() | Invoice |
createWithdraw() | WithdrawResult |
handleCallback() | CallbackReceipt |
checkStatus() | StatusResult |
getBalance() | Money |
capabilities() | CapabilitySet |
paywaySpec() | PaywaySpec |
Adding a new PSP module
Every integration is described declaratively in a PHP spec file — not in code. Core reads the spec and auto-generates HTTP requests, response mapping, and callback parsing.
A full new-provider checklist:
- Create
packages/modules/{new-psp}/config/{new-psp}.spec.php. - Implement
{NewPsp}Signer.phpif the PSP uses a non-standard signature scheme. - Write
{NewPsp}Factory.php— returns a configuredConfigurableRestProvider. - Register the module in your
ProviderRegistry. - Add unit tests for the signer and integration tests for the spec.
- Register in
apps/api/config/orchestration.php → supported_providers.
Already-packaged modules
paynexus/module-debug— fake provider for sandbox (no HTTP, deterministic scenarios).paynexus/module-piastrix— sorted-values HMAC-SHA256 signatures.paynexus/module-ampay— sorted-values HMAC-SHA512, 22 deposit payways / 5 withdraw payways.paynexus/module-betterbro— HMAC-SHA256 + Bearer token.paynexus/module-unlimit— HMAC with validation chain.
Deterministic scenarios
Every endpoint has a sandbox counterpart at https://api-sandbox.paynexus.io. Use your pk_test_* keys. No real PSPs are contacted.
Debug scenarios
Pass _debug_scenario in any request body to force specific outcomes — perfect for wiring up error-handling code paths without hitting real providers.
| Value | Behavior |
|---|---|
success (default) | Normal success flow. Deposit completes via webhook after ~2s. |
pending | Transaction stays pending indefinitely. Good for testing timeouts. |
failed | Returns HTTP 422. Tests synchronous error handling. |
declined | Accepts the request but webhook delivers deposit.failed. |
transport_error | Simulates an upstream network error (HTTP 502). Tests retry logic. |
Example: testing a declined deposit
Receiving webhooks locally
Use ngrok, webhook.site, or Cloudflare Tunnel to expose your local endpoint. Sandbox webhooks are signed with your sandbox webhook secret (Cabinet → Settings → Webhook Secrets → Sandbox).
Sandbox transaction IDs follow the pattern txn_test_<timestamp>_<nonce> so you can assert against them in automated tests.
Need help?
Open an issue on GitHub, or email arnon.hs.btc@gmail.com.