# Rimoswap Payments API Accept crypto payments. Create a charge server-side, send the customer to a hosted checkout, receive a signed webhook when it is paid. Customers with a Rimoswap account settle instantly from their balance with no network fee. Base URL: https://rimoswap.com/api Docs (HTML): https://rimoswap.com/docs/payments Get an API key: https://rimoswap.com/developers ## Authentication All server-side calls require your API key in a header: X-Api-Key: rmo_live_xxxxxx_... The key is shown once at creation and stored hashed. It must stay server-side: it can create charges against the merchant account. Never ship it in browser code, a mobile app, or a public repo. ## Test mode Create a key with "Test" selected under Developers to get a test key (rmo_test_...). Test keys work before business verification is approved and never move money. Charges made with one show "Test mode" on the hosted checkout, carry "test": true on the charge and every webhook, and are settled only by simulating: POST /api/v1/charges/{code}/simulate (X-Api-Key: your test key) { "outcome": "paid" } // or "expired" This sends the matching signed webhook. Live keys cannot see test charges and test keys cannot see live ones. Test keys cannot use the Business API. Never fulfil a real order for a webhook whose charge has "test": true. ## Create a charge POST /api/v1/charges Request body: { "name": "Order #1042", // required, shown on checkout "description": "2x Blue T-Shirt", // optional "amount": 25.00, // required, decimal, in `currency` "currency": "USD", // "USD" or "NGN", default USD "metadata": { "order_id": "1042" }, // optional, returned on the webhook "redirect_url": "https://store.com/thanks", // optional "cancel_url": "https://store.com/cart" // optional } Response 201: { "charge": { "code": "F3ADAB39B66D", "status": "new", "local_amount": 25, "local_currency": "USD", "expires_at": "2026-08-19 17:47:39", "hosted_url": "https://rimoswap.com/c/F3ADAB39B66D", "metadata": { "order_id": "1042" } } } Redirect the customer to `hosted_url`. They select a coin; the crypto amount is locked against the fiat price for 20 minutes. The charge expires after 1 hour. ## Retrieve a charge GET /api/v1/charges/{code} (requires X-Api-Key) Use for reconciliation or if a webhook was missed. Do not poll instead of using webhooks. ## Webhooks Set a webhook URL on the API key. We POST JSON on each status change and retry non-2xx responses with backoff: 1m, 5m, 25m, 2h, 10h (6 attempts). Headers: X-Rimoswap-Signature: X-Rimoswap-Event: charge:confirmed Body: { "id": "9f2b1c7d4e5a6b8c", "event": "charge:confirmed", "created_at": "2026-08-19T16:04:11+00:00", "data": { "code": "F3ADAB39B66D", "status": "confirmed", "local_amount": 25, "local_currency": "USD", "coin": "USDT", "crypto_amount": 25.0160951, "metadata": { "order_id": "1042" } } } Events: charge:created, charge:confirmed, charge:expired. Only fulfil an order on charge:confirmed. Deduplicate on `id` — a retry can deliver the same event twice. ## Verifying the signature HMAC-SHA256 of the RAW request body, keyed with the webhook signing secret. Compute over the exact received bytes; re-serialising the parsed JSON will not match. Compare in constant time. PHP: $raw = file_get_contents('php://input'); $expected = hash_hmac('sha256', $raw, RIMOSWAP_WEBHOOK_SECRET); if (!hash_equals($expected, $_SERVER['HTTP_X_RIMOSWAP_SIGNATURE'] ?? '')) { http_response_code(400); exit; } Node (Express — note express.raw, not express.json): const expected = crypto.createHmac('sha256', SECRET).update(req.body).digest('hex'); const given = req.get('X-Rimoswap-Signature') ?? ''; const ok = expected.length === given.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given)); Python: expected = hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest() hmac.compare_digest(expected, request.headers.get("X-Rimoswap-Signature", "")) ## Charge lifecycle new created, unpaid, payable for 1 hour confirmed paid and settled — fulfil the order expired the hour elapsed unpaid Balance settlement is final on confirmation; no chain confirmations are involved because no chain transaction occurs. ## Errors JSON body with an `error` field. 401 missing or invalid API key 403 merchant account deactivated 404 no charge with that code 409 charge no longer payable (already paid or expired) 422 validation failed — the message names the field ## Integration summary 1. POST /api/v1/charges with X-Api-Key and an amount in USD or NGN. 2. Redirect the buyer to charge.hosted_url. 3. On POST to your webhook URL, verify X-Rimoswap-Signature over the raw body. 4. If event == "charge:confirmed", fulfil the order keyed on data.metadata.order_id. Deduplicate on the event id. --- # Rimoswap Infrastructure API Wallets, naira collections, swaps and payouts for a business's own customers. One API key (X-Api-Key). Test keys (rmo_test_) run against a funded sandbox at once; live keys (rmo_live_) move money after business verification and approval. Docs (HTML): https://rimoswap.com/docs/infrastructure Keys and live access: https://rimoswap.com/business/infrastructure Postman: https://rimoswap.com/downloads/rimoswap-infrastructure-api.postman_collection.json Model: every customer deposit is credited to the business's one Rimoswap balance, tagged with the business's customer_id. Payouts and swaps spend from that balance. Amounts are decimal strings. Networks by name: tron, bsc, base, solana, bitcoin (GET /v1/assets lists all). Endpoints (base https://rimoswap.com/api): - GET /v1/account, /v1/assets, /v1/rates, /v1/balances - POST /v1/customers/addresses {customer_id, coin, network} -> address (idempotent) - GET /v1/customers/{customer_id}/addresses - GET /v1/deposits?customer_id=&limit=&before= - POST /v1/sandbox/deposits {customer_id, coin, network, amount} (test keys) - POST /v1/swaps/quote {from, to, amount} -> quote (30 s); POST /v1/swaps {quote_id} - POST /v1/payouts/quote; POST /v1/payouts {coin, network, amount, address, customer_id?, expected_total_debit?} - GET /v1/payouts/{id}, /v1/payouts - GET /v1/banks; POST /v1/banks/resolve {bank_code, account_number} - POST /v1/bank-payouts {bank_code, account_number, amount, account_name?, customer_id?, narration?} - GET /v1/bank-payouts/{id}, /v1/bank-payouts - GET /v1/collections/quote?amount=; POST /v1/collections {customer_id, amount, customer:{first_name, last_name, bvn, nin}} -> bank details for one naira payment; credited as USDT (minus the collection fee) when paid. GET /v1/collections/{id}, /v1/collections - POST /v1/sandbox/collections/{id}/pay (test keys) - GET /v1/pairs; swap quotes accept "amount_type": "receive" to get an exact amount - POST /v1/addresses/validate {coin, network, address} - GET /v1/ledger?coin=&limit=&before= signed lines of every API balance movement Rules: - POST /v1/payouts and /v1/bank-payouts require an Idempotency-Key header (16-80 chars). Retry with the SAME key; a different body under it is 409. - Errors: {"error": "...", "code": "..."}; branch on code. - Keys can be narrowed to some products; outside them: 403 key_not_allowed. - Webhooks: deposit:confirmed, collection:completed|expired|failed, swap:completed, payout:processing|completed|failed, bank_payout:processing|completed|failed. Verify X-Rimoswap-Signature = HMAC-SHA256(raw body, key's webhook secret). De-duplicate on data.id. - Sandbox: payouts complete at once; "sandbox_outcome": "failed" forces a failure; bank account 0000000000 does not exist.