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

Managed Custody Mode

Core banking API reference for partners integrating via Managed Custody Mode.

This document is the Core Banking OpenAPI reference for Partners integrating with UR in Managed Custody Mode. Section 3 covers onboarding for Partners whose users complete KYC in the Partner's Sumsub flow and share that verification with UR through Sumsub reuse. Fund-moving APIs still require the user's UR account to be Live and the mapped Partner user to exist.

For the conceptual definition of Managed Custody Mode and how it compares to External Wallet Access Mode, see Integration Guide.


1. Mode context

1.1 Where this API sits

Managed Custody Mode is one of UR's two Account Modes. In this mode:

  • The user's UR account (URID + tokenized fiat balances) lives inside a UR-managed embedded wallet. For compliance, this wallet holds tokenized fiat only; it never custodies the user's crypto, which always sits in an external (non-UR) wallet.

  • The Partner backend accesses that account entirely through REST APIs, signed with the Partner's signer key.

  • Your backend submits routine banking actions (FX, internal transfers, Pay-in, Payout, On-ramp (coming soon), Off-ramp, Card). UR validates each request, runs compliance, risk, and limit checks, and executes on-chain settlement using UR's wallet infrastructure.

  • The user is not prompted to sign on-chain transactions for routine banking actions.

The Partner owns the entire UX surface; UR is the regulated financial infrastructure underneath.

1.2 Operation layer map

Every Core Banking endpoint maps to one of UR's seven core operations. This table is the canonical anchor for the rest of this document.

Operation
Direction
Endpoint family
Settlement

On-ramp (coming soon)

User fiat → crypto (delivered to an external wallet)

Async via webhook

Off-ramp

Crypto (from an external wallet) → user fiat

Async via webhook

FX

One fiat token → another fiat token

On-chain, near-instant; webhook confirms

Internal transfer

One UR Account → another UR Account

On-chain; webhook confirms

Pay-in

External bank → user IBAN

Covered in Deposits

Async (SEPA/SWIFT)

Payout

User fiat → external bank

Async (SEPA/SWIFT)

Card

User → merchant via Mastercard

Real-time authorization, async settlement

Read-only endpoints (Profile §5, Balance §6, Transactions §12) sit beside these operations.


2. API foundation

2.1 Base URLs

Environment
Base URL

Production

https://openapi.ur.app

Preview

https://openapi-preview.ur.app

Testnet

https://uropenapi-qa.ur-inc.xyz

Confirm the exact base URL set with UR before production rollout.

2.2 Authentication: Partner Auth (EIP-191)

All authenticated Partner → UR requests use Partner Auth with EIP-191 signatures.

Every authenticated request must include:

Header
Required
Description

X-Api-Signature

Yes

0x-prefixed 65-byte hex signature over the Partner Auth message, produced with the Partner signer's private key.

X-Api-Deadline

Yes

Unix seconds. UR rejects the request if now > deadline. Recommended validity window ≤ 5 min.

X-Api-PublicKey

Yes

The Partner signer address (0x-prefixed). Must be registered with UR.

Canonical payload:

  • GET requests sign the raw query string exactly as sent, without the leading ?.

  • Non-GET requests sign the raw request body exactly as sent.

  • If the request has no query string or body, use an empty string.

The canonical payload is part of the signed message. {canonicalPayload} is not literal text. Replace it with the exact request body or query string that your backend sends.

Build the Partner Auth message from the canonical payload, user identity suffix, and deadline:

If an identity header is not sent, use an empty value in its slot. Do not add a separator before urId:. Add one ASCII space before X-Api-Deadline.

For example, a POST body of {"amount":"100"} with X-Ur-Id: 7123456789, no X-External-User-Id, and X-Api-Deadline: 1772002211 signs:

For a GET /api/fma/v1/kyc/form-a-info?sessionId=abc123 request with the same headers, sign:

For the EIP-191 recovery algorithm, see Signature and Verification.

2.3 User identity headers

Every user-scoped Partner → UR endpoint must identify the user with at least one of the following headers.

Header
Description

X-Ur-Id

The user's URID (numeric token ID of the URID NFT).

X-External-User-Id

The Partner's own user ID, mapped to a URID during onboarding.

Rules:

  • Send at least one of X-Ur-Id or X-External-User-Id. Sending neither is invalid.

  • Sending both is allowed. When both are present, UR resolves the user by X-Ur-Id first.

  • User identity must not be duplicated in query parameters or request bodies for user-scoped APIs.

Example:

or:

2.4 Header block references

To avoid repeating long header tables, endpoint sections refer to these named blocks.

  • User-Scoped Partner Auth Headers: Partner Auth headers (§2.2) + at least one user identity header (§2.3). Used for every user-scoped Partner → UR endpoint.

  • Partner-Scoped Partner Auth Headers: Partner Auth headers (§2.2) only, no user identity. Used for partner-level endpoints not tied to a single user.

  • Public Metadata Headers: No auth required. Used for fully public reference endpoints (banks, payment purposes, etc.). UR reserves the right to change this access policy.

2.5 Standard response envelope

Core Banking APIs use the standard UR OpenAPI response envelope:

  • code = 0 → success; non-zero → business error (see endpoint-specific tables and the global error code reference).

  • message → human-readable explanation, may be empty on success.

  • data → endpoint-specific payload.

2.6 Idempotency

Endpoints that move funds (Off-ramp submission, FX, internal transfer, Payout, On-ramp, Onramp retry) accept a Partner-supplied idempotency key:

  • The field name is reqId.

  • Keep reqId stable across retries of the same logical operation. If a response is lost or times out, query transaction history by reqId before retrying. Do not generate a new reqId for the same logical operation.

  • Webhook delivery is at-least-once; use data.txHash or the transaction id as the idempotency key on the Partner side.

2.7 Preconditions for fund-moving APIs

Before calling Off-ramp, On-ramp, FX, internal transfer, Payout, or Card APIs, the Partner must ensure:

  1. The UR account status is Live.

  2. The mapped Partner user and UR account exist and are not frozen.

  3. Any integration-specific approval required by UR has been enabled for your production setup.


3. Onboarding

