For the complete documentation index, see llms.txt. This page is also available as Markdown.

Hosted Checkout (On-Ramp & Off-Ramp)

The hosted checkout lets you initiate a transaction from your backend with a single API call and hand your customer a ready-made IvoryPay-hosted payment page. You don't collect wallet addresses, bank details, or run KYC yourself — the checkout UI does it.

Use this when you want IvoryPay to own the end-customer experience. If you'd rather collect everything yourself and drive a single server-to-server call, use the direct Initiate On-Ramp / Initiate Off-Ramp endpoints instead.

This guide is written for businesses integrating IvoryPay. The only endpoints you call from your backend are the two checkout/initiate calls. The checkout/confirm calls are made by the hosted checkout UI on your customer's behalf — you do not need to implement them, but they're documented here so you understand the full lifecycle.

How it works

  YOUR BACKEND                 IVORYPAY                       YOUR CUSTOMER
  ────────────                 ────────                       ─────────────
  1. POST .../checkout/initiate ──▶  creates PENDING txn
                               ◀── { reference, checkoutUrl }
  2. redirect customer to checkoutUrl ───────────────────────▶ opens hosted page
                                     authenticates (email OTP)  ◀─ enters details
                                3. checkout/confirm (UI does this)
                               ◀── transaction goes INITIATED
                                     customer pays / sends crypto
  4. webhook: success/failed ──▶ your server
  1. You call POST /v1/onramp/checkout/initiate (or /v1/offramp/checkout/initiate) from your backend with API-key auth. You get back a reference and a checkoutUrl.

  2. You redirect the customer to checkoutUrl.

  3. The hosted checkout authenticates the customer (email OTP → customer JWT), collects the remaining details, and calls checkout/confirm. The transaction moves from PENDING to INITIATED.

  4. The customer completes payment (on-ramp: pays fiat; off-ramp: sends crypto). You're notified via webhooks.

The checkoutUrl format is:

Payment method types by currency

The payment rail is determined automatically by the fiatCurrency of the transaction. You don't pass the rail explicitly — but the details collected at checkout differ, so it's worth understanding the three cases you'll encounter:

Your customer pays/receives via

Rail (type)

Currencies

Bank account (e.g. NGN via NIBSS)

BANK_TRANSFER

NGN

EFT bank transfer (South Africa)

BANK_TRANSFER

ZAR

Mobile money (UGX, and most of East/West Africa)

MOBILE_MONEY

UGX, GHS, KES, TZS, RWF, ZMW, XOF, XAF, CDF, ETB, LSL, MWK, MZN, SLE

What this means per flow

On-ramp (customer pays fiat, receives crypto):

  • BANK_TRANSFER currencies (NGN/ZAR): the confirm response returns a virtual account (accountName, accountNumber, bank) the customer transfers into.

  • MOBILE_MONEY currencies (UGX etc.): the customer pays from their mobile-money wallet. A phone number is required — if the customer has none on file they supply it at confirm time.

Off-ramp (customer sends crypto, receives fiat):

The customer must have a saved bank account to receive the payout, and selects it at confirm time by bankAccountId. The saved account's type must match the rail of the off-ramp's fiatCurrency:

fiatCurrency

Saved account type

Key fields on the saved account

NGN, ZAR

BANK_TRANSFER

accountNumber, bankCode, accountName

UGX, GHS, KES, …

MOBILE_MONEY

accountNumber (E.164 phone), operator

Customers manage their saved accounts via the Customer Bank Accounts endpoints (the hosted checkout does this for them).

On-ramp checkout — end to end

See Initiate On-Ramp Checkout for the full field reference.

Off-ramp checkout — end to end

See Initiate Off-Ramp Checkout for the full field reference.

Idempotency

Both checkout/initiate calls are idempotent on reference while the transaction is still PENDING. If your call times out and you retry with the same reference, you get back the same checkoutUrl and status rather than a duplicate. Once the customer has moved past PENDING (they've started confirming), re-using the reference returns 409 DUPLICATE_TRANSACTION — generate a fresh UUID for a new attempt.

Auth on checkout/initiate

Endpoint
Accepts

POST /v1/onramp/checkout/initiate

API key or business-user JWT (HybridHTTPAuthGuard)

POST /v1/offramp/checkout/initiate

API key only (APIKeyHTTPAuthGuard)

For server-to-server integration, send your merchant API key in the Authorization header on both. (Business-user JWTs are used when a logged-in dashboard user drives the checkout — see Business Checkout Integration.)

Last updated