Payments
A payment moves stablecoin value from one member to another. The sender always signs in their own wallet, so a payment is never a single write. The application creates the payment, the sender signs an intent, the XKOVA Relayer submits it, and the chain settles. This page covers the two shapes a payment takes (a direct send and a payment request) and the lifecycle each one follows.
Two shapes
The payments surface covers two directions of value movement.
- Direct send. The sender pushes value to a recipient. The sender's application creates the payment, then the sender signs it. This is the core money movement primitive.
- Payment request. A recipient asks a payer for value. The request is created first, the payer is notified, and when the payer approves, a direct send is spawned on the payer side and signed as usual.
Both end in the same place: a signed transfer that the XKOVA Relayer submits and XKOVA confirms on chain. See Transfers and Settlement for how a signed transfer reaches a confirmed on chain state.
Funding the sender wallet first
A member's wallet is derived the first time they sign in, and a freshly derived wallet holds nothing. A balance is a running total XKOVA maintains from the wallet's on chain activity, so a wallet that has never received value reads as a zero balance. Before a member can make their first payment, value has to arrive in that wallet. The seam is derive, then fund, then pay.
- Derive. The wallet is provisioned at first sign in and resolves to a deterministic address. XKOVA never holds the key. See Wallets and Balances for the handshake.
- Fund. Bring value into the wallet. Create a funding request with
POST /v1/funding-requests, which converts fiat to stablecoin and credits the minted stablecoin to the wallet named indestination_address. The call returns202and the request advances asynchronously throughpending_compliance,pending_issuance, andsettled; pollGET /v1/funding-requests/{id}to follow it. In sandbox the request runs against a test ramp profile, so no real fiat moves and the wallet is credited with test stablecoin on Avalanche Fuji. - Pay. Once the funding request settles and the balance is on chain, create the
payment.
POST /v1/paymentstakes afrom_wallet_id, and the member signs the intent through the lifecycle below.
You can confirm the wallet is ready before asking the member to sign. Run the feasibility check
(POST /v1/payments/simulate): on a zero balance wallet it returns
would_succeed: false with balance_sufficient: false and a revert_reason
of insufficient_balance, so you catch an unfunded wallet before creating a row. The full funding
and cash-out surface, including the interchangeable conversion partners, is on
Funding and Off-ramp. For where this step fits in the operator setup path, see
Operator Onboarding.
The direct send lifecycle
A direct send moves through four steps. Quote and create are server calls your backend makes. Sign is done by the member in their own wallet. Submit hands the signature back to the API, which builds the calldata and relays it.
- Quote. Ask for a fee quote for a prospective payment. The quote breaks fees out by component so you can show the payer an estimate and catch a likely compliance block early. A quote is advisory: create always recomputes fees and re-runs compliance at write time, so treat the create response, not the quote, as the source of truth for the fees that apply.
- Create. Create the payment. The payment lands in a pending sign state and the API returns a signing URL pointing at the member signing experience.
- Sign. The member opens the signing session and signs a typed data intent in their own wallet. The session returns the exact typed data to sign and a short lived token the member's wallet uses to connect. The member never hands over a private key, and the platform never holds one.
- Submit. The signed intent is submitted back to the API. The API recovers the signer, builds the meta transaction calldata, and the XKOVA Relayer submits it on chain and pays gas.
flowchart TD
Q[Quote, optional and advisory] --> A[Create payment]
A --> B{Compliance screen}
B -- blocked --> X[Blocked, terminal]
B -- review --> R[Held for operator triage]
R -- released --> C
B -- allowed --> C[Pending sign]
C --> D[Member signs typed data in their wallet]
D --> E[Submit signatures to the API]
E --> F[XKOVA Relayer submits on chain, pays gas]
F --> G{On chain result verified}
G -- success --> H[Confirmed]
G -- reverted --> I[Failed, no value moved]
Signing is the non-custodial seam
The sign step is what makes a payment non-custodial. The member signs typed data, and a relayer wraps that signature in the token's meta transaction entrypoint. The contract verifies the signature on chain before it moves anything. The full mechanics are in the Non-custodial Model.
Payment requests
A payment request is recipient initiated. The recipient creates the request, which represents an inbound ask for value. The payer can approve, decline, or be reminded. Approving spawns a payment on the payer side, which the payer's member then signs through the same quote, create, sign, submit flow as a direct send.
| Action | Who | Effect |
|---|---|---|
| Create | Recipient | Opens the ask and notifies the payer. |
| Approve | Payer | Spawns a payment on the payer side to be signed. |
| Decline | Payer | Closes the request without paying. |
| Remind | Recipient | Nudges the payer with a reminder. |
| Cancel | Recipient | Withdraws the ask. |
Reading state
Every payment carries a state and a timeline of transitions you can read back. Fetch a single payment to see its current state, or read its event timeline to see each transition in order. A tenant wide list with cursor pagination and filters lets you reconcile across many payments, and a unified transactions read merges direct sends and escrows into one timeline when you want both in a single feed, covered next.
The unified transactions read
Direct sends and escrows each have their own list, but reconciliation usually wants both in one feed.
GET /v1/transactions returns a single timeline that merges direct sends and escrows into one
list for the calling tenant, newest first. Each row carries a kind discriminator of
direct or escrow, so a client branches on kind to decide which fields
are meaningful. The endpoint is read only at the list level: to act on a row, follow its kind to
the detail endpoint, GET /v1/payments/{id} for a direct send or GET /v1/escrows/{id}
for an escrow, each of which carries its own state machine and actions.
Every row carries the fields common to both kinds: the chain descriptor, gross and net amount, token
symbol, a raw kind-specific status, a canonical status, and timestamps. Escrow-only fields
(recipient_ref, claim_state, claim_path,
on_chain_payment_id, expires_at) are null on direct rows.
Filters
| Filter | Effect |
|---|---|
kind | Narrows to one side. direct or escrow; any
other value returns validation_failed. |
status_canonical | The shared four state bucket applied across both kinds:
queued, pending, success, or failed. Each kind's own
states fold into these buckets, so failed spans a cancelled or expired escrow as well as a
failed direct send. |
date_from, date_to | Bound the created-at window on both sides. |
Cursor pagination
The list is cursor paginated like every other list on the API, returning { data, next_cursor,
has_more }. Pass next_cursor back as the cursor query parameter to page
forward, and stop when has_more is false and next_cursor is null.
limit defaults to 25 and is capped at 100. Ordering is by created-at descending across both
kinds, which keeps the read stable for a reconciliation sweep that walks every transaction a tenant produced.
The read is tenant scoped, the scope integrators use; the internal platform scope that sits above tenants
does not reach it. See Pagination for the cursor contract in full.
Failure and retry
A payment that fails on chain is marked retryable when the failure is one a retry can clear. You can cancel a payment only before it has been signed. After signing, the transfer is in flight and the right move is to wait for settlement or retry if it is marked retryable. See Errors and Correlation IDs for telling a retryable failure from a permanent one, and Idempotency for making create safe to retry without double sending.
Errors worth handling per step
The error catalog documents every code; these are the ones each step of the lifecycle most commonly returns.
| Step | Codes |
|---|---|
| Quote | validation_failed, token_not_enabled,
chain_not_allowed, compliance_blocked, first_use_required |
| Create | validation_failed, idempotency_conflict,
token_not_enabled, workspace_setup_incomplete,
compliance_blocked, authorization_denied |
| Signing session / member JWT | unauthorized (an expired or invalid signing
token), verification_required, compliance_case_open,
not_found |
| Submit signatures | invalid_state (already signed or past signing),
authorization_invalid (the signature does not recover to the expected signer),
verification_required, service_unavailable |
Related
- Transfers and Settlement: the relayer lifecycle and on chain confirmation.
- Escrow and Claims: when the recipient is not yet known or onboarded.
- Wallets and Balances: the member wallet a payment signs from.