How payments work on SDP
Wallets, tokens, server-side signing, fees, and the data model that backs SDP payments.
SDP payments move value between Solana accounts: a custody wallet sends tokens to a destination address, SDP records the transfer, and the rest of the section describes how to send those, accept incoming ones, ramp into and out of fiat, and reconcile activity against your product database.
This page introduces the building blocks the rest of the section assumes.
Custody wallets
Every SDP payment is anchored to a custody wallet — a Solana keypair whose private key SDP holds, scoped to an organization and (optionally) a project. Wallets sign outbound transfers on your behalf and contribute to balances and policies for the addresses you control.
You provision custody wallets up front (see Set Up Wallets) and reference them by their SDP wallet_id (wal_…). Transfer endpoints expect the source as a custody wallet ID — the wallet is resolved server-side from the caller's custody-wallet list — and the destination as the on-chain Solana address of the recipient. Ramp endpoints vary by provider: MoonPay and BVNK accept either a wallet ID or a Solana address for sourceWallet / destinationWallet; Lightspark uses its own ExternalAccount:… identifiers (onramp destinationWallet also accepts a Solana address, offramp sourceWallet does not).
Tokens and token accounts
Payments transfer SPL tokens — stablecoins, tokenized assets, or any mint your project supports. The token field on POST /v1/payments/transfers requires the on-chain mint address for SPL tokens; the literal string SOL is accepted as the native-SOL shorthand. Symbols like USDC are not resolved at request time. The token filter on GET /v1/payments/transfers does an exact match against the Transfer.token value as stored — API-created transfers store the mint you supplied (or SOL), while indexed/observed transfers run mint-to-symbol resolution and may store a resolved symbol (e.g. USDC). Filter using the same label you see on the response, or query without token and filter client-side. Balance responses always attempt symbol resolution and return the symbol whenever the mint is known — so a wallet holding USDC reports USDC rather than the mint string.
Amounts on the wire are UI-unit decimal strings — "100.00" means 100 tokens, regardless of the mint's decimal count. This applies to issuance operations (mint, burn, seize, force-burn) as well as payments transfers. SDP handles the smallest-unit conversion when it builds the on-chain transaction. The one exception is wallet-balance responses, which return both a raw amount (smallest unit) and a uiAmount (decimal string) so balance consumers can pick whichever fits.
Transfer execution
Outbound wallet transfers are server-executed through POST /v1/payments/transfers. SDP builds the transaction, signs with the source wallet's custody key, submits it on the configured Solana cluster, and records the resulting transfer lifecycle.
The rest of this section assumes transfers are submitted by SDP from a custody wallet you control.
Fees and sponsorship
Solana fees are paid in SOL by the transaction's fee payer. On wallet transfers, the source custody wallet is the fee payer by default; SDP can be configured to sponsor fees so end-user wallets do not need a SOL balance.
The transfer data model
Every successful or attempted transfer creates a Transfer record. The fields that matter for most flows:
| Field | Meaning |
|---|---|
id | SDP-internal transfer identifier (use this in API URLs). |
status | One of pending, processing, confirmed, finalized, failed. See Verifying a payment. |
direction | inbound (someone paid you) or outbound (you paid someone). |
source, destination | On-chain addresses. |
token, amount | What moved and how much. amount is a UI-unit decimal string (e.g. "100.00"). |
memo | Optional UTF-8 string, up to 256 chars. See Payment with memo. |
signature | Solana transaction signature once confirmed. Unique across SDP — the natural dedup key. |
slot, blockTime, fee | On-chain settlement details, populated as the transaction lands. |
error | Set if the transfer failed; the rest of the record explains what reached the network. |
risk | Optional risk-score metadata, when a risk provider is configured. |
Outbound wallet transfers start at processing and move to confirmed → finalized. Inbound transfers discovered on-chain surface directly at confirmed. Ramp transfers can start at pending while provider-side steps are still underway. See Verifying a payment for the full state table.
What SDP does and does not give you
SDP exposes everything you need to send, track, and reconcile payments at the transfer level: status polling, filtered lists, wallet-level balances, and a UNIQUE signature on the transfer record for client-side dedup (see Basic payment → Deduplication). It does not ship higher-level commerce primitives — there is no checkout session, payment intent, or invoice object in the API today, no webhooks for settlement events, and payment endpoints do not honor an Idempotency-Key header (unlike issuance endpoints). The Accept payments section shows how to build those abstractions on top of the transfer model.
Related
- Payments API reference — auto-generated endpoint reference.
- End-to-end payment flow — a step-by-step tutorial through the same lifecycle from a different angle.