Skip to main content

Sessions and orders

Your backend mints a single-order collection session with pulse.collectionSessions.create(). The session is the bridge to the browser: it returns a session_token (cs_…), and the order it minted carries everything the payment surface needs — deposit_address, qr_payload (Solana Pay / EIP-681), quote (rate, asset_amount, locked_until), status, amount_status, deposit, and disbursement. Whether you render with a drop-in component or build your own UI against the API, this order is what you’re driving to completion.

Fiat gates

Every session picks one of two fiat gates:
  • naira — carries per-user identity. Add user_ref plus an account block: { provision: true, bvn, nin } for a first-time customer, so Pulse provisions their named virtual account, or { provision: false, virtual_account_ref } for a returning one. BVN/NIN are sensitive — they travel server-to-server only and never reach the browser.
  • usd — no user_ref, no account block. Funds land in your omnibus rather than a per-user account.
Each order also has a direction: offramp (the default — your user pays crypto and you collect fiat) or onramp (your user pays fiat and receives crypto). The engine-direct onramp flow is live today — see Onramp.

Assets and networks

Sessions accept stablecoin assets (USDC, USDT) on a network. Two network surfaces exist, and the casing tells you which one you’re on:
  • Network (product surface — collection sessions) is lowercase: base, polygon, arbitrum, and others. Solana and TON are recognised but coming soon (see Supported stablecoins).
  • EngineNetwork (engine surface — quotes, rates, offramp, onramp) is UPPERCASE: BASE, POLYGON, ARBITRUM, OPTIMISM, ETHEREUM, BSC.
Pass the casing that matches the call you’re making. The full asset and network lists live in the SDK reference.

Quotes and locking

A quote is a locked price; creating one moves no money and creates no order. The order’s quote carries the locked rate, the asset_amount to send, and a locked_until expiry. A separate rates.preview() call reshapes a quote for display with no order created, returning { rate, youSend, youReceive, expiresAt, quoteId }. When an order’s quote expires, re-lock it with collectionOrders.refreshQuote(orderId) rather than creating a new order.

The money-moment lifecycle

A payment moves through the states reflected in the webhook event names: collection.order.createdcollection.deposit.detectedcollection.deposit.confirmeddisbursement.completed. Alongside the happy path, edge states can occur at any point: collection.amount.underpaid / .overpaid, collection.deposit.wrong_chain (funds arrived on the wrong network; the order is still payable on the right one), collection.order.expired, and collection.order.failed. The sandbox can simulate every one of these directly against a real order.

Webhooks are the source of truth

Client-side callbacks like onSuccess are UX signals only — they can be spoofed.
Credit a customer only after a verified disbursement.completed webhook. Client-side callbacks such as onSuccess are UX signals — they can be spoofed. The signed webhook is the only source of truth for crediting a user.