Skip to main content

Step-up for sessions

A person moving money from a session — the web app, or anything acting with a person's sign-in — confirms each payment with a second factor. The proof is an X-Step-Up-Token, bound to the payment's amount, currency and recipient: a token minted for one payment is refused for any other. API keys never step up; see API keys and trusted beneficiaries for what they may do instead.

The web app does all of this for you. This page is for integrators who build their own interface on a person's session.


The binding, per send​

Sendamountcurrencyrecipient
On chainamountthe asset{network}:{address} — the chain the address is on, so across chains the destination's — or {network}:{address}:{memo} with a memo. solana:9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
To an IBANamountthe assetThe IBAN without spaces, upper case: DE89370400440532013000
us_bankamountthe assetus:{routing}:{account}:{checking|savings} — us:101019644:123456789:checking
gb_bankamountthe assetgb:{sort code}:{account}, digits only — gb:123456:34567890
clabeamountthe assetclabe:{clabe}
pixamountthe assetpix:{key}, or pix:{key}|{document} with a document number; an email or random key lower case
By @handleamountthe asset@{handle}, lower case
Swapamountthe asset soldThe destination_account_id
Send back to your EUR accountamountEURThe destination_account_id
Convert EUR into USDCamountEURThe destination_account_id
To a trusted beneficiaryamountthe assetThe entry's own destination, in the form above — not its id
Approve a trusted beneficiary0*trusted: and the destination in the form above: trusted:solana:{address}, trusted:{IBAN}, trusted:us:…, trusted:@{handle}
Revoke a trusted beneficiary0*untrusted:{id}

The rail of a US account and its bank_name are not part of the binding. A replay of a send (the same Idempotency-Key and body) needs no step-up.

Normalisation. The binding compares the amount to two decimal places and the currency and recipient trimmed and lower-cased. So a token bound to a base58 address (Solana, Tron) or a memo also binds the same characters in another case — which is a different address. The send always goes to the address in the body, verbatim: bind the token to exactly what you send.


Minting a token​

The auth portal mints it, after the person confirms with their authenticator code (or passkey), for the signed-in person:

HTTP
POST https://auth.sandbox.cashxchain.com/api/auth/step-up
Content-Type: application/json

{
"code": "123456",
"txn": { "amount": "5.00", "currency": "USDC",
"recipient": "solana:9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" }
}
JSON
{ "token": "eyJ…", "expires_in": 300 }

Send token as X-Step-Up-Token on the payout. It is single-use and valid for five minutes. Two-factor authentication must be set up on the person's sign-in first.


Refusals​

StatuserrorWhenWhat to do
401two_factor_requiredNo X-Step-Up-TokenMint one for this payment
401step_up_expiredOlder than five minutesMint a new one
403step_up_invalidWrong signature, purpose, factor or personMint a new one
403step_up_not_linkedNot bound to a paymentMint one with txn
403step_up_binding_mismatchBound to another amount, currency or recipientMint one for exactly this payment
403step_up_already_usedTokens are single-useMint a new one

A refusal without details.idempotency_key_spent: true spent neither the token nor the Idempotency-Key: correct the request and send it again. With it, the send was recorded and the token spent — see Errors.

Who may. A person of the paying account, in their own session, whose seat holds payouts.create. A session acting for another account (Cxc-Account) is refused 403 session_required on every send to a third party and on a send-back — its step-up would be its own person's, not the customer's.