Use this section when your platform verifies the user through your own Sumsub tenant. During onboarding, your platform mints a single-use Sumsub share token (scoped to UR's clientId) and hands it to UR. UR imports the applicant via Sumsub Copy Applicant and runs data-level validation over the copied snapshot (it does not re-run Sumsub checks against a UR level), creates a URID, provisions the UR-managed wallet, renders and signs Form A, and activates the user's UR Account.

Two onboarding paths are supported:

  • Sumsub reuse (share token), this page. Your platform completes KYC in your own Sumsub tenant and shares the approved applicant with UR through Sumsub reuse. Use this when you already operate a Sumsub tenant.

  • Sumsub SDK in your app. Your backend requests a UR-issued Sumsub access token and the Sumsub SDK in your app runs KYC against UR's Sumsub tenant. See API reference: Managed Custody SDK KYC. Use this when you do not run a Sumsub tenant.

Both paths produce the same end state and use the same post-onboarding banking APIs documented in this page.

3.1 Prerequisites

Before you start onboarding users, make sure the following setup is complete:

  • Your Partner Auth signing key is registered with UR. See API signing key for how to create it in the API sandbox or register your own address.

  • Your platform and UR are configured as Donor / Recipient Partners in Sumsub.

  • Your KYC flow presented the required data-sharing declaration and the user agreed, before identity verification. This declaration is mandatory and is the user-facing basis for the Sumsub reuse on this page; see the required KYC disclosure.

  • Your backend can generate a single-use Sumsub share token scoped to UR's Sumsub clientId after the applicant is approved.

  • Your backend can hand the share token to UR via POST /api/fma/v1/kyc/reuse-share-token.

  • Your backend can persist externalUserId, sessionId, urId, and evmAddress for each user.

  • Your backend can receive onboarding webhooks from UR: fma.kyc.reuse_check.result (the async handoff verdict) and fma.account.result (final activation). After a user is live, fma.additional_kyc.required and fma.additional_kyc.completed cover ops-initiated retries.

Store the returned identifiers before you show the user that onboarding has started. Your externalUserId is your stable user ID. UR maps that value to the returned urId.

3.2 Identity headers during onboarding

Onboarding uses the same Partner Auth rules as the rest of this page. The identity header changes after UR creates the user's UR Account.

Phase
Endpoint
Required identity header
Notes

Pre-account

POST /api/fma/v1/create-account

X-External-User-Id

Do not send X-Ur-Id before UR returns the user's urId.

Post-account

/api/fma/v1/kyc/* and /api/fma/v1/account-status

X-Ur-Id

Use the urId returned by /create-account. Sending X-External-User-Id as well is allowed.

For GET /api/fma/v1/kyc/form-a-info and GET /api/fma/v1/account-status, sign the raw query string exactly as sent. For POST endpoints, sign the raw request body exactly as sent. Append the same identity suffix and deadline described in §2.2.

3.3 Onboarding flow

The onboarding flow starts after the user has completed KYC in your Sumsub workflow and your backend has minted a single-use share token for UR. You create the UR Account, then hand the share token to UR: UR imports the applicant via Copy Applicant and returns a synchronous verdict (passed / incomplete / terminal). /kyc/check is a read of that verdict; it does not itself trigger the import.

1

Create the UR Account

Call POST /api/fma/v1/create-account with your X-External-User-Id and the user's email. For share-token (push-mode) partners, do not send applicantId; it is rejected. UR creates or reuses the user's URID, provisions the UR-managed wallet, and returns sessionId, urId, and evmAddress.

2

Hand off the share token

Call POST /api/fma/v1/kyc/reuse-share-token with the single-use shareToken. UR imports the applicant via Copy Applicant and returns a synchronous verdict: passed, incomplete (with missingFields to remediate and hand off again), or terminal (non-remediable eligibility rejection).

3

Read the verdict (optional)

POST /api/fma/v1/kyc/check with the returned sessionId re-serves the last handoff verdict as a pure read (complete=true when the session can proceed). Use it if you prefer polling to consuming the fma.kyc.reuse_check.result webhook; it does not itself trigger the import.

4

Show Form A

Call GET /api/fma/v1/kyc/form-a-info?sessionId=.... Display data.text to the user exactly as returned. Store data.textHash for the signing call.

5

Sign Form A

After the user consents, call POST /api/fma/v1/kyc/sign-form with sessionId and textHash. UR signs Form A with the user's UR-managed custodial wallet.

6

Submit activation

Call POST /api/fma/v1/kyc/submit. UR starts the asynchronous bank account activation process. Treat fma.account.result or GET /api/fma/v1/account-status as the source of truth for the final Live state.

3.4 Create account

Create or reuse the user's UR Account and onboarding session.

Item
Value

Method

POST

Path

/api/fma/v1/create-account

Headers

Partner Auth headers + X-External-User-Id

Request body:

Request fields:

Field
Required
Description

email

Yes

The user's email address.

Response example:

Rules:

  • Repeated calls with the same X-External-User-Id return the existing onboarding session with idempotentReplay=true.

  • Persist sessionId, urId, and evmAddress before continuing.

  • Use X-Ur-Id on subsequent onboarding calls.

3.5 Check KYC completeness

Re-read the last handoff verdict (from /kyc/reuse-share-token) and check whether the session can proceed to Form A. This is a pure read: it does not import the applicant or re-run any check; the import happens in /kyc/reuse-share-token.

Item
Value

Method

POST

Path

/api/fma/v1/kyc/check

Headers

User-Scoped Partner Auth Headers

Request body:

Complete response:

Incomplete response:

The handoff verdict is available synchronously from /kyc/reuse-share-token and via the fma.kyc.reuse_check.result webhook, so you normally do not need to poll. If you do poll, stop after /kyc/submit succeeds or when the session reaches a terminal state.

Error handling:

Code
Meaning
Partner action

20004

The KYC snapshot is incomplete.

Read missingFields, have the user complete the missing data, then mint a fresh share token and hand off again.

40001

Sumsub is temporarily unavailable.

Back off and retry.

3.6 Get Form A

Fetch the exact Form A text that the user must review.

Item
Value

Method

GET

Path

/api/fma/v1/kyc/form-a-info?sessionId={sessionId}

Headers

User-Scoped Partner Auth Headers

Request body: none.

Response example:

Rules:

  • Display data.text to the user exactly as returned.

  • Pass data.textHash unchanged to /kyc/sign-form.

  • textHash is the 0x-prefixed keccak256 hash of the UTF-8 bytes of data.text.

  • If this endpoint returns 20007, the KYC snapshot is not ready. Return to /kyc/check.

3.7 Sign Form A

Ask UR to sign the rendered Form A text with the user's UR-managed custodial wallet.

Item
Value

Method

POST

Path

/api/fma/v1/kyc/sign-form

Headers

User-Scoped Partner Auth Headers

Request body:

Response example:

Rules:

  • The request does not include a user signature. UR produces the Form A signature using the user's UR-managed wallet.

  • textHash must match the latest Form A text rendered by UR.

  • If user data changes before /kyc/submit, call /kyc/form-a-info again and re-sign the latest textHash.

3.8 Submit onboarding

Submit the completed onboarding session for asynchronous account activation.

Item
Value

Method

POST

Path

/api/fma/v1/kyc/submit

Headers

User-Scoped Partner Auth Headers

Request body:

Response example:

The registrationId can be empty in the synchronous response. UR fills downstream activation details asynchronously. Use fma.account.result or /api/fma/v1/account-status to confirm the final state.

Preconditions:

  • /kyc/check has returned complete=true.

  • Form A has been signed through /kyc/sign-form.

  • The session is in Register state.

3.9 Get account status

Use account status as a polling fallback after /kyc/submit, or as an explicit confirmation before enabling fund-moving features.

Item
Value

Method

GET

Path

/api/fma/v1/account-status

Headers

User-Scoped Partner Auth Headers

Response example:

Poll this endpoint at a 1-minute cadence after /kyc/submit if you do not receive fma.account.result. Stop polling when data.statusStr is Live, Blocked, or Closed.

3.10 Onboarding states

Use the following state values for your local onboarding cache:

State
Meaning
Partner action

PartnerDataIngestion

UR created the onboarding session and is waiting for a complete KYC snapshot.

Call /kyc/check after the Sumsub applicant is complete.

IdentityVerification

UR is still validating identity evidence.

Continue polling /kyc/check, or ask the user to complete missing KYC steps.

SignFormA

The KYC snapshot is ready for Form A.

Call /kyc/form-a-info, display the text, then call /kyc/sign-form.

Register

Form A is signed and the session is ready to submit.

Call /kyc/submit.

Submitting

UR is activating the account with the downstream banking partner.

Wait for fma.account.result or poll /api/fma/v1/account-status.

Completed

The UR Account is activated.

Enable fund-moving features only after account-status returns Live.

Failed

UR rejected or expired the onboarding session.

Show the failure state. UR operations may later issue a retry, which arrives as fma.additional_kyc.required; see Retry KYC.

A retry session reports states from this same set, but only the subset its reduced step list covers, so it can start at SignFormA with no earlier state ever observed. Treat an unexpected state as "a step remains" rather than an error.

3.11 KYC webhooks

UR sends these webhooks to the URL registered for your integration. The first two cover onboarding; the retry events fire later, once the user is already live. Verify every webhook with the same EIP-191 recovery rules in §13.2.

Activation or rejection:

When data.status is activated, confirm Live with /api/fma/v1/account-status before enabling fund-moving features. When data.status is rejected, the payload includes rejectCode and rejectReason.

Retry required: UR ops can ask an already-onboarded user to redo part or all of their KYC. That arrives as fma.additional_kyc.required:

The new session is not in the payload. Call POST /api/fma/v1/kyc/session/create to claim it, then run only the steps taskType names. Do not call /create-account for a retry. A second event, fma.additional_kyc.completed, fires when the retry session finishes. See Retry KYC for the full walkthrough and Webhooks for both payload contracts.


4. Core banking integration principles

The following rules apply across all fund-moving endpoints in this reference.

  1. User balances are queried with GET /api/fma/v1/balance (§6.1).

  2. Off-ramp converts crypto (held on a supported source chain) into the user's tokenized fiat balance. For the current set of supported source chains, source tokens, and target fiat currencies, see Supported Chains & Tokens.

  3. On-ramp converts the user's tokenized fiat balance into crypto on a supported destination chain. New On-ramp submissions must be blocked while a pending retry exists. For the current set of supported destination chains and tokens, see cryptos with aggregatorSupported value in the response of Supported Chains & Tokens.

  4. Card authorization behavior depends on the Partner's Card Mode. See API Reference: Card Mode: Crypto Backed for the Crypto Backed integration surface; Card Mode: Fiat Only has no Partner-side authorization surface.

  5. All async settlement results are delivered via the transaction webhook (§13). The webhook is the authoritative source of truth; API responses to fund-moving calls return only a txHash (the operation has been submitted on-chain, not yet settled).


5. Profile

5.1 Get BR profile

Fetch the user's banking profile, including IBAN, fiat limits, contacts, deposit bank details, and card eligibility.

Item
Value

Method

GET

Path

/api/fma/v1/br

Headers

User-Scoped Partner Auth Headers

Request body: none. Query parameters: none.

Response example:

Notes:

  • limits are denominated in CHF and use a rolling 30-day window.

  • FX, card spending, on-ramp, and payout share the same fiat limit bucket; each of these is checked against limits.available and fails if it exceeds it.

  • A single outgoing transaction must not exceed limits.available.

  • iban is the user's default personal Swiss IBAN. It receives EUR and CHF deposits; it does not receive USD.

  • depositBank is keyed by currency. For each inbound transfer, read the entry whose key matches the deposit currency, and show that account to the user.

  • A USD IBAN is separate from the EUR/CHF IBAN. UR provisions the EUR/CHF IBAN automatically when the user reaches Live, with no prerequisite pay-in. UR provisions the USD IBAN only on request: call POST /v1/apply-usd-payin when the user wants to receive USD. The call is synchronous, and UR creates the USD IBAN immediately if the user is Live. The USD deposit account then appears under the USD key of depositBank. Match the account to the currency the user will send; never reuse the EUR/CHF IBAN for a USD transfer. USD deposits from a non-same-name sender are held for review; see Deposits.


6. Balance

6.1 Get user balance

Fetch the user's fiat balances held inside the user's UR account. In Managed Custody Mode the UR-managed account holds tokenized fiat only.

Item
Value

Method

GET

Path

/api/fma/v1/balance

Headers

User-Scoped Partner Auth Headers

Response example:

  • fiatItems enumerates the user's tokenized fiat balances on Mantle (EUR24, CHF24, USD24, etc.).

  • The endpoint returns fiat only. The UR-managed account never custodies crypto, so there is no crypto balance to report; the user's crypto sits in an external (non-UR) wallet.


7. Off-ramp

Off-ramp converts crypto into fiat in the user's UR account.

Currently supported:

Amount limits. Read the minimum and maximum Off-ramp amount for each source token from the chain config fields minTopUpAmount and maxTopUpAmount; see Supported Chains & Tokens. Read them at request time and do not hardcode them, because UR converts a USD anchor at the live rate for each token. A USDC Off-ramp must be at least 5 USDC. Each Off-ramp also counts against the user's rolling 30-day fiat limit.

Flow

The UR API step is quote retrieval. After the Partner receives the quote, the holder of the external crypto wallet (the user, or the Partner when the wallet is partner-side) signs and submits the Off-ramp contract call. See §7.2.

7.1 Get off-ramp quote

Item
Value

Method

POST

Path

/api/fma/v1/quote/deposit

Headers

User-Scoped Partner Auth Headers

Request body:

  • chainId & fromToken: see Supported Chains & Tokens

  • toCurrency: target fiat currency symbol.

  • amount: human-readable decimal string; UR converts it to token smallest units using the source token decimals.

Response example:

Tempo chain only: amountReceived is the actual USDC amount received on Arbitrum (in smallest unit). This field is only returned for Tempo chain deposits.

Contract execution notes:

  • Pass best.to, best.swapCalldata, and best.minUsdcAmount to the UR Off-ramp contract exactly as returned.

  • The signing external Crypto Wallet (see §7.2) must have approved the Off-ramp contract to spend fromToken for at least amount.

  • The transaction must be submitted before best.deadline; otherwise it can revert.

  • networkFee and crossChainFee are denominated in the source chain's native token and paid by the user from the source chain wallet (in addition to amount).

  • Final settlement is reported asynchronously through the transaction webhook with data.type = "CRYPTO_DEPOSIT".

7.2 Initiate off-ramp

In Managed Custody Mode, the Fiat Wallet is always UR-managed and the Crypto Wallet is always an external (non-UR) wallet: the partner's account or the user's own. For compliance, the UR-managed account never holds crypto: the source crypto is paid in from that external wallet, and UR credits the resulting fiat to the user's UR Account. The Off-ramp contract uses the _targetAccount parameter to identify which UR Account receives the resulting fiat.

Contract: depositTokenViaAggregatorToAccount on the Off-ramp contract. Contract addresses per chain: see Deposit (off-ramp).

Contract Parameters:

Parameter
Type
Required
Description
Example

_inputToken

address

Yes

Source token address (Use 0x00...00 for native tokens).

"0xA0b8...B48" (USDC)

_outputToken

address

Yes

Target fiat token address (Fiat type after deposit).

"0x1234...5678"

_amount

uint256

Yes

Deposit amount (in smallest unit, e.g., Wei).

For USDC: "10000000" = 10.000000

_aggregator

address

Yes

Exchange contract address; use best.to from §7.1.

_swapCalldata

bytes

Yes

Use best.swapCalldata from §7.1.

"0x" for USDC direct deposit.

_minUsdcAmount

uint256

Yes

Use best.minUsdcAmount from §7.1.

_feeAmountViaUsdc

uint256

Yes

Put "0" when user calls the contract directly.

_targetAccount

address

Yes

Use data.targetAccount from §7.1. This is the user's UR Account address that receives the resulting fiat.

For Tempo chain, use depositWithFeeTo on the Tempo Off-ramp contract. Contract addresses per chain: see Deposit (off-ramp).

Contract Parameters:

Parameter
Type
Required
Description

recipient

address

Yes

The user's UR Account address that receives the resulting fiat. Used when the signing wallet is not the user's UR Account itself.

inputToken

address

Yes

Source stablecoin address.

inputAmount

uint256

Yes

Deposit amount (in smallest unit, e.g., Wei).

outputToken

address

Yes

Target fiat token address.

minAmountReceived

uint256

Yes

Minimum amount to receive on Arbitrum. Calculate from the quote API: amountReceived adjusted by slippageBps.

refundAddress

address

Yes

Address to receive refund of excess cross-chain fee. Usually the user's wallet address.

maxFeeUsdcAmount

uint256

Yes

First-hop (Tempo → Arbitrum) cross-chain fee budget (denominated in USDC, in smallest unit). Calculate from the quote API: crossChainFee adjusted by slippageBps.

feeAmountViaUsdc

uint256

Yes

Put "0" when user calls the contract directly.

If you require Partner-side API submission for Off-ramp instead of on-chain user signing, please contact the UR team.


8. FX and internal transfers

This section covers two on-chain fiat operations. FX converts one tokenized fiat balance into another inside the user's UR Account. Internal transfers send one tokenized fiat balance from the user's UR Account to a different UR Account.

Amount limits. Read the minimum and maximum FX amount for each token from the chain config fields minFxAmount and maxFxAmount; see Supported Chains & Tokens. Read them at request time and do not hardcode them. UR converts the minimum from a USD anchor at the live rate. The maximum reflects the user's remaining rolling 30-day allowance, so it changes over time.

8.1 FX quote

Item
Value

Method

POST

Path

/api/fma/v1/quote/fx

Headers

User-Scoped Partner Auth Headers

Request body:

Response example:

8.2 Execute FX

Item
Value

Method

POST

Path

/api/fma/v1/fx-exchange

Headers

User-Scoped Partner Auth Headers

Request body:

amountOutMinimum is optional. If omitted, UR applies a default 0.5% slippage buffer based on the submitted amount.

Response example:

Final result is reported through the transaction webhook with data.type = "FRX". See §12.1 for the full set of status values.

8.3 Initiate an internal transfer

Send tokenized fiat from the user's UR Account to another UR Account. The User-Scoped Partner Auth Headers identify the sender. Send the recipient's URID in toAccountId; do not put the sender's URID in the request body.

Item
Value

Method

POST

Path

/api/fma/v1/internal-transfer

Headers

User-Scoped Partner Auth Headers

Request body:

Request fields:

Field
Type
Required
Description

reqId

string

Yes

Partner-supplied idempotency key. Keep this value stable for the same logical transfer.

amount

string

Yes

Positive human-readable decimal amount. Do not send the amount in token smallest units.

currency

string

Yes

Tokenized fiat currency configured for the endpoint: EUR, CHF, or USD.

toAccountId

string

Yes

Recipient URID as a non-negative decimal integer string. It must differ from the sender's URID.

Response example:

The response confirms submission, not settlement. Confirm the final result through the transaction webhook or query POST /api/fma/v1/transactions by txHash or reqId. The transaction type is INTERNAL_TOKEN_TRANSFER; a successful transfer reaches CONFIRMED.

UR returns a non-zero business code synchronously when the user identity, recipient URID, currency, amount, or reqId is invalid. An insufficient balance or an on-chain execution failure can surface asynchronously as FAILED or REJECTED. Do not credit the recipient based only on the submission response.


9. Bank payout

Bank Payout sends tokenized fiat from the user's UR account to an external bank account via SEPA / SWIFT.

Flow

9.1 Get payout fees

Item
Value

Method

GET

Path

/api/v1/banks/payout/fees

Headers

Public Metadata Headers

Public metadata, not scoped to a single user. Returns the standard envelope:

9.2 Choose recipient

The Partner can use either a recent contact returned by GET /api/fma/v1/br, or create / verify a new contact.

Recent contact path:

  • Read data.contacts[currency] from the BR Profile response (§5.1).

  • Use contact.id as contactId.

  • If the user provides a new reference, call POST /api/fma/v1/verify-reference (§9.3) to get a fresh refId + purposeId.

New contact path:

  • Identify the recipient bank by IBAN (GET /api/v1/banks/iban/{iban}) or by selecting from the non-IBAN bank list (GET /api/v1/banks).

  • Collect required creditor name, address, country, city, payment purpose, and reference.

  • Call POST /api/fma/v1/verify-contact (§9.4).

Public metadata APIs (/api/v1/banks, /api/v1/banks/iban/{iban}, /api/v1/country-cities, /api/v1/payment-purposes) do not require user identity headers unless UR changes their access policy.

9.3 Verify reference

Item
Value

Method

POST

Path

/api/fma/v1/verify-reference

Headers

User-Scoped Partner Auth Headers

Request body:

Response example:

9.4 Verify contact

Item
Value

Method

POST

Path

/api/fma/v1/verify-contact

Headers

User-Scoped Partner Auth Headers

Request body:

Response example:

creditorInfo.name, creditorInfo.street, creditorInfo.city, and creditorInfo.country must use Latin characters.

9.5 Submit payout

Item
Value

Method

POST

Path

/api/fma/v1/submit-payout

Headers

User-Scoped Partner Auth Headers

Request body:

Response example:

Constraints:

  • metadata name, address, and reference values must use Latin characters.

  • purposeId and refId must be provided together, or both omitted.

  • Minimum amount is currency-specific and comes from minimalPayoutAmount (§9.1).

  • Network fees and payout fees are deducted from amount; they are not charged separately.

  • Payout is subject to the user's rolling 30-day CHF-denominated fiat limits.

  • Final result is reported through the transaction webhook with data.type = "FIAT_WITHDRAW".


10. On-ramp

On-ramp converts the user's tokenized fiat balance into crypto, delivered to an external wallet on a target chain. The UR-managed account holds fiat only, so On-ramp crypto never lands in the UR account.

Currently supported:

  • Destination chains and tokens: see cryptos with aggregatorSupported value in the response of Supported Chains & Tokens.

  • Source fiat currencies: USD, EUR, CHF, SGD, JPY, HKD

Flow

10.1 On-ramp login initialization

When the user enters the On-ramp flow, the Partner should run these checks in order:

  1. GET /api/fma/v1/onramp-limit

  2. GET /api/fma/v1/onramp/pending-retry, only if the limit response allows the flow.

If a pending retry exists, the Partner must force the user to Retry or Cancel before starting a new On-ramp.

10.2 Get on-ramp limit

Item
Value

Method

GET

Path

/api/fma/v1/onramp-limit

Headers

User-Scoped Partner Auth Headers

Response example:

Block the flow if regionLocked, usdcDepegged, or livenessLocked is true, or if the requested amount falls outside the minAmounts[currency] to maxAmounts[currency] range.

maxAmounts and minAmounts are keyed by fiat currency. Read both at request time; do not hardcode them. UR derives them from a single USD anchor (about 5 USD minimum; the maximum depends on the user's liveness state) and converts each to fiat at the live rate, so the values differ across currencies.

10.3 Check pending retry

Item
Value

Method

GET

Path

/api/fma/v1/onramp/pending-retry

Headers

User-Scoped Partner Auth Headers

Pending retry represents an On-ramp whose bridge succeeded but whose swap leg failed; the user must resolve it before starting a new On-ramp.

No pending item:

When there is no pending retry, data.originalTxHash is empty.

Pending item:

10.4 Get on-ramp quote

Item
Value

Method

POST

Path

/api/fma/v1/quote/onramp

Headers

User-Scoped Partner Auth Headers

Request body for the main On-ramp flow:

Notes:

  • scene is onramp for the main flow and swap_retry for retry (§10.8).

  • srcChainId is the chain where the user's tokenized fiat is held (UR's home chain).

  • fromCurrency is required for scene = "onramp". fromToken is used only for scene = "swap_retry".

  • dstChainId and toToken must match a crypto type with aggregatorSupported value in the response of Supported Chains & Tokens.

  • networkFee returned in the quote response is the destination-chain gas + cross-chain fee, deducted from the user's input fiat.

  • If the response has needLiveness = true, the Partner must complete liveness before submitting On-ramp.

10.5 Liveness token

Item
Value

Method

GET

Path

/api/fma/v1/onramp-liveness-token

Headers

User-Scoped Partner Auth Headers

Only call this endpoint when a quote returns needLiveness = true.

Response example:

10.6 Liveness result

Item
Value

Method

GET

Path

/api/fma/v1/onramp-liveness-result

Headers

User-Scoped Partner Auth Headers

Response example:

After liveness_result = "pass", the Partner should request a new quote and submit using the new quoteId.

10.7 Submit on-ramp

Item
Value

Method

POST

Path

/api/fma/v1/onramp

Headers

User-Scoped Partner Auth Headers

Request body:

Response example:

Submit constraints:

  • amountIn is a human-readable decimal string and must match the amount used for the cached quote.

  • quoteId must match UR's cached quote (and not be expired).

  • A quote requiring liveness cannot be submitted until liveness passes.

  • New On-ramp must be blocked while a pending retry exists (§10.3).

  • withdrawAddress is the external wallet that receives the crypto. It is required for every On-ramp, same-chain or cross-chain, because the UR-managed account holds fiat only and never receives crypto.

  • UR delivers the destination-chain crypto to withdrawAddress; UR never sends On-ramp crypto to the user's UR account.

  • Final result is reported through the transaction webhook.

10.8 Retry on-ramp swap

On-ramp is a two-leg flow (bridge + swap). When the bridge succeeds but the swap fails, the user's funds are stuck as the bridge intermediate token on the destination chain. Retry redoes the swap leg only, so the payload drops fiat/source-chain inputs and instead carries originalTxHash, the post-bridge intermediate-token amount, and a fresh swap quote.

Item
Value

Method

POST

Path

/api/fma/v1/onramp-swap

Headers

User-Scoped Partner Auth Headers

Only call this endpoint when GET /api/fma/v1/onramp/pending-retry returns a pending item.

Retry flow:

  1. Read pending retry from GET /api/fma/v1/onramp/pending-retry.

  2. Request a fresh retry quote:

    • scene = "swap_retry"

    • srcChainId = pendingRetry.chainId

    • dstChainId = pendingRetry.chainId

    • fromToken = pendingRetry.fromToken

    • toToken = pendingRetry.toToken

    • amount = pendingRetry.amount (human-readable)

  3. Submit /api/fma/v1/onramp-swap:

    • usdcAmount = pendingRetry.amountRaw (USDC minimal unit)

    • tokenOut = pendingRetry.toToken

    • minAmountOut = quote.best.minAmountOut

    • aggregator = quote.best.to (not quote.best.aggregator)

    • swapCalldata = quote.best.swapCalldata

Request body:

10.9 Cancel on-ramp retry

Item
Value

Method

POST

Path

/api/fma/v1/onramp/retry/cancel

Headers

User-Scoped Partner Auth Headers

Request body:

A successful response clears the pending retry record and allows the Partner to re-enable the normal On-ramp entry point.


11. Card

The user's debit card is issued and processed by UR through Mastercard. This section covers only the card-management endpoints common to all Card Modes: card creation, card info retrieval, default-currency selection, and post-settlement history.

Card authorization, prefund, and card-related webhooks are Card-Mode-specific. Card Mode: Fiat Only has no Partner-side authorization surface; UR handles authorization on-chain against the user's tokenized fiat balance. Card Mode: Crypto Backed has its own integration surface (synchronous authorization callback, Prefund Account, Prefund Balance Alert webhook) documented in API Reference: Card Mode: Crypto Backed.

11.1 Create card

Create a virtual card for an eligible Live user.

Item
Value

Method

POST

Path

/api/fma/v1/open-card

Headers

User-Scoped Partner Auth Headers

Request body: {}

Preconditions:

  • GET /api/fma/v1/br returns isCardEligible = true.

  • The user has no existing card if UR only allows one card per user.

  • The user balance satisfies cardActivation.amount and cardActivation.currency.

Response example:

11.2 Get card info

Fetch card metadata and a short-lived cardToken for secure card display.

Item
Value

Method

GET

Path

/api/fma/v1/card

Headers

User-Scoped Partner Auth Headers

Response example:

Display card details:

The card info API does not expose real PAN, CVV, or expiry in JSON. Use cardToken only to render those sensitive fields through UR's card display script. The cardToken is short-lived and expires after 5 minutes. When it expires, call GET /api/fma/v1/card again to get a fresh token.

Card identifiers:

Field
Use

cardToken

Short-lived token for card detail display only. Do not store or log it.

externalId

Stable card management ID. Use it for APIs such as Set Default Card Currency.

activeTokens[].id

Device wallet token ID, such as Apple Pay. Do not use it for card detail display or currency settings.

Load the script from UR:

Add DOM placeholders where the script should render sensitive fields:

Initialize the display after the user chooses to reveal card details:

Common mistakes:

  • Do not store or log cardToken.

  • Do not use activeTokens[].id unless calling a device-token management API.

  • Load the script only on the card details view or secure webview, not globally across your app.

  • Render card details only after explicit user action, such as selecting "Show card details".

11.3 Set default card currency

Set the user's default card transaction currency.

Item
Value

Method

POST

Path

/api/fma/v1/card-currency

Headers

User-Scoped Partner Auth Headers

Request body:

Request fields:

Field
Type
Required
Description

currency

string

Yes

Target default transaction currency, such as USD, EUR, or CHF.

Response example:

Notes:

  • The server automatically resolves the card's externalId from the authenticated user; the Partner does not need to pass it.

  • The next GET /api/fma/v1/card response should show the updated currency.

  • This setting affects UR's default refund currency display and debit preference.

  • Per-transaction overrides at swipe time are governed by the Card Mode; see API Reference: Card Mode: Crypto Backed.

11.4 Update card status

Block or unblock the user's card.

Item
Value

Method

POST

Path

/api/fma/v1/update-card-status

Headers

User-Scoped Partner Auth Headers

Request body:

Request fields:

Field
Type
Required
Description

statusChange

string

Yes

The status transition to apply. Accepted values: block, unblock.

Response example:

Notes:

  • The server automatically resolves the card's externalId from the authenticated user.

  • A blocked card will decline all authorization attempts until unblocked.

11.5 Card authorization

Card authorization behavior is Card-Mode-specific and is not covered in this document. See API Reference: Card Mode: Crypto Backed for the Crypto Backed integration surface. Card Mode: Fiat Only has no Partner-side authorization surface.

11.6 Card settlement notes

Card settlement result records are exposed through transaction history (§12) with data.type = "CRD". Additional card adjustment event contracts (chargebacks, fee adjustments) must be agreed separately with UR.


12. Transactions

Use transaction history for reconciliation. The same endpoint also supports exact lookup by transaction hash or transaction ID.

12.1 Fetch transaction history

Item
Value

Method

POST

Path

/api/fma/v1/transactions

Headers

User-Scoped Partner Auth Headers

Simple first-page request:

Filtered request:

Response example:

Pagination is driven by the response flags and cursors. Only send cursor fields returned by the API, and only when the corresponding flag is true.

To fetch the next page, use nextCursor when hasNextPage is true:

To fetch the previous page, use prevCursor when hasPrevPage is true:

If hasPrevPage is false, do not use prevCursor; a zero cursor means there is no previous page.

Request fields:

Field
Type
Description

pageSize

integer

Page size. Defaults to 50; maximum is 100.

type

string

Single transaction type filter. Do not send together with txTypes.

txTypes

string[]

Multiple transaction type filter. Do not send together with type.

currencies

string[]

Currency filter. Values are normalized to lowercase by UR.

direction

string

Direction filter: IN, OUT, or ALL.

status

string

Transaction status filter.

chainId

string

Chain ID filter, for example eip155:5000.

tokenSymbol

string

Token symbol filter, for example USDC.

minAmount

string

Minimum amount filter.

maxAmount

string

Maximum amount filter.

fromTimestamp

integer

Start timestamp filter.

toTimestamp

integer

End timestamp filter.

cursorTimestamp

integer

Next-page cursor timestamp from data.nextCursor.timestamp.

cursorId

integer

Next-page cursor ID from data.nextCursor.id.

prevCursorTimestamp

integer

Previous-page cursor timestamp from data.prevCursor.timestamp.

prevCursorId

integer

Previous-page cursor ID from data.prevCursor.id.

id

integer

Exact lookup by UR internal transaction ID. Mutually exclusive with txHash and reqId.

txHash

string

Exact lookup by transaction hash. Mutually exclusive with id and reqId.

reqId

string

Exact lookup by the idempotency key (reqId) used when the transaction was created. Mutually exclusive with id and txHash.

Partner-relevant transaction types:

type

Meaning

CRYPTO_DEPOSIT

Crypto deposit / tokenized fiat top-up.

INTERNAL_TOKEN_TRANSFER

Internal token transfer between UR accounts.

UNKNOWN

Unknown or uncategorized transaction type.

FX_EXCHANGE

FX exchange.

MARQETA_AUTHORIZE

Card authorization / card payment.

FIAT_WITHDRAW

Fiat withdrawal / bank payout.

FIAT_DEPOSIT

Fiat deposit / bank pay-in.

ONRAMP

On-ramp.

Transaction status values:

status

Description

UNKNOWN

Unknown; default value. Should not appear in normal flows.

INIT

Transaction created internally but not yet broadcast to the blockchain node.

PENDING

Transaction is in the mempool (transaction pool), awaiting confirmation.

CONFIRMED

Transaction has been confirmed and settled on-chain.

FAILED

Transaction execution failed on-chain.

PENDING_DROP

Transaction is scheduled to be dropped, for example replaced or cancelled.

DROPPED

Transaction was removed from the mempool without being confirmed.

REJECTED

Transaction was rejected by UR's validation or compliance checks before or during execution.

12.2 Fetch transaction details

Use this endpoint to fetch a single transaction. Send exactly one lookup key: txHash for an on-chain transaction hash, id for UR's internal transaction ID, or reqId for the idempotency key used when the transaction was created.

Item
Value

Method

POST

Path

/api/fma/v1/transactions

Headers

User-Scoped Partner Auth Headers

Lookup by transaction hash:

Lookup by UR internal transaction ID:

Lookup by idempotency key:

Response:

  • The response uses the same data.items[] transaction structure as §12.1.

  • Send exactly one of id, txHash, or reqId; they are mutually exclusive.

  • If no transaction matches the lookup key, data.items[] is empty.

12.3 detailsJson reference by transaction type

The detailsJson field is a stringified JSON blob whose structure depends on the transaction type. Parse the string before reading nested fields.

Field names follow protobuf camelCase conventions. All fields are optional (omitempty) unless stated otherwise.


CRYPTO_DEPOSIT (crypto top-up)

Crypto deposit to fund a fiat account.

Field
Type
Description

fromChainId

string

Source chain ID (e.g., eip155:1 for Ethereum mainnet)

fromTxHash

string

Transaction hash on the source chain

fromTxLogIndex

string

Log index on the source chain

inputToken

string

Input token symbol (e.g., USDC, USDT)

inputAmount

string

Input token amount

inputTokenAddress

string

Input token contract address

bankTxHash

string

Settlement transaction hash on Mantle chain

externalSender

string

External sender address

externalTarget

string

External target address

externalTargetTokenId

string

Target token ID on UR

partnerId

string

Partner identifier

partnerRefId

string

Partner reference ID


FX_EXCHANGE (forex exchange)

Fiat-to-fiat currency exchange.

Field
Type
Description

inputAmount

string

Amount of input (sold) currency

outputAmount

string

Amount of output (bought) currency

inputCurrency

string

Input currency code (e.g., USD)

outputCurrency

string

Output currency code (e.g., EUR)

partnerId

string

Partner identifier

partnerRefId

string

Partner reference ID


INTERNAL_TOKEN_TRANSFER (P2P transfer)

Peer-to-peer transfer between UR accounts.

Field
Type
Description

fromAddress

string

Sender wallet address

toAddress

string

Recipient wallet address

fromURId

string

Sender UR ID

toURId

string

Recipient UR ID

fromNickName

string

Sender display name

toNickName

string

Recipient display name

Special fromURId values: 9102 / 9101 / 9103 indicate a bank transfer refund. 9110 / 9113 indicate a card spending refund. 982 indicates UR Rewards.


MARQETA_AUTHORIZE (card payment)

Card authorization / card spending.

Field
Type
Description

authorizationId

string

Card authorization identifier

originAuthorizationId

string

Original authorization ID (for refunds/adjustments)

merchantId

string

Merchant identifier

merchant

string

Merchant name

mcc

uint64

Merchant Category Code

city

string

Transaction city

country

string

Transaction country (ISO 3166 alpha-3)

cardId

string

Card identifier

cardCurrency

string

Cardholder's default currency

transactionCurrency

string

Currency required by the merchant

settlementCurrency

string

Settlement currency (default CHF)

transactionAmount

string

Amount in transaction currency

settlementAmount

string

Amount in settlement currency

originalPaidCurrency

string

Original paid currency

subEvent

string

Sub-event type (for crypto-backed card spending)

multiTokenList

array

Multi-token deductions (crypto-backed mode only)

multiTokenList[].token

string

Token symbol deducted (e.g., USDe)

multiTokenList[].value

string

Equivalent fiat value

multiTokenList[].amount

string

Token amount deducted

multiTokenList[].tokenAddress

string

Token contract address

reason

string

Failure/rejection reason (present only on failed transactions)


FIAT_DEPOSIT (cash deposit / bank pay-in)

Fiat deposit via bank transfer.

Field
Type
Description

accountHolder

string

Depositor account holder name

bankName

string

Bank name

ref

string

Bank reference number

account

string

Bank account number / IBAN

fee

string

Fee amount (if applicable)

contactId

string

Contact identifier


FIAT_WITHDRAW (cash payout / bank withdrawal)

Fiat withdrawal via bank transfer.

Field
Type
Description

txId

string

Internal transaction ID

bankRef

string

Bank reference number

contactId

string

Contact identifier

accountHolder

string

Account holder name

bankName

string

Bank name

account

string

Bank account number / IBAN

fee

string

Fee amount (if applicable)

purposeId

string

Purpose of payment ID

The details may also include a bankMetadata object with settlement tracking:

Field
Type
Description

bankMetadata.initiate.hash

string

Initiation transaction hash

bankMetadata.initiate.timestamp

int64

Initiation timestamp

bankMetadata.initiate.txHashUrl

string

Block explorer URL

bankMetadata.payout.hash

string

Payout transaction hash

bankMetadata.payout.timestamp

int64

Payout timestamp

bankMetadata.payout.txHashUrl

string

Block explorer URL

bankMetadata.return.hash

string

Return transaction hash (if returned)

bankMetadata.return.timestamp

int64

Return timestamp

bankMetadata.return.txHashUrl

string

Block explorer URL

bankMetadata.bic

string

BIC / SWIFT code

bankMetadata.UETR

string

Unique End-to-end Transaction Reference

bankMetadata.creditor.country

string

Creditor country

bankMetadata.debitor.country

string

Debitor country


ONRAMP (fiat to crypto)

Convert fiat token to crypto asset. May involve cross-chain bridging.

Field
Type
Description

inputChainId

string

Source chain ID (where fiat token lives)

inputToken

string

Input token address (e.g., USD24)

inputAmount

string

Fiat input amount

outputChainId

string

Destination chain ID

outputToken

string

Output token address (e.g., USDC, ETH)

expectOutputAmount

string

Expected output amount (quote)

actualOutputAmount

string

Actual output amount (after execution)

expectUsdcAmount

string

Expected intermediate USDC amount

actualUsdcAmount

string

Actual intermediate USDC amount

guid

string

Cross-chain unique identifier (LayerZero GUID)

destination

object

Destination chain transaction info

destination.status

string

Destination chain transaction status

destination.txHash

string

Destination chain transaction hash

destination.txTime

int64

Destination chain transaction time

retryHistory

array

Array of prior destination attempts (same shape as destination)

retryStatus

string

can_retry / retrying / retry_success / retry_fail / no_retry / cancelled

balance

string

Balance after transaction

partnerId

string

Partner identifier

partnerRefId

string

Partner reference ID


Other types

Type

detailsJson

UNKNOWN

No structured details. May contain arbitrary JSON or be empty.


12.4 Identifying refund transactions

Every transaction record carries a refundType field that tells you whether the transaction is a refund and what kind:

refundType

Meaning

"" (empty string)

Not a refund (default).

BANK_REFUND

Bank transfer refund. UR returned funds from a system bank account (fromURId in 9101/9102/9103).

CARD_REFUND

Card spending refund. UR returned card spending funds to the user via a P2P transfer from account 9113.

CARD_REVERSAL

Card authorization reversal. The card network reversed a prior authorization, or UR reversed from the card-booked account (9110).

Use refundType as the primary discriminator.

Rule 1: bank transfer refund (BANK_REFUND)

A P2P transfer where fromURId is one of the UR system bank accounts:

fromURId

Account role

9101

Bank deposit (mint) account

9102

Bank payout account

9103

Sundry / failed transaction account

Rule 2: card spending refund (CARD_REFUND)

A P2P transfer where UR's card settlement system returned funds to the user:

fromURId

Account role

9113

Card spending refund account

Rule 3: card authorization reversal (CARD_REVERSAL)

Appears in two forms:

  • Card network reversal: type = MARQETA_AUTHORIZE, direction = IN. The originAuthorizationId field in detailsJson references the original card authorization.

  • Card-booked account reversal: type = INTERNAL_TOKEN_TRANSFER, direction = IN, fromURId = 9110 (on-chain CARD_BOOKED constant).

Summary

Refund scenario

refundType

type

fromURId

Bank transfer refund

BANK_REFUND

INTERNAL_TOKEN_TRANSFER

9101 / 9102 / 9103

Card spending refund

CARD_REFUND

INTERNAL_TOKEN_TRANSFER

9113

Card reversal (card network)

CARD_REVERSAL

MARQETA_AUTHORIZE

N/A

Card reversal (on-chain)

CARD_REVERSAL

INTERNAL_TOKEN_TRANSFER

9110


12.5 Partner-level transaction history

Query all transactions across all users under your partner account. This is a partner-scoped endpoint and does not require X-Ur-Id or X-External-User-Id headers.

Item
Value

Method

POST

Path

/api/fma/v1/partner-transactions

Headers

Partner Auth Headers (no user identity required)

Request body:

The request accepts the same fields as §12.1 Fetch transaction history: pageSize, type, txTypes, currencies, direction, status, chainId, tokenSymbol, minAmount, maxAmount, fromTimestamp, toTimestamp, cursorTimestamp, cursorId.

Single-record lookups (id, txHash, reqId) are not supported on this endpoint. Use the user-scoped §12.1 or §12.2 endpoints instead.

Response uses the same structure as §12.1 (data.items[], data.hasNextPage, data.nextCursor). Each item includes the urId field so you can identify which user the transaction belongs to.


12.6 Delegator vault transaction history

Query transactions for the Delegate Vault account associated with your partner. The vault account's urId is resolved from your partner configuration (delegateVaultId).

Item
Value

Method

POST

Path

/api/delegator/v1/transactions

Headers

Partner Auth Headers (user identity headers are ignored; vault identity is resolved from config)

Request body:

The request accepts the same fields as §12.1 Fetch transaction history, including pagination cursors, type filters, and single-record lookups (id, txHash, reqId).

Response uses the same structure as §12.1. All returned transactions belong to the vault account.


13. Webhooks

Webhooks are the asynchronous delivery channel for transaction settlement updates. The Partner should subscribe to the transaction event for Off-ramp, FX, On-ramp, Fiat Deposit, and Payout settlement.

Card-Mode-specific webhooks (post-swipe card transaction, prefund.balance.alert) are documented in API Reference: Card Mode: Crypto Backed.

13.1 Webhook envelope

13.2 Webhook signature verification

UR signs webhook requests with EIP-191.

Partner verification steps:

  1. Read the exact raw request body string (do not re-serialize).

  2. Recover the signer address using the body and X-Api-Signature.

  3. Accept the event only if the signer address matches the UR public key provided out of band.

See Signature and Verification for the canonical recovery algorithm.

13.3 Retry and idempotency

  • The Partner should return HTTP 200 within 10 seconds.

  • UR retries non-200 or timed-out webhook deliveries up to 3 times, with a 5-minute interval.

  • Use data.txHash or the transaction data.id as the idempotency key on the Partner side.

13.4 Transaction event

The transaction event uses the same transaction data structure as /api/fma/v1/transactions (§12.1). Route by data.type; see the type table in §12.1 for the canonical set.


14. Implementation checklist

Before going live:

  • Store both externalUserId (Partner side) and urId (UR side) after onboarding.

  • For user-scoped APIs, send at least one of X-External-User-Id or X-Ur-Id (sending both is allowed; UR resolves by X-Ur-Id first).

  • Sign GET requests with the raw query string, and sign non-GET requests with the raw body, then append urId:{X-Ur-Id}externalUserId:{X-External-User-Id} {X-Api-Deadline}.

  • Keep reqId stable across retries for POST /api/fma/v1/fx-exchange, POST /api/fma/v1/internal-transfer, POST /api/fma/v1/submit-payout, POST /api/fma/v1/onramp, and POST /api/fma/v1/onramp-swap.

  • Treat webhook delivery as at-least-once and implement idempotency keyed on data.txHash or data.id.

  • Reconcile transaction history through POST /api/fma/v1/transactions; use txHash, id, or reqId on that same endpoint for exact lookup.

  • Verify every webhook with EIP-191 recovery against UR's public key (§13.2).

  • If the Partner is enabling Card Mode: Crypto Backed, complete the additional checklist in API Reference: Card Mode: Crypto Backed.


15. Reference docs

Last updated