Crypto in. Naira settled.
One API.
Issue a deposit wallet to any of your users. The moment their crypto confirms on-chain, Monica converts it at the locked rate and settles naira to your corporate account — in under 60 seconds — while a signed webhook tells your system exactly what happened.
POST /v1/wallets with your user's reference. You get back a unique
deposit address for the asset and network you chose. Show it to your user however you like.deposit.detected webhook with the txid and amount — before it even confirms.deposit.confirmed with the exact
naira value. Monica is an off-ramp — deposit addresses are settlement endpoints, not custodial wallets.settlement.paid closes the loop with the bank reference. Reconcile by
customer_ref, deposit_id or settlement_id — your choice.API access is issued to registered businesses after a short due-diligence review (KYB). To request keys, write to [email protected] with your company name, RC number, website, expected monthly volume, and the corporate bank account that should receive settlements. Most reviews complete within two business days.
You'll receive two key pairs — sk_test_… for the sandbox and sk_live_… for production —
plus a whsec_… webhook signing secret for each environment. Send your key on every request:
# Every request
Authorization: Bearer sk_live_9f2c7a1e4b8d…
Content-Type: application/json
Idempotency-Key header on every POST.
If a request is retried with the same key within 24 hours you get the original response back,
never a duplicate wallet.
/v1/walletsIssue a deposit wallet for one of your users# Request
curl -X POST https://api.monica.cash/v1/wallets \
-H "Authorization: Bearer sk_live_9f2c7a1e4b8d…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c1e-user-8841-usdt" \
-d '{
"customer_ref": "user_8841",
"asset": "USDT",
"network": "TRC20",
"label": "Deposit wallet — user_8841"
}'
# Response — 201 Created
{
"id": "wal_01JXK4N9Q2M7",
"object": "wallet",
"customer_ref": "user_8841",
"asset": "USDT",
"network": "TRC20",
"address": "TWd2yzw5yFc9C3jn…",
"status": "active",
"created_at": "2026-07-06T09:41:02Z"
}
| Field | Type | Notes |
|---|---|---|
customer_ref | string · required | Your internal ID for the end user. Echoed on every deposit and webhook so you can reconcile without storing our IDs. |
asset | string · required | BTC, USDT, USDC, ETH, SOL, TRX, BNB |
network | string · required | Must match the asset — e.g. TRC20, ERC20, BEP20, SPL, BITCOIN |
label | string · optional | Free text, ≤120 chars, shown in your dashboard exports. |
Addresses are permanent. One user can hold one wallet per asset/network pair —
repeat calls with the same customer_ref + pair return the existing wallet (200, not 201).
/v1/wallets/{wallet_id}Fetch one wallet/v1/wallets?customer_ref=user_8841List a user's wallets/v1/deposits/{deposit_id}Fetch one deposit/v1/deposits?wallet_id=wal_01JXK4N9Q2M7List deposits on a wallet# Deposit object
{
"id": "dep_01JXK7RQ83VD",
"object": "deposit",
"wallet_id": "wal_01JXK4N9Q2M7",
"customer_ref": "user_8841",
"asset": "USDT",
"network": "TRC20",
"amount": "150.000000",
"txid": "b2a4c9f7e1d8…",
"confirmations": 1,
"status": "settled",
"rate_ngn": "1621.50",
"ngn_amount": "243225.00",
"settlement_id": "set_01JXK7SM4T9A",
"detected_at": "2026-07-06T09:41:02Z",
"confirmed_at": "2026-07-06T09:41:19Z",
"settled_at": "2026-07-06T09:41:47Z"
}
status walks the lifecycle: detected → confirmed → converted → settled.
Amounts are strings to preserve precision; never parse them as floats for accounting.
/v1/settlements/{settlement_id}Fetch one payout to your bank/v1/settlements?from=2026-07-01&to=2026-07-06List settlements for reconciliation# Settlement object
{
"id": "set_01JXK7SM4T9A",
"object": "settlement",
"deposit_id": "dep_01JXK7RQ83VD",
"ngn_amount": "243225.00",
"bank_reference": "NIP/260706/094147/8823",
"account_last4": "7031",
"status": "paid",
"paid_at": "2026-07-06T09:41:47Z"
}
/v1/ratesCurrent NGN conversion rates for every supported assetIndicative only — the binding rate is always the one locked at confirmation,
returned on the deposit object and in deposit.confirmed.
Register an HTTPS endpoint during onboarding (or change it later via
[email protected]).
Every event is a POST with a JSON body and two headers you must verify.
| Event | Fires when |
|---|---|
deposit.detected | A transaction to one of your wallets is seen in the mempool / first block. Unconfirmed — treat as informational. |
deposit.confirmed | Required confirmations reached. Rate locked, naira value fixed. Safe to credit your user's ledger. |
deposit.converted | The crypto→NGN conversion executed on Monica's treasury. |
settlement.paid | Naira left for your corporate account. Carries the bank reference for reconciliation. |
wallet.created | A wallet was issued (including via dashboard). Useful for audit trails. |
# Delivery — example: deposit.confirmed
POST https://yourapp.com/webhooks/monica
X-Monica-Timestamp: 1783158079
X-Monica-Signature: 6e8c1d0a94b7f3…
{
"id": "evt_01JXK7RS0P2Q",
"type": "deposit.confirmed",
"created_at": "2026-07-06T09:41:19Z",
"data": {
"deposit_id": "dep_01JXK7RQ83VD",
"wallet_id": "wal_01JXK4N9Q2M7",
"customer_ref": "user_8841",
"asset": "USDT",
"network": "TRC20",
"amount": "150.000000",
"txid": "b2a4c9f7e1d8…",
"confirmations": 1,
"rate_ngn": "1621.50",
"ngn_amount": "243225.00",
"status": "confirmed"
}
}
Verifying signatures
Compute an HMAC-SHA256 over timestamp + "." + raw_body with your
whsec_… secret and compare it, constant-time, to X-Monica-Signature.
Reject anything older than 5 minutes.
# Pseudocode — any language
payload = header("X-Monica-Timestamp") + "." + raw_request_body
expected = hex( hmac_sha256(key: WEBHOOK_SECRET, message: payload) )
if !constant_time_equals(expected, header("X-Monica-Signature")): reject 401
if abs(now() - header("X-Monica-Timestamp")) > 300: reject 401
process(event)
respond 200
2xx within 10 seconds — do your heavy work async.
Failed deliveries retry with exponential backoff for 24 hours. Events can very occasionally
arrive twice or out of order: key your handlers on event.id and treat them as idempotent.
# Every error, same shape
{
"error": {
"code": "unsupported_network",
"message": "USDT is not available on network BITCOIN.",
"param": "network"
}
}
| Status | Code | Meaning |
|---|---|---|
400 | validation_error | A field is missing or malformed. param names it. |
401 | invalid_key | Missing, revoked, or wrong-environment API key. |
403 | account_restricted | Your account is under review — check your inbox. |
404 | not_found | No such wallet / deposit / settlement on your account. |
409 | idempotency_conflict | Same Idempotency-Key reused with a different body. |
422 | unsupported_network | The asset/network pair isn't supported. |
429 | rate_limited | Over 120 requests/minute. Honour Retry-After. |
5xx | internal | Our side. Safe to retry with the same idempotency key. |
Everything above works identically at https://sandbox.api.monica.cash/v1 with your
sk_test_… key. Sandbox wallets accept testnet coins (TRC20 Shasta, Ethereum Sepolia,
Bitcoin testnet3, Solana devnet), confirmations are accelerated, and settlements post to a
virtual ledger you can inspect at GET /v1/settlements — no real bank movement, no real crypto.
Webhooks fire exactly as in production, signed with your test whsec_….
Monica is a direct crypto-to-naira off-ramp operated by Monica Technologies Limited, Lagos, aligned with Nigeria's SEC VASP framework. We do not provide custodial wallet services: a deposit address issued through this API is a settlement endpoint, and on on-chain confirmation title to the crypto passes to Monica's treasury against our obligation to settle naira to your account. Partners must be registered businesses, complete KYB, and remain responsible for KYC on their own end users. Full terms are issued with your API agreement.
Ready to build on the off-ramp?
Tell us about your business and volumes — most integrations go live within a week of KYB approval.
[email protected] Include your company name, RC number, website, expected volume and settlement account.