Stablique API
One API to send stablecoin-settled payments across Africa. Stablique API handles chain selection, corridor routing, off-ramp partner selection, and fiat delivery — your integration is a single REST call.
Instant settlement
USDT settles on-chain in seconds. Fiat payout to recipient within minutes via off-ramp partners.
Multi-corridor
Send to mobile money, bank accounts, and UPI across DRC, Kenya, Nigeria, Rwanda, and more.
Automatic fallback
Stablique API fetches quotes from multiple off-ramp partners and routes via the best available path.
KYT on every payment
Every transaction is screened via Chainalysis. Sanctions checks on sender and recipient.
Webhook delivery
HMAC-signed webhooks notify your system at every lifecycle state change. Retried with backoff.
Full sandbox
Isolated sandbox environment with simulated corridors and test USDT. No real funds required.
Base URL
https://api.stablique.xyz/v1
All requests must use HTTPS. HTTP requests are rejected. The sandbox base URL is https://sandbox.stablique.xyz/v1.
Request & response format
All request bodies and responses use JSON. Include Content-Type: application/json on all POST requests.
Supported corridors
Off-ramp partners
Stablique API maintains real-time integrations with the following off-ramp partners. Quote selection is automatic — you never need to choose a partner directly.
| Partner | Coverage | Payout methods | Typical payout time |
|---|---|---|---|
| Yellow Card | DRC, Nigeria, Kenya, Rwanda, Ghana | Mobile money, bank transfer | 2–15 minutes |
| Kotani Pay | Kenya, Uganda, Tanzania, Ghana | M-Pesa, Airtel Money | 1–5 minutes |
| Ovex | South Africa, Zimbabwe | Bank transfer, EFT | 15–60 minutes |
| TransFi | India, Philippines, Indonesia | UPI, bank transfer, GCash | 5–30 minutes |
| Mudrex | India | UPI | 2–10 minutes |
Quickstart
Send your first payment in under 10 minutes.
Create an account
Sign up at dashboard.stablique.xyz. Complete KYB verification — most approvals complete within 24 hours.
Get your API key
In your dashboard, navigate to Settings → API keys and create a new key. You'll get a sandbox key immediately; a production key is issued after KYB approval.
# Your API key looks like this
nz_live_sk_a8f3d2c1e4b5a6f7...
nz_test_sk_b9c3d4e5f6a7b8c9... # sandbox
Top up your account
Deposit USDT to your Stablique account. Your deposit address is in the dashboard under Account → Top up. You can also initiate a top-up via the API.
curl -X POST https://sandbox.stablique.xyz/v1/accounts/topup \
-H "Authorization: Bearer nz_test_sk_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "1000.00",
"asset": "USDT",
"chain": "tron"
}'
Send a payment
Use POST /v1/payments/send to initiate a payment. Stablique API handles the rest.
curl -X POST https://sandbox.stablique.xyz/v1/payments/send \
-H "Authorization: Bearer nz_test_sk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: your-unique-key-123" \
-d '{
"amount": "250.00",
"source_currency": "USDT",
"destination_currency": "KES",
"corridor": "DRC-KE",
"recipient": {
"name": "Amara Diallo",
"phone": "+254712345678",
"payout_method": "mpesa"
},
"reference": "invoice_2026_0042"
}'
Listen for webhooks
Register a webhook endpoint to receive real-time status updates as your payment moves through the settlement lifecycle.
curl -X POST https://sandbox.stablique.xyz/v1/webhooks \
-H "Authorization: Bearer nz_test_sk_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/stablique",
"events": ["payment.settled", "payment.failed"]
}'
nz_test_sk_.
Authentication
Stablique API uses Bearer token authentication. Include your API key in the Authorization header of every request.
API key format
Authorization: Bearer nz_live_sk_<key>
Key types
| Prefix | Environment | Description |
|---|---|---|
| nz_live_sk_ | Production | Real payments. Requires completed KYB. Keep secret — never expose client-side. |
| nz_test_sk_ | Sandbox | Simulated payments only. Safe to use in development and CI environments. |
Key scopes
Each API key can be scoped to specific permissions. Create scoped keys in the dashboard for least-privilege access.
| Scope | Permissions |
|---|---|
| payments:write | Create and manage payments |
| payments:read | Read payment status and history only |
| account:read | View balance and account details |
| account:write | Initiate top-ups and withdrawals |
| webhooks:write | Register and delete webhook endpoints |
Security requirements
API keys grant full access to your Stablique account. Follow these requirements:
- Never expose keys in client-side code, mobile apps, or public repositories
- Store keys in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault)
- Rotate keys immediately if you suspect compromise — use the dashboard or
DELETE /v1/api-keys/{id} - Use scoped keys for each service — one key per microservice, minimum required permissions
SDKs & libraries
Official Stablique SDKs for Node.js, Python, and PHP. All SDKs are open source and maintained by Stablique.
Node.js / TypeScript
npm install @stablique/payments
import { StabliqueClient } from '@stablique/payments';
const client = new StabliqueClient({
apiKey: process.env.STABLIQUE_API_KEY,
environment: 'production' // or 'sandbox'
});
// Send a payment
const payment = await client.payments.send({
amount: '250.00',
sourceCurrency: 'USDT',
destinationCurrency: 'KES',
corridor: 'DRC-KE',
recipient: {
name: 'Amara Diallo',
phone: '+254712345678',
payoutMethod: 'mpesa'
},
reference: 'invoice_2026_0042',
idempotencyKey: 'inv-0042-send-1'
});
console.log(payment.id, payment.status);
// pay_01HX4K9MZTP8... pending_quote
Python
pip install stablique
from stablique import StabliqueClient
client = StabliqueClient(
api_key=os.environ["STABLIQUE_API_KEY"],
environment="production"
)
payment = client.payments.send(
amount="250.00",
source_currency="USDT",
destination_currency="KES",
corridor="DRC-KE",
recipient={
"name": "Amara Diallo",
"phone": "+254712345678",
"payout_method": "mpesa"
},
reference="invoice_2026_0042",
idempotency_key="inv-0042-send-1"
)
PHP
composer require stablique/payments
use Stablique\Payments\StabliqueClient;
$client = new StabliqueClient([
'api_key' => $_ENV['STABLIQUE_API_KEY'],
'environment' => 'production',
]);
$payment = $client->payments->send([
'amount' => '250.00',
'source_currency' => 'USDT',
'destination_currency' => 'KES',
'corridor' => 'DRC-KE',
'recipient' => [
'name' => 'Amara Diallo',
'phone' => '+254712345678',
'payout_method' => 'mpesa',
],
]);
Sandbox
The Stablique sandbox is a fully isolated test environment. No real funds move. Off-ramp partners are simulated. Use it freely in development and CI.
Base URL
https://sandbox.stablique.xyz/v1
Sandbox behaviour
| Feature | Sandbox |
|---|---|
| Payment lifecycle | Auto-advances to settled after ~5 seconds |
| USDT balance | Pre-loaded with 100,000 test USDT — no top-up needed |
| Off-ramp partners | All simulated — no real payout occurs |
| KYT screening | Always passes unless you use the magic phrase (see below) |
| Webhooks | Fired normally — register a real endpoint to test delivery |
| Quotes | Simulated rates — not real market rates |
Test scenarios
Use these magic values to trigger specific sandbox behaviours:
| Value | Field | Triggers |
|---|---|---|
| FAIL_KYT | recipient.name | KYT compliance hold |
| FAIL_PAYOUT | reference | Off-ramp payout failure, payment returns to failed |
| SLOW_SETTLE | reference | Settlement takes 60 seconds instead of 5 |
| +254000000000 | recipient.phone | Phone validation failure |
Sandbox API key
Your sandbox key is available immediately on signup — no KYB required. It has the prefix nz_test_sk_ and only works against the sandbox base URL.
Send a payment
Initiates a payment. Stablique API fetches quotes from available corridor partners, selects the best route, settles USDT on-chain, and delivers fiat to the recipient. Returns immediately with a payment object in pending_quote state.
Idempotency-Key header. If a request fails or times out, resubmitting with the same key is safe — Stablique API will return the original payment rather than creating a duplicate.
Request headers
| Header | Required | Description |
|---|---|---|
| Authorization | required | Bearer token — your API key |
| Content-Type | required | Must be application/json |
| Idempotency-Key | required | Unique string (UUID recommended). Deduplicates requests for 24 hours. |
Request body
| Field | Type | Description |
|---|---|---|
| amount required | string | Amount to send in source_currency. Decimal string, e.g. "250.00". Minimum: "1.00". |
| source_currency required | string | Currency you're sending. Currently USDT only. |
| destination_currency required | string | Currency the recipient receives. e.g. KES, CDF, NGN, INR. |
| corridor required | string | Source-destination corridor code. See GET /corridors for available codes. |
| recipient required | object | Recipient details. See below. |
| reference optional | string | Your internal reference. Returned on the payment object and in webhooks. Max 255 chars. |
| metadata optional | object | Arbitrary key-value pairs attached to the payment. Returned as-is. Max 10 keys, 500 chars per value. |
Recipient object
| Field | Type | Description |
|---|---|---|
| name required | string | Full legal name of recipient. Used for KYT screening. |
| phone optional | string | E.164 format. Required if payout_method is mpesa, airtel, or mobile_money. |
| bank_account optional | string | IBAN or local account number. Required if payout_method is bank_transfer. |
| bank_code optional | string | BIC/SWIFT or local routing code. Required for bank_transfer. |
| upi_id optional | string | UPI ID in name@upi format. Required if payout_method is upi. |
| payout_method required | string | One of: mpesa, airtel, mobile_money, bank_transfer, upi. |
Example request
{
"amount": "250.00",
"source_currency": "USDT",
"destination_currency": "KES",
"corridor": "DRC-KE",
"recipient": {
"name": "Amara Diallo",
"phone": "+254712345678",
"payout_method": "mpesa"
},
"reference": "invoice_2026_0042",
"metadata": {
"customer_id": "cust_8821"
}
}
Response
{
"id": "pay_01HX4K9MZTP8VKWJ3RDF7GN2C",
"status": "pending_quote",
"amount": "250.00",
"source_currency": "USDT",
"destination_currency": "KES",
"corridor": "DRC-KE",
"recipient": {
"name": "Amara Diallo",
"phone": "+254712345678",
"payout_method": "mpesa"
},
"reference": "invoice_2026_0042",
"quote": null,
"chain_tx": null,
"fees": null,
"created_at": "2026-05-28T14:32:11.000Z",
"updated_at": "2026-05-28T14:32:11.000Z",
"metadata": { "customer_id": "cust_8821" }
}
{
"error": {
"code": "validation_error",
"message": "recipient.phone is required for payout_method mpesa",
"field": "recipient.phone"
}
}
{
"error": {
"code": "insufficient_balance",
"message": "Available USDT balance (180.00) is less than requested amount (250.00)",
"available": "180.00",
"requested": "250.00"
}
}
Get a payment
Retrieve a payment by its ID. Use this to poll for status updates or to retrieve full payment details including fees, chain transaction hash, and quote details once the payment has progressed.
Path parameters
| Parameter | Description |
|---|---|
| id required | Payment ID returned from POST /payments/send. Prefix: pay_. |
Example response — settled payment
{
"id": "pay_01HX4K9MZTP8VKWJ3RDF7GN2C",
"status": "payout_settled",
"amount": "250.00",
"source_currency": "USDT",
"destination_currency": "KES",
"destination_amount": "32150.00",
"corridor": "DRC-KE",
"recipient": {
"name": "Amara Diallo",
"phone": "+254712345678",
"payout_method": "mpesa"
},
"quote": {
"partner": "kotani_pay",
"rate": "128.60",
"expires_at": "2026-05-28T14:32:41.000Z"
},
"chain_tx": {
"chain": "tron",
"hash": "0x4a3f8b2c1e9d7f6a...",
"confirmed_at": "2026-05-28T14:32:18.000Z"
},
"fees": {
"platform_fee": "0.875",
"network_fee": "0.10",
"partner_fee": "0.50",
"total_fee": "1.475",
"fee_currency": "USDT"
},
"reference": "invoice_2026_0042",
"created_at": "2026-05-28T14:32:11.000Z",
"settled_at": "2026-05-28T14:34:52.000Z",
"updated_at": "2026-05-28T14:34:52.000Z"
}
List payments
Returns a paginated list of payments for your account, ordered by created_at descending.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | integer | 20 | Number of results per page. Max 100. |
| cursor | string | — | Pagination cursor from previous response's next_cursor. |
| status | string | — | Filter by status. e.g. payout_settled, failed. |
| corridor | string | — | Filter by corridor code. |
| from | string | — | ISO 8601 datetime — only return payments created after this time. |
| to | string | — | ISO 8601 datetime — only return payments created before this time. |
Response
{
"data": [ /* array of payment objects */ ],
"has_more": true,
"next_cursor": "pay_01HX4K...",
"total": 1847
}
Get quotes
Fetch real-time quotes for a corridor and amount. Returns all available off-ramp partner quotes, ranked by best effective rate. Quotes are valid for 30 seconds.
You typically do not need to call this endpoint directly — POST /payments/send automatically selects the best quote. Use this endpoint if you want to show the user a rate before initiating payment.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| corridor required | string | Corridor code. e.g. DRC-KE. |
| amount required | string | Amount in source currency. |
| source_currency required | string | Currently USDT only. |
| destination_currency required | string | e.g. KES. |
| payout_method optional | string | Filter quotes to a specific payout method. |
Response
{
"quotes": [
{
"id": "qt_01HX4K9MZTP8VKWJ",
"partner": "kotani_pay",
"rate": "128.60",
"destination_amount": "32150.00",
"fee": "0.50",
"payout_method": "mpesa",
"estimated_payout_minutes": 3,
"expires_at": "2026-05-28T14:32:41.000Z"
},
{
"id": "qt_01HX4K9MZTP8VKWK",
"partner": "yellow_card",
"rate": "127.80",
"destination_amount": "31950.00",
"fee": "0.75",
"payout_method": "mpesa",
"estimated_payout_minutes": 8,
"expires_at": "2026-05-28T14:32:41.000Z"
}
],
"best_quote_id": "qt_01HX4K9MZTP8VKWJ"
}
Accept a quote
Lock a quote and initiate settlement. The quote must not be expired. This creates a payment and immediately begins USDT on-chain settlement.
Request body
| Field | Type | Description |
|---|---|---|
| recipient required | object | Same recipient object as POST /payments/send. |
| reference optional | string | Your internal reference. |
| metadata optional | object | Arbitrary key-value pairs. |
Response
Returns a payment object in chain_settling state. The quote is locked and USDT settlement has begun.
List corridors
Returns all available corridors, their supported payout methods, currencies, and current availability status.
{
"corridors": [
{
"code": "DRC-KE",
"name": "DRC to Kenya",
"source_currency": "USDT",
"destination_currency": "KES",
"payout_methods": ["mpesa", "bank_transfer"],
"min_amount": "1.00",
"max_amount": "50000.00",
"status": "active",
"estimated_settlement_minutes": { "min": 2, "max": 15 }
}
]
}
Get account balance
Returns the current USDT balance for your Stablique account.
{
"available": "9483.25",
"pending": "250.00",
"total": "9733.25",
"currency": "USDT",
"updated_at": "2026-05-28T14:32:11.000Z"
}
| Field | Description |
|---|---|
| available | Balance available to send immediately |
| pending | Balance reserved for in-flight payments (not yet settled) |
| total | available + pending |
Top up account
Returns a USDT deposit address for a specific chain. Send USDT to this address to credit your Stablique account. Credits typically appear within 1–3 block confirmations.
Request body
| Field | Type | Description |
|---|---|---|
| amount optional | string | Expected top-up amount. Used to generate a labelled deposit address. If omitted, a general deposit address is returned. |
| chain required | string | Chain to receive USDT on. One of: tron, ethereum, polygon, base. |
Response
{
"deposit_address": "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW",
"chain": "tron",
"asset": "USDT",
"expected_amount": "1000.00",
"memo": null,
"expires_at": null
}
Withdraw
Withdraw USDT from your Stablique account to an external wallet. Withdrawals require account:write scope and are subject to AML screening.
Request body
| Field | Type | Description |
|---|---|---|
| amount required | string | USDT amount to withdraw. Minimum: "10.00". |
| destination_address required | string | Destination wallet address. Must be a whitelisted address for amounts above $10,000. |
| chain required | string | Chain to send on. One of: tron, ethereum, polygon, base. |
| memo optional | string | On-chain memo or tag (required by some exchanges). |
Webhook guide
Stablique API sends HMAC-signed webhook events to your registered endpoints whenever a payment changes state. Webhooks are the recommended way to track payment status — do not poll GET /payments/{id} in a loop.
Event types
| Event | Triggered when |
|---|---|
| payment.quoted | A quote has been selected and locked |
| payment.chain_settling | USDT on-chain transaction has been broadcast |
| payment.payout_pending | On-chain confirmed — off-ramp partner notified |
| payment.payout_initiated | Off-ramp partner has initiated fiat payout |
| payment.settled | Fiat delivered to recipient — final state |
| payment.failed | Payment failed — final state |
| payment.compliance_hold | Payment held pending KYT review |
Payload structure
{
"event_id": "evt_01HX4K9MZTP8VKW",
"event_type": "payment.settled",
"created_at": "2026-05-28T14:34:52.000Z",
"data": {
/* full payment object */
}
}
Verifying signatures
Every webhook request includes an X-Stablique-Signature header. Verify it before processing the payload.
// Node.js verification
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from('sha256=' + expected)
);
}
Delivery & retries
Stablique API expects a 2xx response within 10 seconds. If your endpoint fails or times out, we retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 (initial) | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6–10 | 6 hours each |
After 10 failed attempts the event is moved to the dead-letter queue. You can replay dead-lettered events from the dashboard.
Register a webhook
Request body
| Field | Type | Description |
|---|---|---|
| url required | string | HTTPS endpoint URL. Must respond with 2xx. |
| events required | string[] | Array of event types to subscribe to. Use ["*"] for all events. |
| description optional | string | Human-readable label for this endpoint. |
Response
{
"id": "wh_01HX4K9MZTP8VKW",
"url": "https://yourapp.com/webhooks/stablique",
"events": ["payment.settled", "payment.failed"],
"secret": "whsec_a8f3d2c1e4b5...",
"status": "active",
"created_at": "2026-05-28T14:32:11.000Z"
}
secret is only returned once at creation time. Store it securely — you will not be able to retrieve it again.List webhooks
Returns all registered webhook endpoints for your account.
Delete a webhook
Permanently deletes a webhook endpoint. In-flight deliveries will still attempt to complete. Returns 204 No Content on success.
Errors
Stablique API uses standard HTTP status codes and machine-readable error codes in every error response.
Error response format
{
"error": {
"code": "insufficient_balance",
"message": "Human-readable description of the error",
"request_id": "req_01HX4K9M..."
}
}
HTTP status codes
| Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 204 | No content (delete operations) |
| 400 | Bad request — malformed JSON or missing required field |
| 401 | Unauthorized — invalid or missing API key |
| 402 | Payment required — insufficient balance |
| 403 | Forbidden — API key lacks required scope |
| 404 | Not found |
| 409 | Conflict — idempotency key already used with different parameters |
| 422 | Validation error — request parameters failed validation |
| 429 | Rate limited — too many requests |
| 503 | Service unavailable — corridor or partner temporarily unavailable |
Error codes
| Code | Description |
|---|---|
| validation_error | A request parameter failed validation. The field property indicates which field. |
| insufficient_balance | Your USDT balance is too low to send this payment. |
| corridor_unavailable | The requested corridor is temporarily offline. Check GET /corridors. |
| no_quotes_available | No off-ramp partners returned quotes for this corridor and amount. |
| quote_expired | The quote you attempted to accept has expired. Fetch a new quote. |
| compliance_hold | Payment held for KYT review. Contact support if urgent. |
| kyb_required | KYB verification is required before using production. Complete it in the dashboard. |
| rate_limited | You have exceeded your rate limit. Retry after the Retry-After header value. |
| idempotency_conflict | The idempotency key was used with different parameters. Use a new key. |
Idempotency
All state-changing endpoints (POST) support idempotency keys. Always use them for payment operations.
Pass a unique string in the Idempotency-Key header. If a request fails mid-flight or you're unsure whether it was received, resubmit it with the same key — Stablique API will return the original response rather than creating a duplicate payment.
Key rules
- Keys are scoped to your API key — different API keys can use the same idempotency key safely
- Keys are valid for 24 hours after the original request
- If you submit the same key with different request parameters, you receive a
409 Conflicterror - Use UUIDs (v4) — they are collision-resistant and statistically unique
Recommended pattern
// Generate a stable key from your own reference
const { v4: uuidv4 } = require('uuid');
// Option A: random UUID per attempt (simplest)
const key = uuidv4();
// Option B: deterministic from your internal ID (allows safe replay)
const key = `payment-${your_internal_id}-${attempt_number}`;
Payment lifecycle
Every Stablique API payment moves through a defined sequence of states from creation to final settlement or failure.
| Status | Description | Terminal? |
|---|---|---|
| pending_quote | Payment created. Stablique API is fetching quotes from corridor partners. | No |
| quoted | Best quote selected and locked. Settlement about to begin. | No |
| chain_settling | USDT on-chain transaction has been broadcast. Waiting for block confirmations. | No |
| payout_pending | On-chain confirmed. Off-ramp partner has been notified to initiate payout. | No |
| payout_initiated | Off-ramp partner is processing fiat payout to recipient. | No |
| payout_settled | Fiat delivered to recipient. Payment complete. | Yes |
| failed | Payment failed. USDT returned to your account balance. | Yes |
| compliance_hold | Payment paused for KYT review. Will progress or fail after review. | No |
Typical timing
| Stage | Typical duration |
|---|---|
| pending_quote → quoted | < 2 seconds |
| quoted → chain_settling | < 1 second |
| chain_settling → payout_pending | 10–60 seconds (Tron), 1–3 minutes (Ethereum) |
| payout_pending → payout_settled | 1–15 minutes (mobile money), 15–60 minutes (bank transfer) |
Compliance & KYC
Stablique API performs KYT (Know Your Transaction) screening on every payment. You are responsible for KYB (Know Your Business) verification of your own customers.
What Stablique screens
- Every payment is screened against OFAC, EU, and UN sanctions lists
- On-chain transaction addresses are checked via Chainalysis for source-of-funds risk
- High-risk transaction patterns trigger automatic compliance holds
Your responsibilities
- You must complete KYB verification before accessing production
- You are responsible for KYC on your own end users — Stablique's screening is supplemental, not a substitute
- Do not initiate payments for customers you have reason to believe are sanctioned or using proceeds of crime
- Respond to compliance hold notifications within 48 hours with any requested information
compliance_hold state, contact compliance@stablique.xyz immediately. Do not re-submit the payment — this may be treated as an attempt to evade screening.
Changelog
API version history and breaking changes.
v1.0.0 — May 2026
Initial release.
POST /payments/send— send stablecoin-settled paymentsGET /payments/{id}andGET /payments— payment retrieval and listingGET /quotesandPOST /quotes/{id}/accept— quote managementGET /corridors— corridor availabilityGET|POST /accounts/balance|topup|withdraw— account managementPOST|GET|DELETE /webhooks— webhook management- Live corridors: DRC-KE, DRC-DRC, KE-KE, NG-KE, IN-KE
- Off-ramp partners: Yellow Card, Kotani Pay, Ovex, TransFi, Mudrex