This guide walks through the complete integration flow — from creating an account to receiving a webhook notification — using testnet. Every step includes the exact curl command, the expected response, and what to watch out for.
Prerequisites: You need
curlinstalled. A testnet environment is available athttps://api.testnet.fluxa.dev.
Create an account. Fluxa is multi-tenant — this creates a tenant (individual or organization) and returns a JWT for subsequent requests.
curl -X POST https://api.testnet.fluxa.dev/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "My Fintech Co",
"email": "dev@myfintech.co",
"password": "your-secure-password"
}'Expected response (201 Created):
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"tenant_id": "0193b0b4-1b33-7e9a-bcf6-2e2a0abb6d3f"
}What could go wrong:
400 BAD_REQUEST— Missing or invalid fields. Ensurename,email, andpasswordare all present.409 Conflict— Email already registered. Use a different email or proceed to login.
Save the token — you'll use it as Authorization: Bearer <token> in the next step.
API keys are the primary authentication mechanism for programmatic access. The raw key is shown exactly once on creation.
curl -X POST https://api.testnet.fluxa.dev/v1/keys \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <jwt_token>" \
-d '{
"label": "My Integration Key"
}'Expected response (201 Created):
{
"id": "0193b0b4-1b33-7e9a-bcf6-2e2a0abb6d43",
"key": "YOUR_API_KEY_sk_live_replace_with_real_key",
"prefix": "sk_live_",
"label": "My Integration Key",
"created_at": "2026-06-22T12:00:00Z"
}
⚠️ Save thekeyvalue now. It will never be returned again. If you lose it, revoke it and create a new one.
What could go wrong:
401— JWT expired or missing. Re-login or re-register.500 INTERNAL_ERROR— Server error. Retry with exponential backoff.
For the remaining steps, use the API key directly:
Authorization: Bearer YOUR_API_KEY_sk_live_replace_with_real_key
Create a Stellar wallet. Fluxa generates a keypair and stores the secret key encrypted with AES-256-GCM. The raw secret is never exposed.
curl -X POST https://api.testnet.fluxa.dev/v1/wallets \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_..."Expected response (201 Created):
{
"id": "0193b0b4-1b33-7e9a-bcf6-2e2a0abb6d3f",
"public_key": "GAIH3YPEXB6HHVH6RIC6LDGAB7G4WMFP7F2F3I4K6Z6Q5V7X5Y5Z5A5B",
"created_at": "2026-06-22T12:00:00Z"
}What could go wrong:
401— Missing or invalid API key. Ensure theAuthorizationheader is present.500 INTERNAL_ERROR— Keypair generation or encryption failure. Retry.
Save the id (wallet UUID) and public_key (Stellar address G...).
On Stellar testnet, new accounts need an initial XLM balance to exist on the network. Use the Stellar Friendbot to fund the wallet.
curl "https://friendbot.stellar.org?addr=GAIH3YPEXB6HHVH6RIC6LDGAB7G4WMFP7F2F3I4K6Z6Q5V7X5Y5Z5A5B"Expected response (200 OK):
{
"hash": "a1b2c3d4e5f6...",
"_embedded": {
"record": {
"account": "GAIH3YPEXB6HHVH6RIC6LDGAB7G4WMFP7F2F3I4K6Z6Q5V7X5Y5Z5A5B",
"balance": "10000.0000000"
}
}
}What could go wrong:
- Friendbot returns an error — The account may already be funded. Friendbot can only fund an account once.
- Network timeout — Friendbot is rate-limited. Wait a few seconds and retry.
Verify the balance:
curl -H "Authorization: Bearer sk_live_..." \
"https://api.testnet.fluxa.dev/v1/wallets/<wallet_id>/balances"Expected:
{
"wallet_id": "0193b0b4-...",
"balances": [
{
"asset_code": "XLM",
"balance": "10000.0000000"
}
]
}On mainnet, skip Friendbot. Fund the account by sending XLM from an exchange or an existing wallet.
Before the wallet can hold USDC, it must establish a trustline to the USDC issuer. This submits a Stellar change_trust operation.
curl -X POST https://api.testnet.fluxa.dev/v1/wallets/<wallet_id>/trustlines \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_..." \
-d '{
"asset_code": "USDC",
"issuer": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
}'Expected response (201 Created):
{
"id": "0193b0b4-1b33-7e9a-bcf6-2e2a0abb6d44",
"asset_code": "USDC",
"issuer": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
"status": "added",
"created_at": "2026-06-22T12:00:00Z"
}What could go wrong:
400 BAD_REQUEST— Invalid asset code or issuer. Use the correct testnet USDC issuer.500 INTERNAL_ERROR— Stellar submission failed. The account may not be funded (go back to Step 4).op_already_exists— Trustline already exists. This is harmless.
Now the wallet can hold USDC. Transfer some test USDC from the Stellar testnet friendbot or a faucet to this wallet, or proceed with XLM for transfers.
Before converting currencies, get a 30-second exchange rate quote. This locks in the rate for a short window.
curl -X POST https://api.testnet.fluxa.dev/v1/fx/quote \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_..." \
-d '{
"source_asset": "USDC",
"dest_asset": "NGN",
"amount": "100.00"
}'Expected response (200 OK):
{
"source_asset": "USDC",
"dest_asset": "NGN",
"source_amount": "100",
"dest_amount": "150000",
"fee_amount": "0.50",
"net_amount": "99.50",
"fee_bps": 50,
"rate": "1500",
"expires_at": "2026-06-22T12:00:30Z"
}What could go wrong:
400 BAD_REQUEST— Unsupported currency pair. Check that both assets are supported.400 BAD_REQUEST—fee schedule not found. Contact support to configure your fee schedule.
The expires_at field gives you 30 seconds to execute the conversion. If it expires, you'll need to request a new quote.
Convert USDC to NGN using a previously quoted rate. This internally fetches a fresh quote, validates it hasn't expired, and executes the swap.
curl -X POST https://api.testnet.fluxa.dev/v1/fx/convert \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_..." \
-d '{
"wallet_id": "<wallet_id>",
"source_asset": "USDC",
"dest_asset": "NGN",
"amount": "100.00"
}'Expected response (200 OK):
{
"id": "conv-uuid",
"wallet_id": "0193b0b4-...",
"source_asset": "USDC",
"dest_asset": "NGN",
"source_amount": "0.0666666",
"dest_amount": "100.0000000",
"rate": "1500.0000000",
"created_at": "2026-06-22T12:00:00Z"
}What could go wrong:
400 BAD_REQUEST—slippage tolerance exceeded. The quote expired. Get a new quote and try again.400 BAD_REQUEST—insufficient balance. The wallet doesn't have enough USDC.400 BAD_REQUEST—invalid or unsupported asset. Verify the asset code.
Transfers are asynchronous. The API returns 202 Accepted immediately with a pending status. A background worker submits the transaction to the Stellar network.
First, create a second wallet (recipient) by repeating Step 3, and fund it with XLM via Friendbot (Step 4).
curl -X POST https://api.testnet.fluxa.dev/v1/transfers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_..." \
-d '{
"from_wallet_id": "<sender_wallet_id>",
"to_wallet_id": "<recipient_wallet_id>",
"asset": "USDC",
"amount": "10.50"
}'Expected response (202 Accepted):
{
"id": "0193b0b4-1b33-7e9a-bcf6-2e2a0abb6d41",
"tx_hash": "",
"type": "transfer",
"status": "pending",
"from_wallet_id": "0193b0b4-...",
"to_wallet_id": "0193b0b4-...",
"asset": "USDC",
"amount": "10.5000000",
"fee_amount": "0.0300000",
"net_amount": "10.4700000",
"fee_bps": 30,
"created_at": "2026-06-22T12:00:00Z"
}What could go wrong:
400 BAD_REQUEST—source and destination wallets must differ. Use two different wallets.400 BAD_REQUEST—insufficient balance. The sender wallet doesn't have enough USDC.400 BAD_REQUEST—invalid or unsupported asset. UseUSDCor another supported asset.400 BAD_REQUEST—fee schedule not found. Contact support.
The
fee_amountis the platform fee deducted from the transfer.net_amountis what the recipient receives.
Poll the transfer endpoint until the status changes from pending to confirmed or failed.
curl -H "Authorization: Bearer sk_live_..." \
"https://api.testnet.fluxa.dev/v1/transfers/<transfer_id>"Expected response — pending (200 OK):
{
"id": "0193b0b4-...",
"status": "pending",
...other fields...
}Expected response — confirmed (200 OK):
{
"id": "0193b0b4-...",
"status": "confirmed",
"tx_hash": "a1b2c3d4e5f67890...",
...other fields...
}Expected response — failed (200 OK):
{
"id": "0193b0b4-...",
"status": "failed",
...other fields...
}A simple polling loop in bash:
#!/bin/bash
ID="<transfer_id>"
KEY="sk_live_..."
URL="https://api.testnet.fluxa.dev/v1/transfers/$ID"
STATUS="pending"
while [ "$STATUS" = "pending" ] || [ "$STATUS" = "submitted" ]; do
sleep 2
STATUS=$(curl -s -H "Authorization: Bearer $KEY" "$URL" | jq -r '.status')
echo "Status: $STATUS"
done
echo "Final status: $STATUS"What could go wrong:
404 NOT_FOUND— Wrong transfer ID or the transfer belongs to another tenant.- Transfer stays
pendingindefinitely — The worker may not be running or the Stellar network is slow. Check worker logs and Stellar status.
Instead of polling, register a webhook endpoint that Fluxa will call when a transfer settles. The payload includes an HMAC-SHA256 signature for verification.
curl -X POST https://api.testnet.fluxa.dev/v1/webhooks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_..." \
-d '{
"url": "https://your-app.com/webhooks/fluxa",
"events": [
"transfer.settled",
"transfer.failed"
]
}'Expected response (201 Created):
{
"id": "0193b0b4-1b33-7e9a-bcf6-2e2a0abb6d42",
"url": "https://your-app.com/webhooks/fluxa",
"secret": "a1b2c3d4e5f67890a1b2c3d4e5f67890",
"events": [
"transfer.settled",
"transfer.failed"
],
"active": true,
"created_at": "2026-06-22T12:00:00Z"
}
⚠️ Save thesecret. It is returned exactly once. You'll need it to verify webhook signatures.
Verifying webhook signatures:
Each webhook delivery includes a signature header:
X-Fluxa-Signature: sha256=<HMAC-SHA256 hex>
X-Fluxa-Event: transfer.settled
Verify using the secret:
# Example verification in Node.js
echo '
const crypto = require("crypto");
function verifyWebhook(payload, signature, secret) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(JSON.stringify(payload))
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
' | nodeWhat could go wrong:
400 BAD_REQUEST— Invalid URL format. Ensure the URL is valid and publicly reachable.- Webhook deliveries show
failedstatus — Your endpoint is not reachable or returned a non-2xx status. Check your server logs and firewall.
Check delivery status:
curl -H "Authorization: Bearer sk_live_..." \
"https://api.testnet.fluxa.dev/v1/webhooks/<webhook_id>/deliveries"| Event | When fired |
|---|---|
transfer.initiated |
Transfer created and queued |
transfer.settled |
Transfer confirmed on Stellar |
transfer.failed |
Transfer failed |
wallet.funded |
Wallet received funds |
conversion.completed |
FX conversion executed |
- Import the Postman collection to explore all endpoints interactively.
- Review the Error Reference for a complete list of error codes and resolutions.
- Set up both the API (
cmd/api) and Worker (cmd/worker) processes for local development.