Try every endpoint right from this page — no setup needed
Base URLhttps://sandbox.fiatsend.com/v1
Demo API Keyfs_test_demo_key_2026
Getting Started
Fiatsend Partner API
Enable instant stablecoin-to-mobile-money payouts for your users in Africa. Convert USDC and USDT to Ghana Cedis and deliver funds directly to MTN MoMo, Telecel Cash, and AirtelTigo Money wallets in seconds.
Instant Conversion
Fast settlement from stablecoin to mobile money wallet.
3 Mobile Networks
MTN MoMo, Telecel Cash, and AirtelTigo Money across Ghana.
On-Chain Verification
Every transaction is anchored on-chain with a verifiable hash.
Real-Time Webhooks
Get instant status callbacks for every withdrawal state change.
Base URLs
Environment
Base URL
Sandbox
https://sandbox.fiatsend.com/v1
Production
https://api.fiatsend.com/v1
Rate Limits
Plan
Requests / min
Requests / day
Sandbox
60
10,000
Production
300
100,000
Security
Authentication
All API requests require a Bearer token in the Authorization header. API keys are issued during partner onboarding.
HTTP Header
Authorization: Bearer your_api_key_here
Getting API Keys
Create your keys in console.fiatsend.com under API Keys. Sandbox keys start with fs_test_; production keys start with fs_live_.
Example Request
bash
curl -X GET "https://sandbox.fiatsend.com/v1/health" \
-H "Authorization: Bearer fs_test_abc123..."
Create and manage stablecoin-to-mobile-money withdrawals.
POST/v1/withdrawals
Create a Withdrawal
Initiate a stablecoin-to-mobile-money withdrawal. Fiatsend locks the FX rate at the time of request, converts the stablecoin amount to GHS, and delivers funds to the specified mobile money wallet.
The withdrawal goes through the following statuses:
pending — Request received, awaiting processing
processing — On-chain transaction submitted, mobile money transfer initiated
completed — Funds delivered to mobile money wallet
failed — Transaction failed (funds returned to source)
Request Body
Field
Type
Required
Description
amount
string
Yes
Amount in source currency (e.g., "50.00")
currency
string
Yes
Source stablecoin: USDC or USDT
recipient_phone
string
Yes
Recipient's phone in E.164 format (e.g., +233241234567)
mobile_network
string
Yes
MTN, TELECEL, or AIRTELTIGO
reference_id
string
Yes
Your unique transaction reference (idempotency key)
Retrieve a paginated list of transactions with optional filters.
Query Parameters
Parameter
Type
Required
Description
status
string
No
Filter by status: pending, processing, completed, failed
from_date
date-time
No
Start date filter (ISO 8601)
to_date
date-time
No
End date filter (ISO 8601)
reference_id
string
No
Return only the withdrawal created with this reference_id. Re-posting a reference_id to POST /v1/withdrawals returns the existing withdrawal (HTTP 200), so retries are safe.
Take payments on your own website or app. Your server creates a checkout session for the exact amount, sends the customer to the hosted checkout, and Fiatsend notifies your server when it's paid. Customers can pay with Fiatsend checkout, mobile money or bank transfer.
Test and live keys use the same address
Create sessions with a fs_test_… key to test (no real money moves, and Fiatsend checkout uses the Stellar test network) and a fs_live_… key for live payments, which need an activated account. Get keys in Console under Developers → API Keys. The shared demo key used by "Try it" elsewhere in this portal does not work for checkout sessions, so this section has no Try it buttons. Keep keys on your server.
Prefer no code?
Payment buttons, an embeddable checkout and the Console guide are covered in the Website Checkout guide.
POST/v1/checkout/sessions
Create Checkout Session
Create a checkout for one customer's payment. The session stays open until it is paid or cancelled.
Request Body
Field
Type
Required
Description
amount
number
Yes
Price in major units, e.g. 50.00. Must be more than 0.
currency
string
No
GHS (default) or USDC.
description
string
No
What the customer is paying for (up to 200 characters). Shown on the checkout page and receipt.
client_reference_id
string
No
Your own order ID (up to 100 characters). Returned on the session and in webhooks.
customer
object
No
Optional name, email and phone (E.164, e.g. +233241234567). With an email the customer receives a receipt by email.
success_url
string
No
Where to send the customer after paying. https:// only (http://localhost allowed for testing). {SESSION_ID} is replaced with the session ID; without a placeholder, session_id is added as a query parameter.
cancel_url
string
No
Where to send the customer if they go back without paying. Same rules as success_url.
metadata
object
No
Up to 20 string key/value pairs (keys up to 40 characters, values up to 500), returned unchanged.
accepted_methods
string[]
No
Any of fiatsend, mobile_money (GHS only), bank_transfer. Defaults to the methods saved in the business's Payment settings.
Returns one session. A status of complete means it is paid; use this (or the webhook below) to confirm payment before you fulfil an order. Never rely on a browser event alone.
fiatsend, mobile_money or bank_transfer once paid, or manual if the business marked it paid itself.
livemode
boolean
true for live keys, false for test keys.
created_at
string
ISO 8601 creation time.
Webhook: checkout.session.completed
Sent to your server the moment a session is paid, even if the customer has closed the page. Register an endpoint in Console under Developers → Webhooks and tick checkout.session.completed. Endpoints must be public https:// URLs.
Signature.X-Fiatsend-Signature is the HMAC-SHA256 (hex) of the raw request body using your webhook secret, exactly as described in the Webhook Guide. X-Fiatsend-Event carries the event name.
Payload shape. The event name is in the event field and the full session is in data.object. This differs from the withdrawal events above, which use type.
Delivery. Up to 3 attempts in total (about 1 second, then 4 seconds apart), each with an 8 second timeout. Reply with any 2xx to acknowledge. Delivery is at-least-once, so de-duplicate on the event id.
Redirects and the Embedded Checkout
After payment the customer is returned to your success_url; if they go back, to your cancel_url. To open checkout in a popup on your page instead of redirecting, load Fiatsend's script (links must point to console.fiatsend.com). If the popup is blocked, the customer is redirected so they can still pay.
html
<script src="https://console.fiatsend.com/fiatsend-checkout.js"></script>
<button data-fiatsend-checkout="YOUR_BUTTON_ID">Pay with Fiatsend</button>
<script>
// Or open a session you created on your server
Fiatsend.checkout.open({ session: "cs_..." });
Fiatsend.checkout.on("paid", function (e) { /* UI hint only: confirm on your server */ });
</script>
Errors
Checkout endpoints return errors in this shape (note: different from the status/code shape used by other endpoints):
json
{ "error": { "code": "invalid_request", "message": "amount: amount must be more than 0" } }
HTTP
Code
Meaning
400
invalid_request
A field is invalid. The message names it, e.g. amount: amount must be more than 0.
401
—
API key missing, malformed or invalid. Body: {"message": "..."}.
403
account_not_active
A live key was used before the account was activated. Use a test key until you go live.
404
not_found
No such checkout session for this key's mode.
422
account_not_ready
The business can't take that payment yet, e.g. no Stellar wallet is connected for fiatsend checkout.
429
rate_limited
Too many requests. Retry shortly.
502
upstream_unavailable
Checkout is temporarily unavailable. Retry.
Limits
Checkout endpoints allow 120 requests per minute per business.
API Reference
Payment Intents Beta
Accept consumer-approved payments from Fiatsend users. Merchants create intents and the consumer approves or rejects in-app.
Beta
Payment intents are in beta: they work in sandbox and production, but fields and events may still change. For a stable way to take payments, use Checkout Sessions.
POST/v1/payment-intents
Create Payment Intent
Create a merchant payment request for a specific consumer phone number.
Cancel a pending payment request from the merchant side before consumer approval.
Payment Intent Events
Registered webhooks receive payment_intent.pending_approval, payment_intent.approved, payment_intent.completed, payment_intent.rejected, payment_intent.cancelled, payment_intent.expired and payment_intent.failed, signed the same way as withdrawal events.
API Reference
Rates
Get real-time FX rates for stablecoin to GHS conversion.
GET/v1/rates
Get Current FX Rate
Get a real-time quote for converting stablecoins to GHS. The returned rate includes a valid_until timestamp — the rate is guaranteed until that time.
Query Parameters
Parameter
Type
Required
Description
from_currency
string
Yes
USDC or USDT
to_currency
string
Yes
GHS
amount
string
Yes
Amount in source currency (e.g., "100.00")
The fee is in the source stablecoin (1% of amount), and total_ghs = (amount − fee) × rate. Withdrawals return the same values as fee and ghs_amount.
min_amount and max_amount are in the network's currency (GHS): every payout must deliver between GHS 50 and GHS 15,000 (ghs_amount) on any network, otherwise the request returns BELOW_MINIMUM or ABOVE_MAXIMUM. Payouts are also subject to your tier limits (GET /v1/limits: basic, standard, enterprise).
Fiatsend sends real-time notifications to your server when withdrawal statuses change. Instead of polling the API, register a webhook URL and we'll push updates to you.
Fiatsend sends a POST request to your URL whenever a subscribed event occurs
Your server verifies the signature and processes the event
Respond with a 2xx status code to acknowledge receipt
Retry Policy
If your endpoint returns a non-2xx status or times out (30s), Fiatsend retries with exponential backoff: 1 min, 5 min, 30 min, 2 hr, 24 hr. After 5 failed attempts, the webhook is marked as failed.
Webhook Payload
Each delivery is a POST with headers X-Fiatsend-Signature: sha256=<hex>, X-Fiatsend-Event (the event type) and X-Fiatsend-Delivery (unique per attempt). Use id to deduplicate: retries resend the same event id. Delivery is attempted up to 3 times.
Every webhook request includes an X-Fiatsend-Signature header containing an HMAC-SHA256 signature of the request body, computed using your webhook secret. Always verify this signature before processing events.
javascript
const crypto = require('crypto');
// X-Fiatsend-Signature is sha256=<hex HMAC-SHA256 of the raw body>.
function verifyWebhookSignature(rawBody, header, secret) {
if (!header) return false;
const received = header.startsWith('sha256=') ? header.slice(7) : header;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(received, 'hex');
const b = Buffer.from(expected, 'hex');
// timingSafeEqual throws on different lengths, so check first and answer 401, not 500.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express middleware example
app.post('/webhooks/fiatsend', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-fiatsend-signature'];
const isValid = verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
const type = event.type ?? event.event; // withdrawal events use type
// Deduplicate on event.id: retries reuse the same id.
switch (type) {
case 'withdrawal.completed':
// Mark payout as complete in your system
break;
case 'withdrawal.failed':
// Handle failure, notify user
break;
}
res.status(200).json({ received: true });
});
Event Types
Event
Description
withdrawal.pending
Withdrawal request received, awaiting processing
withdrawal.processing
On-chain transaction submitted, mobile money transfer initiated
withdrawal.completed
Funds successfully delivered to mobile money wallet
withdrawal.failed
Transaction failed — funds returned to source
checkout.session.completed
A checkout session was paid. Uses a different payload shape; see Checkout Sessions.
Checkout webhooks are registered in Console (Developers → Webhooks) rather than with POST /v1/webhooks, put the event name in event with the session in data.object, and are retried up to 3 times. The signature scheme above is identical. See Checkout Sessions.
Reference
Error Codes
Error responses carry a machine-readable code and a human-readable message. Payouts, rates, networks, webhooks and payment intents use the shape below. Checkout sessions use {"error": {"code": "...", "message": "..."}}, so handle both: body.code ?? body.error?.code.
json
{
"status": "error",
"code": "INVALID_PHONE",
"message": "Phone number must be a valid Ghana number starting with +233"
}
Code
HTTP Status
Description
UNAUTHORIZED
401
Invalid or missing API key
INVALID_PHONE
400
Invalid phone number format
INVALID_NETWORK
400
Unsupported mobile money network
BELOW_MINIMUM
422
Payout below GHS 50 (the cedis delivered)
ABOVE_MAXIMUM
422
Payout above GHS 15,000 (the cedis delivered)
REFERENCE_IN_USE
409
This reference_id is already used; use a unique one per payout
INSUFFICIENT_BALANCE
422
Your balance doesn't cover the withdrawal; fund it in the console
PAYOUTS_UNAVAILABLE
503
Mobile money payouts are temporarily unavailable; nothing was charged, retry later
RATE_EXPIRED
422
Quoted rate has expired
NETWORK_DOWN
503
Mobile money network unavailable
INSUFFICIENT_LIQUIDITY
503
Insufficient liquidity for conversion
RATE_LIMITED
429
Too many requests
INTERNAL_ERROR
500
Internal server error
Resources
SDKs & Tools
Libraries and tools to accelerate your integration.
Node.js SDK
Temporarily unavailable for Partner API integration.
Unavailable
Python SDK
Temporarily unavailable for Partner API integration.
Building with an AI coding assistant? Give it the Fiatsend rules (sandbox first, string amounts, reference_id idempotency, webhook signatures) so the code it writes is right the first time. Everything lives in the fiatsend-ai kit.
Copies the llms-full.txt link: the whole API reference in one AI-readable file. Short index: llms.txt.
Which Piece for Which Tool
AI tool
Add this
Claude Code
Copy skills/fiatsend-api/ into .claude/skills/ (or ~/.claude/skills/)
Claude.ai / Claude Desktop
Upload the skills/fiatsend-api folder as a skill
Cursor
Copy .cursor/rules/fiatsend.mdc into your project's .cursor/rules/
The kit's MCP server lets your AI tool check rates and networks, preview and track payouts, create checkout sessions and debug webhook signatures while you work. It's sandbox-first: fs_test_ keys can't move real money, live payouts stay off unless FIATSEND_ALLOW_LIVE_PAYOUTS=true is set, and every payout needs explicit confirmation.
The npm package @fiatsend/mcp is coming soon. Until then, build it from the kit:
bash
git clone https://github.com/fiatsend/fiatsend-ai.git
cd fiatsend-ai/mcp-server && npm install && npm run build
Then add it to your tool's MCP config (Claude Desktop, Cursor .cursor/mcp.json, Windsurf), using the full path to the kit: