# Mercuryo Widget — Partner Integration Guide Mercuryo Widget is a ready-made On-Ramp and Off-Ramp solution. Your users can buy and sell 50+ cryptocurrencies across 40+ fiat currencies — paying with Visa, Mastercard, Apple Pay, Google Pay, or local alternative payment methods (APMs) — all within a Mercuryo-hosted interface that can be embedded directly into your product. KYC is handled by Mercuryo via SumSub, with a light-KYC option (no documents required) available for transactions up to €699. Themes and colors can be fully customized via Figma. ## What You Can Build * **On-Ramp (Buy)** — let users purchase crypto with a card, Apple Pay, Google Pay, or local APMs. * **Off-Ramp (Sell)** — let users sell crypto and receive fiat to their card (EUR and USD). * **Spend Card** — let users issue a virtual EUR Mastercard, fund it with crypto, and use it for payments worldwide. > **Note:** Off-Ramp and Spend Card are disabled by default. Contact your integration manager to enable them for your widget. ## Integration Methods | Method | Description | |---|---| | [Redirect](./integration.md#redirect-integration) | A link opens the widget in a new tab or as a full-page redirect. The simplest setup — no embedding required. | | [iFrame](./integration.md#iframe-integration) | The widget is embedded directly on your page inside an iFrame. Users complete the transaction without navigating away. | | [Mobile (iOS / Android)](./integration.md#mobile-integration) | The widget is loaded inside a WebView in your native iOS or Android app. | ## Key URLs | Service | Production | Sandbox | |---|---|---| | Dashboard | `https://dashboard.mercuryo.io` | `https://sandbox-dashboard.mrcr.io` | | Widget (Redirect / Custom Tab) | `https://exchange.mercuryo.io` | `https://sandbox-exchange.mrcr.io` | | Widget (for iFrame embedding) | `https://widget.mercuryo.io` | `https://sandbox-widget.mrcr.io` | | API | `https://api.mercuryo.io` | `https://sandbox-api.mrcr.io` | ## Quick Links * [Quick Start](./quick-start.md) — go live in 4 steps * [Sandbox & Testing](./sandbox.md) — test without real funds * [Widget Parameters](./widget-parameters.md) — full URL parameter reference * [Callbacks & Webhooks](./callbacks.md) — receive transaction status updates ## Support Contact your integration manager or email [sales@mercuryo.io](mailto:sales@mercuryo.io). * [Help Center for Users](https://help.mercuryo.io/hc/en-gb) * [Help Center for Merchants](https://b2bhelp.mercuryo.io/hc/en-gb) # Quick Start Get your first Mercuryo Widget integration live in 4 steps. ## Prerequisites Before starting, make sure you have: * Access to the [Mercuryo Dashboard](https://dashboard.mercuryo.io) — contact your integration manager if you don't have credentials yet. * A domain or app that will host the widget. * A chosen integration method — **Redirect**, **iFrame**, or **Mobile**. See [Integration Methods](./integration.md) for the differences. ## Step 1 — Create a Widget 1. Sign in to the [Mercuryo Dashboard](https://dashboard.mercuryo.io). 2. Go to **Widgets** → **Add Widget**. 3. In the **Domain URL** field, the value depends on your integration method: * **Redirect:** enter `https://exchange.mercuryo.io` * **iFrame / Mobile:** enter your own domain (e.g., `https://yourdomain.com`) Leave no trailing slash or extra characters — a domain mismatch causes the `widget.mercuryo.io refused to connect` error. 4. Note your **Widget ID** — you'll need it in every widget URL. 5. Copy your **Secret** from the bottom of the widget page — needed for [signature generation](./security.md). ## Step 2 — Configure Callback URL 1. In the widget settings, fill in the **Callback URL** field with your server endpoint. 2. Copy the **Sign Key** — you'll use it to [verify incoming callbacks](./callbacks.md#callback-signature-verification). See the [Callback dashboard](https://dashboard.mercuryo.io/widgets/callbacks) to browse, resend, and send test callbacks. ## Step 3 — Build the Widget URL A minimal On-Ramp widget URL looks like this: ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID&address=USER_WALLET_ADDRESS&merchant_transaction_id=YOUR_TX_ID&signature=v2:GENERATED_SIGNATURE ``` * `widget_id` — your Widget ID from the Dashboard. * `address` — the user's cryptocurrency wallet address for receiving purchased crypto. * `merchant_transaction_id` — your internal transaction ID for tracking. * `signature` — security signature. See [Signature Generation](./security.md). You can pre-fill many more parameters (currency, amount, payment method, etc.). See the full [Widget Parameters](./widget-parameters.md) reference. ## Step 4 — Handle Transaction Updates Choose how to track transaction status changes: | Method | Description | |---|---| | **Callbacks** | Mercuryo sends POST requests to your callback URL on every status change | | **API Polling** | Poll `GET /v1.6/sdk-partner/transactions` to check status | | **Dashboard** | View transactions manually in the Mercuryo Dashboard | See [Callbacks & Webhooks](./callbacks.md) for setup and payload details. --- > **Note:** Test with the [Sandbox environment](./sandbox.md) before going to production. You can try all flows without using real funds. # Sandbox & Testing Before going to production, test your integration in the Sandbox environment. All flows work identically to production, but no real funds are involved. ## Accessing Sandbox 1. Contact your integration manager to receive Sandbox credentials. 2. Request whitelisting of your IP addresses for Sandbox usage. 3. Use the Sandbox endpoints below instead of production ones. ## Sandbox URLs | Service | URL | |---|---| | Dashboard | `https://sandbox-dashboard.mrcr.io` | | Widget (Redirect / Custom Tab) | `https://sandbox-exchange.mrcr.io` | | Widget (for iFrame embedding) | `https://sandbox-widget.mrcr.io` | | API Host | `https://sandbox-api.mrcr.io` | ## Testnet Addresses Use these addresses when testing crypto transactions: | Network | Address | Supported Assets | |---|---|---| | [BTC Testnet](https://tbtc.bitaps.com/) | `msBE6aCaAesegu4VzbQW3L5xWBL8vi15Q7` | Bitcoin | | [ETH Sepolia](https://sepolia.etherscan.io/) | `0xbBC8f6B710359dbcdF02f9eb50Ade391890A6021` | Ethereum (ETH), USDT | ## Test Payment Cards ### Card 1 — Basic * **Number:** `4444 4444 4444 3333` * **Expiry:** any future date * **CVV:** `123` * **Cardholder name:** any name + surname ### Card 2 — Success / Fail * **Number:** `5555 4444 3333 1111` * **Expiry:** any future date * **CVV:** `123` for success, `555` for failure * **Cardholder name:** any name + surname ## KYC in Sandbox The Sandbox environment requires manually approving KYC reviews. If you don't want to wait for manual confirmation, use SumSub's test documents that have automatic processing: [SumSub Verification Document Templates](https://docs.sumsub.com/docs/verification-document-templates) --- ## Production URLs Once testing is complete, switch to production endpoints: | Service | URL | |---|---| | Dashboard | `https://dashboard.mercuryo.io` | | Widget | `https://exchange.mercuryo.io` | | Widget (iFrame embed) | `https://widget.mercuryo.io` | | API Host | `https://api.mercuryo.io` | ## Migration Checklist Before going live, make sure you've completed the following: * [ ] Updated all URLs to production endpoints * [ ] Obtained production credentials from the Dashboard * [ ] Updated API keys to production keys * [ ] Configured the callback URL in the production widget settings * [ ] Tested the integration with a small amount in production before full launch # Integration Methods The Mercuryo Widget supports three integration methods. Choose the one that fits your platform best. ## Redirect Integration The simplest method: redirect users to the widget URL. No embedding required. ```javascript const widgetUrl = `https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID`; window.location.href = widgetUrl; ``` **When to use:** Web applications where it's acceptable for users to leave the page. Lowest implementation effort. > In Sandbox, use `https://sandbox-exchange.mrcr.io` instead. --- ## iFrame Integration Embed the widget directly in your page using Mercuryo's JavaScript SDK. Users stay on your site. ### Setup 1. Add the widget container to your page ``: ```html
``` 2. Add the SDK script before ``: ```html ``` 3. Initialize the widget: ```javascript mercuryoWidget.run({ widgetId: 'YOUR_WIDGET_ID', host: document.getElementById('mercuryo-widget'), address: 'USER_WALLET_ADDRESS', merchantTransactionId: 'YOUR_TX_ID', signature: 'v2:GENERATED_SIGNATURE' }); ``` ### Parameter Names in the JS SDK Multi-word URL parameters use **camelCase** in `mercuryoWidget.run()`. Single-word parameters are identical in both formats. | URL parameter | JS SDK property | |---|---| | `widget_id` | `widgetId` | | `fiat_currency` | `fiatCurrency` | | `merchant_transaction_id` | `merchantTransactionId` | | `fix_payment_method` | `fixPaymentMethod` | See [Widget Parameters](./widget-parameters.md) for the full list with both URL and JS SDK formats. **Example — pre-authenticated user with pre-filled currency:** ```javascript mercuryoWidget.run({ widgetId: 'YOUR_WIDGET_ID', host: document.getElementById('mercuryo-widget'), address: 'USER_WALLET_ADDRESS', merchantTransactionId: 'YOUR_TX_ID', signature: 'v2:GENERATED_SIGNATURE', // Pre-authenticate the user (see User Pre-Authorization) initTokenType: 'sdk_partner_authorization', initToken: '0a25dd714163a9006', // Pre-fill and lock currency fiatCurrency: 'EUR', currency: 'USDT', fixFiatCurrency: true }); ``` ### JS SDK Callbacks The JS SDK exposes callback properties in `mercuryoWidget.run()` that fire on widget events. Use these to react to transaction status changes, user actions, and widget lifecycle. ```javascript mercuryoWidget.run({ widgetId: 'YOUR_WIDGET_ID', host: document.getElementById('mercuryo-widget'), address: 'USER_WALLET_ADDRESS', merchantTransactionId: 'YOUR_TX_ID', signature: 'v2:GENERATED_SIGNATURE', onLoad: () => { console.log('Widget is loading'); }, onReady: () => { console.log('Widget is ready'); }, onStatusChange: (data) => { console.log('Status changed:', data); // { status, merchant_transaction_id, amount, currency, network, // fiat_amount, fiat_currency, payment_method } }, onPaymentFinished: (data) => { console.log('Payment finished:', data); // { payment_method } }, onSellTransferEnabled: (data) => { console.log('Sell address ready:', data); // { id, amount, currency, network, address, flow_id } }, onUserLoggedIn: (data) => { console.log('User logged in:', data); // { token } }, onUserLoggedOut: () => { console.log('User logged out'); } }); ``` | Callback | Fires when | Payload | |---|---|---| | `onLoad` | Widget starts loading | — | | `onReady` | Widget is fully loaded and interactive | — | | `onStatusChange` | Transaction status changes (fires on every change) | `{ status, merchant_transaction_id, amount, currency, network, fiat_amount, fiat_currency, payment_method }` | | `onPaymentFinished` | On-Ramp: fiat payment processed and KYC check (if any) completed — regardless of outcome | `{ payment_method }` | | `onSellTransferEnabled` | Off-Ramp: user has selected a sell method and the crypto deposit address is shown | `{ id, amount, currency, network, address, flow_id }` | | `onUserLoggedIn` | User successfully signed in to the widget | `{ token }` | | `onUserLoggedOut` | User signed out of the widget | — | ### iFrame HTML Alternative If you prefer a plain ` ``` > **Note:** Camera permission is required. The `allow="camera"` attribute is mandatory — users need camera access for the KYC liveness test. Without it, KYC will fail. **Dashboard domain setup:** In your widget's **Domain URL** field, enter the exact domain where the widget is embedded. No trailing slashes or characters. A domain mismatch shows the error `widget.mercuryo.io refused to connect`. --- ## Mobile Integration ### iOS Use the [Mercuryo-Widget-Wrapper-iOS](https://github.com/mercuryoio/Mercuryo-Widget-Wrapper-iOS) library: ```swift import MRCRWidget let configuration = try WidgetConfiguration( widgetId: "YOUR_WIDGET_ID", environment: .production, params: nil ) let vc = WidgetAssembly( configuration: configuration, uiDelegate: self ).build() self.present(vc, animated: true) ``` ### Android — WebView Use the [Mercuryo-Widget-Wrapper-Android](https://github.com/mercuryoio/Mercuryo-Widget-Wrapper-Android) library: ```kotlin import MRCRWidget class MainActivity : MercuryoWebViewActivity(val viewID: Int) { // Widget initialized automatically } ``` ### Android — Custom Tabs Custom Tabs are required to enable Google Pay in Android apps. WebView does not support Google Pay. Basic implementation: ```kotlin val url = "https://exchange.mercuryo.io" val intent = CustomTabsIntent.Builder().build() intent.launchUrl(requireContext(), Uri.parse(url)) ``` With custom appearance: ```kotlin val customTabsIntent = CustomTabsIntent.Builder() .setToolbarColor(Color.BLUE) .build() customTabsIntent.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left) customTabsIntent.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right) customTabsIntent.launchUrl(requireContext(), Uri.parse(url)) ``` **Benefits of Custom Tabs vs WebView:** * Google Pay works correctly * Consistent URL handling with WebView integration * Better conversion rates for mobile payment options --- ## Mobile Payment Compatibility ### Apple Pay | Browser | WebView | Custom Tab | |---|---|---| | Safari | Fully Compatible | Incompatible | | Google Chrome | Incompatible | Incompatible | | Chromium Browsers | Incompatible | Incompatible | ### Google Pay | Browser | WebView | Custom Tab | |---|---|---| | Safari | Incompatible | Fully Compatible | | Google Chrome | Incompatible | Fully Compatible | | Chromium Browsers | Incompatible | Fully Compatible | * [Countries where Google Pay is supported](https://support.google.com/googlepay/answer/12429287?hl=en#zippy=%2Cpay-online-or-in-apps) * [Countries where Mercuryo operates](./faq.md#where-does-mercuryo-operate) > The availability of mobile payment methods may vary depending on the user's device, OS version, and card issuer. # Signature Generation The `signature` parameter protects On-Ramp widget URLs from forgery. It must be included in every On-Ramp request. It is **not required** for Off-Ramp operations. ## How It Works The signature is validated when the user selects a payment method. If the signature is missing or invalid, the user sees the `Signature is invalid` error and cannot proceed with the payment. ## Signature Format Concatenate four values **without spaces**: ```plaintext address + secret + ip + merchant_transaction_id ``` Then compute a SHA-512 hash of that string and prefix it with `v2:`. **Parameters:** * `address` — the user's cryptocurrency wallet address * `secret` — your widget's Secret key from the Dashboard (Widgets → select widget → Secret) * `ip` — the IP address of the user opening the widget * `merchant_transaction_id` — your internal transaction ID > **Note:** `address` and `merchant_transaction_id` must also be present as URL parameters in the widget URL. If they're missing from the URL, signature validation will fail. ## Code Examples ### JavaScript (Node.js) ```javascript const crypto = require('crypto'); function generateSignature(address, secret, ip, merchantTransactionId) { const data = `${address}${secret}${ip}${merchantTransactionId}`; const hash = crypto.createHash('sha512').update(data).digest('hex'); return `v2:${hash}`; } // Example const signature = generateSignature( '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 'your_secret_key_123', '192.168.1.1', 'tx_123456' ); // Result: "v2:83a6473f7b0aade8cd794c38f435d8db..." ``` ### Python ```python import hashlib def generate_signature(address: str, secret: str, ip: str, merchant_transaction_id: str) -> str: data = f"{address}{secret}{ip}{merchant_transaction_id}" hash_value = hashlib.sha512(data.encode()).hexdigest() return f"v2:{hash_value}" # Example signature = generate_signature( '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 'your_secret_key_123', '192.168.1.1', 'tx_123456' ) # Result: "v2:83a6473f7b0aade8cd794c38f435d8db..." ``` ### Go ```go package main import ( "crypto/sha512" "encoding/hex" "fmt" ) func generateSignature(address, secret, ip, merchantTransactionID string) string { data := address + secret + ip + merchantTransactionID hash := sha512.Sum512([]byte(data)) return fmt.Sprintf("v2:%s", hex.EncodeToString(hash[:])) } ``` ## Result URL Example ```plaintext https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID &address=0x742d35Cc6634C0532925a3b844Bc454e4438f44e &merchant_transaction_id=tx_123456 &signature=v2:83a6473f7b0aade8cd794c38f435d8db621149f206e8488ec99ab12edefc5d55d605384dc0b3cc947a93fd3bd4d0d210c6972fcf39fbb4dce893729ddf8ea825 ``` ## Try It: Signature Calculator To verify your implementation, use the calculator below to generate a signature and compare it against the one your server produces. > **Note:** The hash is computed locally using your browser. Address 0x742d35Cc6634C0532925a3b844Bc454e4438f44e Secret •••••••••••••••••••••• IP 192.168.1.1 Merchant Transaction ID tx\_123456 Calculate Signature Please fill in all fields before calculating. Result `` # Widget Parameters Configure the widget by passing parameters when building the URL or initializing the JS SDK. * **URL / plain iFrame** — pass as URL query parameters in `snake_case` * **JS SDK** (`mercuryoWidget.run()`) — pass as properties in `camelCase` Multi-word parameters follow a consistent naming rule: `fiat_currency` → `fiatCurrency`. Single-word parameters (`currency`, `address`, `signature`, `network`, etc.) are identical in both formats. --- ## Most Used Parameters | URL parameter | JS SDK property | Description | Example | |---|---|---|---| | `widget_id` | `widgetId` | Your Widget ID from the Dashboard | `67710925-8b40-4767-846e-3b88db69f04d` | | `address` | `address` | User's crypto wallet address for receiving purchased crypto | `0x742d35Cc...` | | `merchant_transaction_id` | `merchantTransactionId` | Your internal transaction ID | `tx_123456` | | `signature` | `signature` | Security signature — see [Signature Generation](./security.md) | `v2:abc123...` | | `type` | `type` | Lock operation type: `buy` or `sell` | `buy` | | `currency` | `currency` | Pre-select cryptocurrency ticker | `BTC` | | `amount` | `amount` | Pre-fill cryptocurrency amount | `0.001` | | `fiat_currency` | `fiatCurrency` | Pre-select fiat currency ticker | `EUR` | | `fiat_amount` | `fiatAmount` | Pre-fill fiat amount | `100` | | `network` | `network` | Pre-select blockchain network | `ETHEREUM` | | `fix_currency` | `fixCurrency` | Prevent user from changing cryptocurrency | `true` | | `fix_fiat_currency` | `fixFiatCurrency` | Prevent user from changing fiat currency | `true` | | `fix_amount` | `fixAmount` | Lock cryptocurrency amount | `true` | | `fix_fiat_amount` | `fixFiatAmount` | Lock fiat amount | `true` | | `payment_method` | `paymentMethod` | Pre-select payment method: `card`, `mobile_pay`, `spend`, `spend_card` | `card` | | `fix_payment_method` | `fixPaymentMethod` | Lock payment method (use with `payment_method`) | `true` | | `address_map` | `addressMap` | Map of wallet addresses by currency (URL-encoded JSON) — [formats below](#address_map-formats) | — | | `refund_address` | `refundAddress` | Wallet address for refund on process failure (Off-Ramp, Swap) | `0x123...` | | `init_token` | `initToken` | Token for silent user sign-in — see [User Pre-Authorization](./authentication.md) | `0a25dd714163a9006` | | `init_token_type` | `initTokenType` | Type of the `init_token` | `sdk_partner_authorization` | | `share_token` | `shareToken` | SumSub share token for pre-verified KYC — see [KYC](./kyc.md) | `_act-ca0dae00...` | | `theme` | `theme` | Pre-built custom theme — see [Customization](./customization.md) | `trustwallet` | | `lang` | `lang` | Widget language (ISO 639-1) | `de` | | `redirect_url` | `redirectUrl` | URL to redirect user after transaction completes | `https://yourapp.com/success` | | `widget_flow` | `widgetFlow` | Open the widget on a specific flow | `recurrent_setup` | --- ## `address_map` Formats `address_map` accepts a URL-encoded JSON object that maps currency tickers to wallet addresses. Three formats are supported: Simple (address only): ```json {"BTC": "2N2SJgCWoksfTmQySbMZhgwmdy9QNLRuD84", "ETH": "0x7E64..."} ``` With network specified: ```json {"BTC": {"address": "2N2SJgCW...", "network": "BITCOIN"}, "ETH": {"address": "0x7E64...", "network": "ETHEREUM"}} ``` Mixed (some currencies with network, some without): ```json {"BTC": "2N2SJgCW...", "ETH": {"address": "0x7E64...", "network": "ETHEREUM"}} ``` --- ## Parameter Reference For parameters not specified in the table above, see the external references below: * [URL Parameter Reference](https://widget.mercuryo.io/docs.html) — URL parameters for redirect integration * [Interactive Demo](https://demo-widget.mercuryo.io) — try JS SDK parameters live in the browser # On-Ramp — Crypto Purchase Users buy cryptocurrency with fiat money using a payment card, Apple Pay, Google Pay, or APMs (alternative payment methods, e.g. Revolut Pay). ## Flow Overview 1. (Optional) Fetch data to pre-fill the widget and show rates on your side. 2. Generate the widget URL with required parameters. 3. Show the widget (redirect or embed). 4. The user completes payment. 5. Mercuryo sends crypto to the user's wallet. 6. Track transaction status via callbacks or API. --- ## Step 1 — Fetch Pre-fill Data (Optional) Use these endpoints before opening the widget to display rates and available currencies on your own UI, and to build the widget URL with relevant parameters. | Endpoint | Purpose | |---|---| | `GET /v1.6/public/data-by-ip` | Get user's country code from IP address | | `GET /v1.6/lib/currencies` | List of supported currencies with availability flags per country | | `GET /v1.6/public/card-countries` | Check if user's country supports card payments | | `GET /v1.6/public/currency-limits` | Min/max transaction limits for a currency pair | | `GET /v1.6/widget/buy/rate` | Current exchange rate and fee for a specific transaction | **Filtering by country:** combine `data.country.code` from `data-by-ip` with data from `lib/currencies`. For crypto-network pairs, check `widget_onramp_enabled` flag and verify the user's country is not in `restricted_countries_onramp`. For fiat currencies, check `onramp_enabled` and `restricted_countries_onramp`. --- ## Step 2 — Build the Widget URL Example URL for purchasing 0.001 BTC with USD: ``` https://exchange.mercuryo.io/?widget_id=67710925-8b40-4767-846e-3b88db69f04d &type=buy ¤cy=BTC &fix_currency=true &amount=0.001 &fix_amount=true &fiat_currency=USD &address=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh &merchant_transaction_id=tx_123456 &signature=v2:83a6473f7b0aade8cd794c38f435d8db... ``` Parameter summary: | Parameter | Required | Description | |---|---|---| | `widget_id` | Yes | Your Widget ID | | `type` | No | Lock to `buy` | | `currency` | No | Pre-select crypto (e.g., `BTC`) | | `fix_currency` | No | Prevent the user from changing crypto | | `amount` | No | Pre-fill crypto amount | | `fix_amount` | No | Lock the crypto amount | | `fiat_currency` | No | Pre-select fiat (e.g., `USD`) | | `address` | Yes (On-Ramp) | User's wallet to receive purchased crypto | | `merchant_transaction_id` | Yes | Your transaction ID (also used in signature) | | `signature` | Yes | See [Signature Generation](./security.md) | See the full [Widget Parameters](./widget-parameters.md) reference for all available options. --- ## Step 3 — Show the Widget * **Redirect:** `window.location.href = widgetUrl` * **iFrame JS SDK:** `mercuryoWidget.run({ widgetId: '...', host: ..., address: '...', ... })` * **Mobile:** use iOS or Android wrapper See [Integration Methods](./integration.md) for code examples. --- ## Step 4 — User Flow in the Widget Depending on what you pre-filled, the user may need to: * Enter the crypto amount and currency (if not pre-filled) * Provide their wallet address (if `address` was not passed) * Sign in or register with Mercuryo (skip with [Silent Authentication](./authentication.md)) * Complete KYC (skip with [SumSub share token](./kyc.md) if already verified on your side) * Select a payment method and complete payment --- ## Step 5 — Track Transaction Status After the user completes payment, Mercuryo sends crypto to the wallet address. Track the transaction status: * **Callbacks** — Mercuryo POSTs to your callback URL on every status change. See [Callbacks](./callbacks.md). * **API Polling** — `GET /v1.6/sdk-partner/transactions?merchant_transaction_id=tx_123456` See [Transaction Types & Statuses](./transactions.md) for all possible status values. # Off-Ramp — Crypto Sell Users sell cryptocurrency and receive fiat money to their Visa or Mastercard. > **Note:** Off-Ramp is disabled by default. Contact your integration manager to enable it for your widget. ## Prerequisites * The user must be signed in to Mercuryo and have a valid, non-expired KYC. * KYC must be completed before the sell transaction can proceed. * Only **EUR** and **USD** are available as fiat payout currencies. * Off-Ramp is available only in [selected countries](https://help.mercuryo.io/hc/en-gb/articles/14831527435805). ## 6-Hour Rule If the user does not send crypto within **6 hours** after initiating the sell, the request fails. If crypto arrives after this window, it is credited to the user's [Mercuryo Wallet](https://my.mercuryo.io) as a regular crypto deposit (not converted to fiat). Crypto is refunded to `refund_address` only if there is an error on Mercuryo's side. --- ## Flow Overview 1. (Optional) Fetch data to pre-fill the widget. 2. Generate the widget URL. 3. Show the widget. 4. The user provides card details and sends crypto to the Mercuryo wallet address shown in the widget. 5. Mercuryo processes the exchange and sends fiat to the user's card. 6. Track transaction status via callbacks or API. --- ## Step 1 — Fetch Pre-fill Data (Optional) Use these endpoints before opening the widget to display rates and available currencies on your own UI, and to build the widget URL with relevant parameters. | Endpoint | Purpose | |---|---| | `GET /v1.6/public/data-by-ip` | User's country code | | `GET /v1.6/lib/currencies` | Supported currencies with `widget_offramp_enabled` and `restricted_countries_offramp` | | `GET /v1.6/public/currencies-sell` | Fiat and crypto currencies available for sell | | `GET /v1.6/public/currency-limits` | Transaction limits | | `GET /v1.6/lib/limits/sell` | Limits for all enabled sell currency pairs | | `GET /v1.6/widget/sell/rate` | Current exchange rate and fee | --- ## Step 2 — Build the Widget URL Example URL for selling 0.1 BTC for USD: ``` https://exchange.mercuryo.io/?widget_id=67710925-8b40-4767-846e-3b88db69f04d &type=sell ¤cy=BTC &fix_currency=true &amount=0.1 &fix_amount=true &fiat_currency=USD &refund_address=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh &merchant_transaction_id=tx_789012 ``` Parameter summary: | Parameter | Required | Description | |---|---|---| | `widget_id` | Yes | Your Widget ID | | `type` | No | Lock to `sell` | | `currency` | No | Pre-select crypto to sell | | `fix_currency` | No | Prevent user from changing crypto | | `amount` | No | Pre-fill crypto amount | | `fix_amount` | No | Lock the amount | | `fiat_currency` | No | Pre-select payout fiat currency (`EUR` or `USD`) | | `refund_address` | No | Wallet address for refund on failure (user can enter in widget if not provided) | | `merchant_transaction_id` | No | Your tracking ID | > **Note:** Signature is not required for Off-Ramp. --- ## Step 3 — Show the Widget Same as On-Ramp. See [Integration Methods](./integration.md). --- ## Step 4 — User Flow in the Widget 1. The user enters card details for fiat payout. 2. The widget displays a crypto wallet address to send crypto to. 3. The user sends crypto to the displayed address. 4. Mercuryo confirms the deposit and initiates fiat payout. 5. The user's card receives the fiat amount. --- ## Step 5 — Track Transaction Status * **Callbacks** — see [Callbacks](./callbacks.md) * **API Polling** — `GET /v1.6/sdk-partner/transactions` See [Transaction Types & Statuses](./transactions.md) for `sell` and `deposit` status values. --- ## Spend Card Off-Ramp If Spend Card is enabled for your widget, users can sell crypto and receive EUR directly to their Spend Card instead of an external payment card. See [Spend Card](./spend-card.md) for widget URL, user flow, and transaction tracking. # Callbacks & Webhooks Mercuryo sends a POST request to your callback URL every time a transaction status changes. Callbacks are the recommended way to track transaction states. ## Setup 1. Sign in to the [Dashboard](https://dashboard.mercuryo.io). 2. Go to **Widgets** → select your widget. 3. Fill in the **Callback URL** field with your server endpoint. 4. Copy the **Sign Key** — you'll need it to verify incoming callbacks. Go to [Widget Callbacks](https://dashboard.mercuryo.io/widgets/callbacks) to browse callback history, resend a callback, or send a test callback. --- ## Transaction Callback Payload For the full payload schema and examples for all transaction types, see [Callback Reference](/api/callbacks). --- ## Identifying Transaction Types In callbacks, transactions are identified by a combination of `type` and `payment_method` fields. See [Transaction Types & Statuses](/guide/monitoring/transactions#in-callbacks) for the full reference table. > **Note:** Spend Card event callbacks (card status changes, card top-ups, card payments) use a separate callback URL and payload format. See [Spend Card → Event Callbacks](/spend-card/callbacks). --- ## Callback Delivery & Retry Logic Mercuryo considers a callback successfully delivered when your server responds with HTTP `200`. For any other response code, the system retries automatically: | Attempt | Delay | | --------------------------- | -------------------------------------------------------- | | 1st retry | 60 seconds | | 2nd retry | 120 seconds | | 3rd retry | 240 seconds | | Nth retry | 2^(N−1) × 60 seconds (N = attempt number, starting at 1) | | Maximum interval | 4 hours | | After reaching max interval | Every 4 hours for 3 days | | Total retry period | 3 days | You can also manually resend callbacks from the [Dashboard](https://dashboard.mercuryo.io/widgets/callbacks). --- ## Callback Signature Verification Each callback includes an `X-Signature` header containing an HMAC SHA-256 hash of the raw JSON body, generated using your Sign Key. > **Important:** Use the complete raw JSON body exactly as received — do not parse, reformat, or extract fields from it. Any modification will produce a different hash and cause verification to fail. ### JavaScript ```javascript const crypto = require('crypto'); function verifyCallbackSignature(rawBody, signature, signKey) { const hmac = crypto.createHmac('sha256', signKey); const calculated = hmac.update(rawBody).digest('hex'); return calculated === signature; } ``` ### Python ```python import hmac import hashlib def verify_callback_signature(raw_body: str, signature: str, sign_key: str) -> bool: calculated = hmac.new(sign_key.encode(), raw_body.encode(), hashlib.sha256).hexdigest() return calculated == signature ``` ### Go ```go import ( "crypto/hmac" "crypto/sha256" "encoding/hex" ) func verifyCallbackSignature(rawBody, signature, signKey string) bool { h := hmac.New(sha256.New, []byte(signKey)) h.Write([]byte(rawBody)) return hex.EncodeToString(h.Sum(nil)) == signature } ``` --- ## Try It: Callback Signature Calculator Paste your raw callback body and Sign Key to verify your implementation. > **Note:** The hash is computed locally using your browser. Sign Key •••••••••••••••••••• Raw JSON Body Calculate Signature Please fill in both fields before calculating. X-Signature `` Compare this value with the `X-Signature` header from the received callback. # Transaction Types & Statuses Mercuryo transactions are composed of multiple linked operations. Each operation type has its own lifecycle. This page describes all transaction types, their statuses, and how they relate to each other. --- ## How Transactions Are Identified ### In Callbacks Transactions are identified by the combination of `type` + `payment_method` fields. | `type` | `payment_method` | Description | |---|---|---| | `buy` | `card` | Purchase with a payment card | | `buy` | `mobile_pay` | Purchase with Apple Pay or Google Pay | | `buy` | `invoice` | Invoice Payment (APM, e.g., Revolut Pay) | | `sell` | `card` | Sell to external card | | `withdraw` | — | Crypto sent to user's wallet | | `deposit` | — | Crypto received by Mercuryo | ### In API Polling When using `GET /v1.6/sdk-partner/transactions`, transactions are returned as objects where the key is the type: ```json { "data": [ { "buy": { "id": "0de85a1545a175145", "transaction_id": "0de85a15474d42563" } } ] } ``` API polling type names: | Type | Description | |---|---| | `buy` | Purchase with card | | `mobile_pay` | Purchase with Apple Pay / Google Pay | | `buy_acquirer` | Invoice Payment (APM) | | `sell` | Sell crypto for fiat | | `withdraw` | Send crypto to user wallet | | `deposit` | Receive crypto to Mercuryo wallet | --- ## Card Payment (Buy with Card) Triggered when a user purchases crypto with a bank card. On success, automatically creates a **`withdraw`** transaction to send crypto to the user. **Identify:** Callback: `type=buy`, `payment_method=card` | API: `type=buy` | Status | Type | Description | |---|---|---| | `new` | Intermediate | Payment data entered, awaiting processing | | `pending` | Intermediate | Card verification and 3D Secure in progress | | `order_scheduled` | Intermediate | Payment authorized, awaiting KYC verification | | `descriptor_failed` | Intermediate | Card verification check failed | | `failed_exchange` | Intermediate | Exchange request failed | | `paid` | **Final** | Transaction successfully completed | | `order_failed` | **Final** | Declined by card issuer / acquirer | | `cancelled` | **Final** | Cancelled (funds returned if charged) | **State transitions:** | From | To | Trigger | |---|---|---| | `new` | `pending` | card verification started | | `new` | `order_failed` | bank system error | | `pending` | `order_scheduled` | payment authorized, KYC required | | `pending` | `descriptor_failed` | card verification check failed | | `pending` | `failed_exchange` | exchange error | | `pending` | `order_failed` | payment rejected | | `pending` | `cancelled` | system cancelled | | `order_scheduled` | `paid` | KYC passed or not required | | `order_scheduled` | `failed_exchange` | exchange error | | `order_scheduled` | `cancelled` | system cancelled | | `descriptor_failed` | `cancelled` | system cancelled | | `failed_exchange` | `cancelled` | system cancelled | --- ## Mobile Payment (Apple Pay / Google Pay) Streamlined purchase flow using mobile payment systems. On success, automatically creates a **`withdraw`** transaction. **Identify:** Callback: `type=buy`, `payment_method=mobile_pay` | API: `type=mobile_pay` | Status | Type | Description | |---|---|---| | `new` | Intermediate | Payment data entered, awaiting processing | | `pending` | Intermediate | Payment verification and processing | | `order_scheduled` | Intermediate | Payment authorized, awaiting KYC | | `failed_exchange` | Intermediate | Exchange request failed | | `paid` | **Final** | Successfully completed | | `order_failed` | **Final** | Declined by payment provider | | `cancelled` | **Final** | Cancelled (funds returned if charged) | **State transitions:** | From | To | Trigger | |---|---|---| | `new` | `pending` | payment processing started | | `new` | `order_failed` | bank system error | | `pending` | `order_scheduled` | order queued | | `pending` | `failed_exchange` | exchange error | | `pending` | `order_failed` | payment rejected | | `pending` | `cancelled` | system cancelled | | `order_scheduled` | `paid` | KYC passed or not required | | `order_scheduled` | `failed_exchange` | exchange error | | `order_scheduled` | `cancelled` | system cancelled | | `failed_exchange` | `cancelled` | system cancelled | --- ## Invoice Payment (APM) Alternative payment methods (e.g., Revolut Pay, bank transfer). On success, automatically creates a **`withdraw`** transaction. > **Note:** Status names differ between Callbacks and API Polling for this type. **Identify:** Callback: `type=buy`, `payment_method=invoice` | API: `type=buy_acquirer` ### Callback Statuses | Status | Type | Description | |---|---|---| | `payment_initiated` | Intermediate | Payment request created, redirect URL sent to user | | `refund_in_progress` | Intermediate | Refund being processed | | `completed` | **Final** | Successfully completed | | `failed` | **Final** | Processing error | | `cancelled` | **Final** | Cancelled (usually due to timeout) | | `refunded` | **Final** | Money successfully refunded | **State transitions (callbacks):** | From | To | Trigger | |---|---|---| | `payment_initiated` | `completed` | successfully processed | | `payment_initiated` | `failed` | processing error | | `payment_initiated` | `cancelled` | system cancelled | | `payment_initiated` | `refund_in_progress` | refund initiated | | `refund_in_progress` | `refunded` | refund completed | ### API Polling Statuses | Status | Type | Description | |---|---|---| | `new` | Intermediate | Transaction created | | `payment_received` | Intermediate | Provider received user's payment | | `refund_in_progress` | Intermediate | Refund being processed | | `completed` | **Final** | Successfully completed | | `failed` | **Final** | Processing error | | `cancelled` | **Final** | Cancelled | | `refunded` | **Final** | Refunded | **State transitions (API polling):** | From | To | Trigger | |---|---|---| | `new` | `payment_received` | user completed payment | | `new` | `failed` | processing error | | `new` | `cancelled` | system cancelled | | `payment_received` | `completed` | successfully processed | | `payment_received` | `refund_in_progress` | refund initiated | | `payment_received` | `cancelled` | system cancelled | | `refund_in_progress` | `refunded` | refund completed | --- ## Sell User sells crypto to receive fiat to their payment card. **Identify:** Callback: `type=sell` | API: `type=sell` Creates `sell` → user sends crypto → creates `deposit` → updates `sell` for payout. On failure: creates `withdraw` to return crypto. | Status | Type | Description | |---|---|---| | `new` | Intermediate | Sell request created (Phase 1: payout address shown; Phase 2: crypto received, preparing payout) | | `pending` | Intermediate | Payout processing started | | `succeeded` | **Final** | Payout completed | | `failed` | **Final** | Payout failed (crypto returned to user) | | `cancelled` | **Final** | Cancelled (crypto returned to user) | **State transitions:** | From | To | Trigger | |---|---|---| | `new` | `pending` | processing started | | `pending` | `succeeded` | payout completed | | `pending` | `failed` | processing error | | `pending` | `cancelled` | system cancelled | --- ## Deposit Service transaction created automatically when a user sends crypto for a sell operation. **Identify:** Callback: `type=deposit` | API: `type=deposit` | Status | Type | Description | |---|---|---| | `pending` | Intermediate | Awaiting blockchain confirmations | | `completed` | **Final** | Deposit confirmed | **State transitions:** | From | To | Trigger | |---|---|---| | `pending` | `completed` | deposit verified | --- ## Withdraw Service transaction created automatically in two cases: 1. After a successful **buy** (including `buy`+`spend`) — to send purchased crypto to the user's wallet. 2. After a **failed sell** (any variant) — to return crypto to the user. **Identify:** Callback: `type=withdraw` | API: `type=withdraw` | Status | Type | Description | |---|---|---| | `new` | Intermediate | Withdrawal created, awaiting processing | | `pending` | Intermediate | Verification in progress | | `completed` | **Final** | Crypto successfully sent to blockchain | | `failed` | **Final** | Withdrawal failed | | `cancelled` | **Final** | Cancelled by system | **State transitions:** | From | To | Trigger | |---|---|---| | `new` | `pending` | processing started | | `pending` | `completed` | successfully processed | | `pending` | `failed` | processing error | | `pending` | `cancelled` | system cancelled | --- ## Transaction Relationships ``` On-Ramp (buy / mobile_pay / buy_acquirer) └─ On success → creates withdraw (send crypto to user) Off-Ramp (sell, payment_method=card) ├─ Phase 1: sell created (payout address shown) ├─ Phase 2: deposit created (user sends crypto) ├─ Phase 3: sell updated for payout └─ On failure → creates withdraw (return crypto to user) ``` --- > For Spend Card transaction types (`buy`+`spend`, `fiat_deposit`+`spend_card_topup`, `sell`+`spend_card_topup`, `sell`+`spend_card_open`), see [Spend Card → Transaction Types](./spend-card-transactions.md). # User Pre-Authorization This section covers two approaches for authenticating users from your backend. **Silent Authentication** pre-authorizes users before they open the widget — they skip the login step entirely. **API-Based Authentication** is used exclusively for the Submit Documents KYC flow and does not authenticate users in the widget. All requests that register or sign in a user below require the `Sdk-Partner-Token` header. Contact your integration manager to obtain it. --- ## Silent Authentication Silent authentication lets your users skip the login step in the widget by passing a short-lived token in the URL. Mercuryo handles the authentication on its end — no user credentials are shared with you. Before using silent authentication, you must obtain the user's consent to Mercuryo's [Terms of Service](https://mercuryo.io/legal/terms/) on your side. > ⚠️ Be careful: in the API response the field is called `init_type_token`, while the widget URL parameter is `init_token_type` — read the value from `init_type_token` and pass it to the widget as `init_token_type`. ### Silent Sign-Up For users who **don't have a Mercuryo account yet**. **Endpoint:** [`POST /v1.6/sdk-partner/sign-up`](/api/widget-partner/tag/user-widget-authorization/post/sdk-partner/sign-up) Lets you register a new Mercuryo user without sending them through the widget's own sign-up form. Use `language_code` to pre-set the user's widget locale, and `share_token` to carry over KYC verification already done on your SumSub instance (see [KYC](./kyc.md)) so the user isn't asked to verify again in the widget. **Steps:** 1. Call `POST /v1.6/sdk-partner/sign-up` to get `init_token` and `init_type_token`. 2. Pass these as `init_token` and `init_token_type` URL parameters when redirecting the user to the widget. 3. Show your widget. ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID &init_token_type=sdk_partner_authorization &init_token=0a25dd714163a9006 ``` ### Silent Sign-Up + SumSub KYC For users who already have a verified KYC on your SumSub instance. Same as Sign-Up but also include `share_token` in the request body: ``` POST /v1.6/sdk-partner/sign-up { "email": "user@example.com", "accept": true, "share_token": "_act-ca0dae00-0ecd-000d-00e0-00d0ca000b0d" } ``` Widget URL with both tokens: ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID &init_token_type=sdk_partner_authorization &init_token=0a25dd714163a9006 &share_token=_act-ca0dae00-0ecd-000d-00e0-00d0ca000b0d ``` ### Silent Sign-In For users who **already have a Mercuryo account**. **Endpoint:** [`POST /v1.6/sdk-partner/login`](/api/widget-partner/tag/user-widget-authorization/post/sdk-partner/login) Identify the user by one of `email`, `phone`, or `user_uuid4`. **Steps:** 1. Call `POST /v1.6/sdk-partner/login` to get `init_token` and `init_type_token`. 2. Pass them as `init_token` and `init_token_type` URL parameters. 3. Show your widget. ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID &init_token_type=sdk_partner_authorization &init_token=0a25dd714163a9006 ``` --- ## API-Based Authentication This approach authorizes the user on your backend, so you can perform actions on their behalf via the API — for example, uploading KYC documents (see [Submit Documents via API](./kyc.md#option-3-submit-documents-via-api)). Unlike silent authentication, user information **is** shared with you during the process. Signing the user in returns an `Sdk-User-Token` (as `bearer_token` in the response) — pass it in the header of any subsequent API call made on behalf of that user. It expires in **24 hours** in Production and **does not expire** in Sandbox. Before signing up, the user must accept the [Terms of Service](https://mercuryo.io/legal/terms/) on your frontend. Pass their consent in the `accept` parameter. ### OTP-less Sign-In For users whose email and identity you've already verified on your side — no additional verification is required. **Endpoint:** [`POST /v1.6/sdk-partner/user/sign-in-no-verify`](/api/widget-partner/tag/user-api-authentication/post/sdk-partner/user/sign-in-no-verify) **Steps:** 1. Call `POST /v1.6/sdk-partner/user/sign-in-no-verify` with the user's email. 2. Take the `bearer_token` field from the response and use it as the `Sdk-User-Token` header value in subsequent requests made on behalf of the user. ### Sign-In with OTP Verification For users who must verify their email via a one-time code. **Endpoints:** * [`POST /v1.6/sdk-partner/user/sign-in`](/api/widget-partner/tag/user-api-authentication/post/sdk-partner/user/sign-in) * [`POST /v1.6/sdk-partner/user/sign-in/verify`](/api/widget-partner/tag/user-api-authentication/post/sdk-partner/user/sign-in/verify) * [`POST /v1.6/sdk-partner/user/sign-in/verify/resend`](/api/widget-partner/tag/user-api-authentication/post/sdk-partner/user/sign-in/verify/resend) **Steps:** 1. Call `POST /v1.6/sdk-partner/user/sign-in` with the user's email. An OTP is sent to the user. 2. Call `POST /v1.6/sdk-partner/user/sign-in/verify` with the OTP entered by the user. * To resend the OTP: `POST /v1.6/sdk-partner/user/sign-in/verify/resend` 3. Take the `bearer_token` field from the verify response and use it as the `Sdk-User-Token` header value in subsequent requests made on behalf of the user. ### Sign-Out and Session Refresh Once the user is signed in, you can end their session or refresh it. These endpoints require the `Sdk-User-Token` header instead of `Sdk-Partner-Token`: * [`POST /v1.6/sdk-partner/user/sign-out`](/api/widget-partner/tag/user-api-authentication/post/sdk-partner/user/sign-out) — sign the user out. * [`GET /v1.6/sdk-partner/user/refresh-token`](/api/widget-partner/tag/user-api-authentication/get/sdk-partner/user/refresh-token) — refresh the user's session. # KYC & Compliance Know Your Customer (KYC) procedures are required by law for financial institutions operating under AML/CFT regulations. Mercuryo handles KYC verification on its side — you choose how deeply to integrate it. SumSub is Mercuryo's primary KYC provider. --- ## When KYC Is Required KYC verification is mandatory for: * All **Off-Ramp (sell)** operations. * **On-Ramp (buy)** operations when: * The transaction amount exceeds the KYC threshold. * The user's country requires mandatory KYC. * The specific payment method requires KYC. --- ## KYC Requirements by Feature and Country | Feature | Country / Region | Required Documents | |---|---|---| | `crypto` | EU + EEA | ID card / passport / residence permit / driving licence + questionnaire + selfie | | `crypto` | EU + EEA, and non-EU countries | ID card / passport / residence permit / driving licence + selfie | | `crypto` | US | ID card / passport / residence permit / driving licence + SSN + selfie | | `crypto` | RU (IP outside EEA) | Passport / driving licence + proof of address + selfie | | `crypto` | RU (IP within EEA) | Passport / driving licence + residence permit + selfie | | `crypto` | BR (DOC_FREE + selfie scenario) | CPF + selfie | | `crypto` | BR (when DOC_FREE failed) | ID card / passport / residence permit / driving licence + selfie | | `card + crypto` | EU | ID card / passport + proof of address + selfie — required for Spend Card issuance | | `ssn` | US | Proof of address (as data) + TIN | **Available features:** * `crypto` — enables all crypto buy/sell operations. * `card` — enables Spend Card issuance and usage. Required in addition to `crypto` for users in EU who want to issue a Spend Card. * `ssn` — simplified US flow using SSN; up to a transaction limit without documents. Full KYC (`crypto`) is required after the limit. --- ## Identity Document Requirements The document must contain: * Full name * MRZ code * Citizenship * Date of birth * Document number * Issuing authority * Date of issue The document must: * Be unexpired * Be scanned or photographed clearly * Show all corners and sides * Have all information legible ## Proof of Address We do not accept bank statements from neobanks. --- ## Integration Options ### Option 1 — Standard In-Widget KYC If you don't implement any KYC on your side, Mercuryo provides the SumSub interface directly in the widget. Users complete identity verification and a liveness test without leaving the widget. No additional integration required. This is the default. ### Option 2 — SumSub Share Token If you already use SumSub for KYC on your platform, you can share verified users with Mercuryo. Users won't need to verify twice. > **Note:** This option requires approval from Mercuryo's Customer Success and Compliance teams, as SumSub charges for this service. Discuss it with your Mercuryo manager before proceeding. **Whitelisting prerequisite:** 1. Conclude a contract with SumSub for the Reusable KYC feature. 2. Request a **partner token** from your Mercuryo integration manager. 3. In your SumSub account: **Partners** → **Recipients** → **Add Recipient** → enter the partner token. 4. Ask your Mercuryo manager to verify the setup. **Processing rules:** * The applicant must have completed **ID document + liveness (selfie)** verification with **approved status** on your side. * The share token flow is triggered by **email match** between your SumSub applicant and the Mercuryo user. * Mercuryo can accept a share token only **once** per applicant (SumSub architecture limitation). * Field sets may differ between systems — only document type and country are used for matching. **Steps:** 1. Generate `share_token` using the SumSub API: ``` POST https://api.sumsub.com/resources/accessTokens/-/shareToken ``` Required parameters: `applicantId`, `forClientId=Mercuryo` 2. Pass the generated value as `share_token` in the widget URL or in the `sign-up` request body. See [User Pre-Authorization](./authentication.md) for how to pass `share_token` during sign-up. [How it works — video](https://www.youtube.com/watch?v=DpGd8wy07RM) | [SumSub sharing docs](https://developers.sumsub.com/api-reference/#sharing-applicants-between-partner-services) ### Option 3 — Submit Documents via API Manually send user documents to Mercuryo via API. Users still need to complete the liveness check in the widget. Requires [API-based Authentication](./authentication.md#api-based-authentication). **Steps:** 1. Call `GET /v1.6/sdk-partner/user/kyc-status` to check if KYC is already complete. * If `features[].status == "complete"` for the needed feature — no action needed. 2. Call `POST /v1.6/sdk-partner/kyc/docs` to upload documents. * Include: ID card, driving licence, passport, residence permit, proof of address (see API Reference for file naming conventions), `feature`, and country code. * The response contains `kyc_access_token` — use it as `share_token`. 3. Authenticate the user: call [`POST /v1.6/sdk-partner/login`](/api/widget-partner/tag/user-widget-authorization/post/sdk-partner/login) to get `init_token` and `init_type_token`. * Be careful: the response field is named `init_type_token`, while the redirect URL parameter below is `init_token_type` — see [Silent Authentication](./authentication.md#silent-authentication). 4. Redirect the user to the widget with all tokens: ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID &init_token_type=sdk_partner_authorization &init_token=0a25dd714163a9006 &share_token=YOUR_KYC_ACCESS_TOKEN ``` 5. The user completes any remaining documents and the liveness check in the widget. 6. Poll `GET /v1.6/sdk-partner/user/kyc-status` for the result: | Status | Description | |---|---| | `complete` | KYC successfully completed | | `incomplete` | SumSub has not started verification yet | | `failed_attempt` | First attempt failed — user can retry | | `failed` | Verification failed — contact Mercuryo Support | | `under_review` | SumSub is reviewing the submitted documents | 7. Once `complete`, redirect the user to the widget to proceed with the transaction. --- ## KYC for Spend Card Issuing a Spend Card requires the `card` KYC feature in addition to `crypto`. For EU users, this means providing an ID card or passport, proof of address, and a selfie (`card + crypto` row in the table above). The required documents vary by the user's country of citizenship — see the [full list of eligible countries](https://help.mercuryo.io/hc/en-gb/articles/27685426171421-Spend-Card). See [Spend Card](./spend-card.md) for full integration details. # Widget Customization Customize the Mercuryo Widget to match your brand identity. You can apply a pre-built theme or create a fully custom color scheme. --- ## Pre-Built Themes Add the `theme` parameter to the widget URL: ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID&theme=THEME_NAME ``` Click a theme name to preview it: | Theme | Preview | |---|---| | exmo | [Preview](https://exchange.mercuryo.io/?theme=exmo) | | bitx | [Preview](https://exchange.mercuryo.io/?theme=bitx) | | btc_alpha | [Preview](https://exchange.mercuryo.io/?theme=btc_alpha) | | xzen | [Preview](https://exchange.mercuryo.io/?theme=xzen) | | ch | [Preview](https://exchange.mercuryo.io/?theme=ch) | | savl | [Preview](https://exchange.mercuryo.io/?theme=savl) | | spatium | [Preview](https://exchange.mercuryo.io/?theme=spatium) | | now_payments | [Preview](https://exchange.mercuryo.io/?theme=now_payments) | | invity | [Preview](https://exchange.mercuryo.io/?theme=invity) | | phemex | [Preview](https://exchange.mercuryo.io/?theme=phemex) | | lumi | [Preview](https://exchange.mercuryo.io/?theme=lumi) | | trustwallet | [Preview](https://exchange.mercuryo.io/?theme=trustwallet) | | roobee | [Preview](https://exchange.mercuryo.io/?theme=roobee) | | kickex | [Preview](https://exchange.mercuryo.io/?theme=kickex) | | quantfury | [Preview](https://exchange.mercuryo.io/?theme=quantfury) | | simpleswap | [Preview](https://exchange.mercuryo.io/?theme=simpleswap) | | 1inch | [Preview](https://exchange.mercuryo.io/?theme=1inch) | | digifinex | [Preview](https://exchange.mercuryo.io/?theme=digifinex) | | mycelium | [Preview](https://exchange.mercuryo.io/?theme=mycelium) | | coinstats | [Preview](https://exchange.mercuryo.io/?theme=coinstats) | | advantechLight | [Preview](https://exchange.mercuryo.io/?theme=advantechLight) | | advantechDark | [Preview](https://exchange.mercuryo.io/?theme=advantechDark) | | swapspace | [Preview](https://exchange.mercuryo.io/?theme=swapspace) | | tonkeeper | [Preview](https://exchange.mercuryo.io/?theme=tonkeeper) | | bitpapa | [Preview](https://exchange.mercuryo.io/?theme=bitpapa) | | bitpapaDark | [Preview](https://exchange.mercuryo.io/?theme=bitpapaDark) | | czixlight | [Preview](https://exchange.mercuryo.io/?theme=czixlight) | | czixdark | [Preview](https://exchange.mercuryo.io/?theme=czixdark) | | swapspace2 | [Preview](https://exchange.mercuryo.io/?theme=swapspace2) | | 3commas_light | [Preview](https://exchange.mercuryo.io/?theme=3commas_light) | | 3commas_dark | [Preview](https://exchange.mercuryo.io/?theme=3commas_dark) | | 3commas_web_dark | [Preview](https://exchange.mercuryo.io/?theme=3commas_web_dark) | | 3commas_web_light | [Preview](https://exchange.mercuryo.io/?theme=3commas_web_light) | | unionbtc | [Preview](https://exchange.mercuryo.io/?theme=unionbtc) | | arctic | [Preview](https://exchange.mercuryo.io/?theme=arctic) | | dark_ch | [Preview](https://exchange.mercuryo.io/?theme=dark_ch) | | light_ch | [Preview](https://exchange.mercuryo.io/?theme=light_ch) | | white_market | [Preview](https://exchange.mercuryo.io/?theme=white_market) | | exolix | [Preview](https://exchange.mercuryo.io/?theme=exolix) | | grapherex_light | [Preview](https://exchange.mercuryo.io/?theme=grapherex_light) | | grapherex_dark | [Preview](https://exchange.mercuryo.io/?theme=grapherex_dark) | | haru_light | [Preview](https://exchange.mercuryo.io/?theme=haru_light) | | haru_dark | [Preview](https://exchange.mercuryo.io/?theme=haru_dark) | --- ## Custom Design To create a fully custom color scheme, use the [Figma Customization Guide](https://www.figma.com/design/HWyqd79rUqtuv79tXuJFnh/OOR-Theme-book?node-id=39-4683). You can customize: * Button colors * Input field styles * Background and text colors * Light and dark theme variants After designing your theme in Figma, contact your integration manager to have it implemented in your widget. --- ## Brand Assets Download official Mercuryo brand assets from the GitHub repository: * [Mercuryo Fonts](https://github.com/mercuryoio/api-migration-docs/blob/master/design/Mercuryo_fonts.zip) * [Mercuryo Logos](https://github.com/mercuryoio/api-migration-docs/blob/master/design/Mercuryo_logos.zip) # Try Again Feature The Try Again feature lets users retry a failed On-Ramp payment attempt directly inside the widget, without starting over. This is especially useful when the initial payment fails due to insufficient funds, card blocking, or bank rejection. > To enable and configure Try Again, contact your integration manager and specify your preferences for amount modification, payment method modification, callback behavior, and maximum retry attempts. --- ## Configuration Options ### Transaction Amount Modification * **Enabled:** users can change the fiat/crypto amount during a retry. * **Disabled:** original amount is preserved for all retry attempts. ### Payment Method Modification * **Enabled:** users can switch payment methods between attempts (e.g., from card to Google Pay). * **Disabled:** users must retry with the original payment method. ### Callback Configuration * **Enabled (intermediate callbacks sent):** all status transitions trigger callbacks. * **Disabled (final callbacks only):** only final statuses (`paid`, `cancelled`, `order_failed`) trigger callbacks. ### Maximum Retry Attempts Set the maximum number of times the user can retry. --- ## Callback Behavior with Try Again When a user retries, Mercuryo creates a **new transaction** with a new `id`. The `merchant_transaction_id` stays the same across all retries, allowing you to group attempts by your own transaction ID. Parameters that may change across retries: | Parameter | When it changes | |---|---| | `id` | Every retry (new Mercuryo transaction ID) | | `created_at` | Every retry | | `created_at_ts` | Every retry | | `fiat_currency` | If Transaction Amount modification is enabled | | `fiat_amount` | If Transaction Amount modification is enabled | | `fiat_amount_usd` | If Transaction Amount modification is enabled | | `amount` | If Transaction Amount modification is enabled | | `rate` | If Transaction Amount modification is enabled | | `payment_method` | If Payment Method modification is enabled | ### Callback Example — First Attempt ```json { "eventId": "ead92193-5310-4962-9730-d7280d623410", "data": { "amount": "0.00048148", "fiat_amount": "25.00", "fiat_amount_usd": "28.43", "id": "0c9be256a0eed7062", "created_at": "2024-09-11 09:48:51", "created_at_ts": 1726048131, "updated_at": "2024-09-11 09:48:53", "updated_at_ts": 1726048133, "type": "buy", "merchant_transaction_id": "trx_20240911_102155662", "currency": "BTC", "network": "BITCOIN", "fiat_currency": "EUR", "payment_method": "card", "status": "order_scheduled", "user": { "uuid4": "00e0043f-9c9a-4a93-b4b3-f10729af6ef6", "country_code": "de", "phone": "*****", "email": "*****" }, "card": { "number": "*****" }, "fee": "0.28", "partner_fee": "0.00", "rate": "51341.70", "card_masked_pan": null, "contract_address": null } } ``` ### Callback Example — Second Attempt (Retry) ```json { "eventId": "b7c4e201-8f93-47d1-a056-e3291b804f78", "data": { "amount": "0.00024148", "fiat_amount": "13.7", "fiat_amount_usd": "13.70", "id": "0c9be512a0ebd8012", "created_at": "2024-09-11 09:50:03", "created_at_ts": 1726056363, "updated_at": "2024-09-11 09:50:53", "updated_at_ts": 1726056363, "type": "buy", "merchant_transaction_id": "trx_20240911_102155662", "currency": "BTC", "network": "BITCOIN", "fiat_currency": "USD", "payment_method": "mobile_pay", "status": "paid", "user": { "uuid4": "00e0043f-9c9a-4a93-b4b3-f10729af6ef6", "country_code": "de", "phone": "*****", "email": "*****" }, "fee": "0.15", "partner_fee": "0.00", "rate": "56341.70", "card_masked_pan": null, "contract_address": null } } ``` --- ## Callback Optimization Options ### Disable All Intermediate Callbacks When enabled, Mercuryo only sends callbacks for final statuses: `paid`, `cancelled`, `order_failed`. Intermediate statuses (`new`, `pending`) are suppressed for all transactions. **Recommended when:** users can create many transactions in rapid succession and you only care about the outcome. Without this setting, a sequence of retries will generate callbacks like: ``` new → pending → new → pending → new → pending → {final_status} ``` ### Disable Intermediate Callbacks for Retry Attempts Only Intermediate callbacks are sent only for the **first** attempt. For all subsequent retries, only final status callbacks are sent. | Attempt | Callbacks sent | |---|---| | First | `new`, `pending`, `paid`, `cancelled`, `order_failed` | | 2nd, 3rd, ... | Only: `paid`, `cancelled`, `order_failed` | # Passkey Feature Passkey is a passwordless login feature for Mercuryo users. Instead of entering a password or email code, users confirm sign-in using Face ID, fingerprint, PIN, or a hardware key on their device. --- ## Technical Limitations * **Not supported in Safari** on iPhones, iPads, or Mac. * **Not available in WebView** — WebView does not implement the required passkey browser APIs. * **Not triggered for partner silent sign-in** — passkey is only used when users log in directly via the Mercuryo widget login form. --- ## End-User Flow 1. After the feature is enabled on the Mercuryo side, the user must first log in using one of: * Email verification code * Email code + password (if a password was set) 2. After login, the user is prompted to set up a Passkey. They can skip this step; if skipped, they'll be prompted again on subsequent logins. 3. Once a Passkey is set, the user can use it instead of the email verification code on future logins. The email code remains available as a fallback. 4. If the user uses **non-cloud key storage** (e.g., a local device), they must set up the Passkey on each device they use. 5. If a user uses **1Password** for passkeys and switches devices, they must add a new passkey on the new device. After that, the same 1Password entry works across devices. --- ## User Management * Each user can have up to **10 passkeys** set. * Passkeys are managed via the widget menu: users can rename keys, add new ones, or remove unused ones. # Off-Chain Transactions When users buy cryptocurrency with Mercuryo, a blockchain network fee is added to the transaction cost. In some cases, this fee can significantly increase the effective price of a purchase. The **Off-Chain** feature eliminates network fees entirely by processing transactions internally, without recording them on the blockchain. > To enable Off-Chain, contact your integration manager. --- ## How It Works Instead of sending purchased crypto to the user's personal wallet over the blockchain, the crypto is credited to a **Mercuryo business wallet** registered on your behalf. You manage distribution from there. **Important limitations:** * The business wallet cannot be used to **sell** cryptocurrency. * It is used to credit purchased crypto and to withdraw crypto to external wallets. --- ## Setup Steps ### 1. Sign Additional Terms & Conditions If you already provided Mercuryo with your company's registration data, founders, and directors during the On/Off-Ramp onboarding, no additional documents are required. Sign the consent form and return it by email. ### 2. Create a Mercuryo Business Wallet Register a new wallet at [my.mercuryo.io/login/email](https://my.mercuryo.io/login/email). Use an email address that has **never been used for any Mercuryo service** before. ### 3. Notify Your Integration Manager Share the email address used to register the wallet. The integration manager will: * Designate it as a business wallet. * Disable the minimum commission parameter for your account. ### 4. Use the Business Wallet Address in Transactions In your wallet, copy the receiving address for the relevant network. Use this address as the `address` parameter in widget URLs instead of individual user addresses. # Recurring Payments Recurring Payments let users set up automatic crypto purchases on a regular schedule — weekly or monthly. Once configured, the user's card is charged automatically on the selected day. > To enable Recurring Payments for your widget, contact your integration manager. --- ## How It Works When Recurring Payments are enabled for your widget, users can access them from the navigation menu (top-right icon). This opens the plan management screen. You can also open the widget directly on either recurring screen using the `widget_flow` parameter: | Value | Opens | |---|---| | `recurrent_setup` | Plan creation — the user selects frequency and charge day, then completes the initial payment | | `recurrent_manage` | Plan management — the user views existing plans and can cancel them | When the widget is opened with either of these values, the standard buy/sell flow is not accessible — only the recurring screens are shown. **Plan creation flow (`recurrent_setup`):** 1. The user selects a frequency (`weekly` or `monthly`) and a charge day. 2. The user completes the initial payment to activate the subscription. 3. After setup, the user is redirected to your `redirect_url`. 4. Subsequent charges happen automatically on the configured schedule. You can pre-fill and lock frequency and charge day using URL parameters — see [Key Parameters](#key-parameters) below. --- ## Key Parameters | Parameter | Description | |---|---| | `widget_flow` | `recurrent_setup` to open plan creation; `recurrent_manage` to open plan management | | `address` | User's wallet address for receiving purchased crypto | | `currency` | Pre-select cryptocurrency (e.g., `USDT`) | | `fiat_currency` | Pre-select fiat currency (e.g., `EUR`) | | `fiat_amount` | Pre-fill fiat amount | | `network` | Pre-select blockchain network (e.g., `ETHEREUM`, `TRON`, `BINANCESMARTCHAIN`) | | `frequency` | Lock the charge frequency: `weekly` or `monthly` | | `charge_day` | Lock the charge day. For `monthly`: `1`–`31`. For `weekly`: `monday`–`sunday` | | `merchant_subscription_id` | Your own identifier for the recurring plan. Max 255 characters. Must be unique per widget — returns an error if a plan with this ID already exists for your widget. | | `merchant_transaction_id` | Your internal transaction ID | | `redirect_url` | URL to redirect the user after completing subscription setup | | `signature` | Security signature — see [Signature Generation](./security.md) | If `frequency` or `charge_day` is pre-set, the value is fixed in the widget UI. The user must return to your screen to change it. See the full [Widget Parameters](./widget-parameters.md) reference for all available options. --- ## Example **Redirect URL:** ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID &widget_flow=recurrent_setup ¤cy=USDT &network=TRON &fiat_currency=EUR &fiat_amount=540 &address=TQhoZ9hgkQ1NMAMJZ23EPaGtJDMCmv6yJd &frequency=monthly &charge_day=15 &merchant_subscription_id=your-plan-id-001 &merchant_transaction_id=tx_123456 &redirect_url=https://yourapp.com/success &signature=v2:GENERATED_SIGNATURE ``` **iFrame JS SDK:** ```javascript mercuryoWidget.run({ widgetId: 'YOUR_WIDGET_ID', host: document.getElementById('mercuryo-widget'), address: 'TQhoZ9hgkQ1NMAMJZ23EPaGtJDMCmv6yJd', widgetFlow: 'recurrent_setup', currency: 'USDT', network: 'TRON', fiatCurrency: 'EUR', fiatAmount: '540', frequency: 'monthly', chargeDay: '15', merchantSubscriptionId: 'your-plan-id-001', merchantTransactionId: 'tx_123456', redirectUrl: 'https://yourapp.com/success', signature: 'v2:GENERATED_SIGNATURE' }); ``` --- ## Callbacks ### Subscription lifecycle (`type=recurring_subscription`) When a recurring subscription changes status, Mercuryo sends a `type=recurring_subscription` callback to your configured callback URL — separate from individual charge notifications. | Event | `status` | `cancel_reason` | |---|---|---| | First charge completed, subscription is now active | `active` | — | | User cancelled the subscription | `cancelled` | `user_cancelled` | | System cancelled after 3 consecutive failed charges | `cancelled` | `system_attempts_exceeded` | See the [Callback Reference](/api/callbacks/tag/callbacks/post/callback/subscription) for the full payload structure and examples. ### Charge enrichment Buy and withdrawal transactions created by a recurring charge include three additional fields in their callback payload. These fields are absent on non-recurring transactions. | Field | Type | Description | |---|---|---| | `recurring_subscription_id` | string | Recurring plan identifier in Mercuryo | | `recurring_merchant_subscription_id` | string \| null | Your plan identifier (`merchant_subscription_id`). `null` if not provided at setup | | `recurring_execution_attempt` | integer | Charge attempt number within the current cycle (1–3) | See the [Callback Reference](/api/callbacks/tag/callbacks/post/callback/transaction) for the full transaction callback payload. --- > **Sandbox:** Only `GBP` is available as the fiat currency when testing recurring payments in the Sandbox environment. # Mercuryo PRO Mercuryo PRO is an OTC (over-the-counter) widget for large transactions — equivalent to **€50,000 or more** — processed via IBAN transfer. Each transaction is handled with dedicated support from a Customer Success Manager. --- ## Features * High-volume transaction processing (€50,000+) * Dedicated Customer Success Manager per transaction * Lower commissions starting from 1% (below 1% for transactions over €1 million) * Currently supports **USD and EUR** only * Specialized KYB/KYC procedures (different from standard widget verification) * Requires a separate agreement between Mercuryo and the client --- ## Integration Methods ### Method 1 — Automatic Redirect from Standard Widget When a user enters an amount of €50,000 or more in the standard widget, the widget automatically shows a Mercuryo PRO notification with a link to the PRO interface. No additional integration is required. ### Method 2 — Direct Redirect Generate a PRO widget URL for a specific user and send it directly. **Steps:** 1. Call `GET /v1.6/get-otc-id` to get a new `otc_id`. 2. Build the PRO URL with the OTC ID. 3. Send the URL to the user. Mercuryo will contact them to proceed. **URL example:** ``` https://exchange.mercuryo.io/?otc_id=4d5591ad-5129-4f11-859c-298e4ed1508c ``` **Supported parameters:** | Parameter | Description | Example | |---|---|---| | `otc_id` | OTC transaction ID | `4d5591ad-...` | | `fiat_amount` | Fiat amount | `60000` | | `fiat_currency` | Fiat currency | `EUR` | | `type` | Operation type | `buy` or `sell` | | `amount` | Crypto amount | `0.1` | | `currency` | Cryptocurrency | `BTC` | | `network` | Blockchain network | `SOLANA` | **Full URL example:** ``` https://exchange.mercuryo.io/?otc_id=123&fiat_amount=60000&fiat_currency=EUR&type=sell¤cy=USDC&network=POLYGON ``` # FAQ ## Where does Mercuryo operate? Mercuryo is available in most countries worldwide. To check the full list of restricted countries, see: [Where does Mercuryo operate?](https://help.mercuryo.io/hc/en-gb/articles/14495532693021-Where-does-Mercuryo-operate-) The user's location is determined by: 1. IP address 2. Card issuer country 3. Country of residence confirmed during KYC ## What cryptocurrencies does Mercuryo support? Mercuryo supports 50+ cryptocurrencies. For the up-to-date list including asset names, tickers, and networks, see: [Which cryptocurrencies are supported?](https://help.mercuryo.io/hc/en-gb/articles/14495549158045-Which-cryptocurrencies-are-supported) ## What payment cards does Mercuryo accept? Mercuryo accepts Visa and Mastercard (credit and debit). ## Where do I get my Widget ID and Secret? Both are available in the [Mercuryo Dashboard](https://dashboard.mercuryo.io) under **Widgets** → select your widget. ## How do I get the `Sdk-Partner-Token`? Contact your Mercuryo integration manager. This token is required for [silent authentication](./authentication.md) and [API-based KYC](./kyc.md). ## Additional resources * [Help Center for Users](https://help.mercuryo.io/hc/en-gb) * [Help Center for Merchants](https://b2bhelp.mercuryo.io/hc/en-gb) * [Widget Demo with parameters](https://demo-widget.mercuryo.io) # Spend Card Mercuryo Spend Card is a virtual EUR debit Mastercard that users can fund with cryptocurrency and use for payments worldwide. > **Note:** Spend Card is disabled by default. Contact your integration manager to enable it for your widget. --- ## Card Features * Virtual EUR-denominated Mastercard * Fund the card balance by selling crypto (Off-Ramp) or by buying crypto using the Spend Card balance (On-Ramp) * Use for online and in-store payments wherever Mastercard is accepted * In-store payments supported via Apple Pay and Google Pay * 3D Secure protection for online transactions (SMS verification) * One card per user ## User Requirements To issue a Spend Card, the user must: * Have completed KYC * Have a valid mobile phone number (required for 3D Secure SMS) * Be an EEA citizen or EU resident — see the [full list of eligible countries](https://help.mercuryo.io/hc/en-gb/articles/27685426171421-Spend-Card) --- ## What Changes When Enabled When Spend Card is enabled for your widget: * A dedicated **Spend Card** tab appears in the widget for users. * In the On-Ramp flow, users can pay with their Spend Card balance. * In the Off-Ramp flow, **Spend Card** becomes available as a payout destination — users can sell crypto directly to their card. --- ## Widget Parameters Use these parameters to deep-link users directly into Spend Card sections of the widget: | Parameter | Value | Description | |---|---|---| | `payment_method` | `spend_card` | Pre-select Spend Card as the payout method in the Off-Ramp flow | | `fix_payment_method` | `true` | Prevent the user from changing the payment method | | `fiat_currency` | `EUR` | Required for Spend Card — the payout currency is always EUR | | `fix_fiat_currency` | `true` | Lock the fiat currency to EUR | | `show_spend_card_details` | `true` | Open directly to the card details page (for users who already have a card) | **Example — direct to Spend Card Off-Ramp:** ``` https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID &type=sell &fiat_currency=EUR &fix_fiat_currency=true &payment_method=spend_card &fix_payment_method=true ``` See [Widget Parameters](./widget-parameters.md) for the full reference. --- ## End-User Flows ### Card Issuance via Off-Ramp (spend_card_open) 1. The user specifies the crypto amount for a Sell operation. 2. The user selects the Spend Card as the payout method. This option is available if the feature is enabled by the integration manager. 3. The user completes KYC verification, if required. 4. The user provides a phone number for 3D Secure confirmation and enters the code sent to it. 5. The user confirms the transaction and initiates the transfer. 6. After the blockchain transaction is complete, the user receives a success notification. 7. Mercuryo issues the Spend Card after receiving the crypto. 8. Card details are displayed to the user once the card is ready. ### Purchase with Spend Card Balance via On-Ramp (spend) 1. The user specifies the fiat amount for a Buy operation. 2. The user selects **Fiat balance** as the payment method. This option is available if the feature is enabled by the integration manager. When Fiat balance is selected, funds are transferred from the user's bank card to the Spend Card, and the crypto purchase is made from the Spend Card balance. If the user doesn't have a Spend Card yet, one is created with a zero balance — they can top it up afterward. 3. The user completes KYC verification, if required. 4. The user provides a phone number for 3D Secure confirmation and enters the code sent to it. 5. The user confirms the transaction and initiates the transfer. 6. Once the fiat amount is debited from the Spend Card, a crypto withdrawal is initiated for the user. 7. Card details are displayed to the user once the card is ready. # Transaction Types & Statuses Spend Card operations create additional transaction types alongside the standard On-Ramp and Off-Ramp types. For all other types, see [Transaction Types & Statuses](./transactions.md). --- ## How Transactions Are Identified ### In Callbacks Spend Card transactions are identified by the combination of `type` + `payment_method`: | `type` | `payment_method` | Description | |---|---|---| | `sell` | `spend_card_open` | Sell crypto to issue and fund a new Spend Card | | `sell` | `spend_card_topup` | Sell crypto to top up an existing Spend Card | | `buy` | `spend` | Purchase crypto using Spend Card balance | | `fiat_deposit` | `spend_card_topup` | Bank card charge when Spend Card balance is insufficient during a purchase | ### In API Polling When using `GET /v1.6/sdk-partner/transactions`, Spend Card types are not distinguished from standard types. See [Transaction Types & Statuses](./transactions.md#in-api-polling) for the response format. API polling type names for Spend Card operations: | Type | Description | |---|---| | `sell` | Selling crypto to fund a Spend Card — card issuance (`spend_card_open`) or top-up (`spend_card_topup`) | | `buy` | Buying crypto using Spend Card balance (`payment_method=spend`) | --- ## Sell (Spend Card) Triggered when a user sells crypto to issue or top up a Spend Card. Two variants: * **`spend_card_open`** — creates `deposit` first → then creates `sell` for EUR funding and card issuance. On failure: creates `withdraw` to return crypto. * **`spend_card_topup`** — same flow as standard sell, the payout credits EUR to the user's existing Spend Card. **Identify:** Callback: `type=sell`, `payment_method=spend_card_open` or `spend_card_topup` | API: `type=sell` | Status | Type | Description | |---|---|---| | `new` | Intermediate | Sell request created (payout address shown / crypto received, preparing payout) | | `pending` | Intermediate | EUR top-up processing started | | `succeeded` | **Final** | EUR credited to Spend Card (or new card opened and funded) | | `failed` | **Final** | Payout failed (crypto returned to user) | | `cancelled` | **Final** | Cancelled (crypto returned to user) | **State transitions:** | From | To | Trigger | |---|---|---| | `new` | `pending` | processing started | | `pending` | `succeeded` | payout completed | | `pending` | `failed` | processing error | | `pending` | `cancelled` | system cancelled | --- ## Spend Card Purchase Triggered when a user buys crypto using their Spend Card balance. On success, automatically creates a **`withdraw`** transaction. If the Spend Card balance is insufficient, a **`fiat_deposit`** transaction is created automatically to charge the shortfall from the user's bank card. **Identify:** Callback: `type=buy`, `payment_method=spend` | API: `type=buy` | Status | Type | Description | |---|---|---| | `new` | Intermediate | Payment data entered, awaiting processing | | `pending` | Intermediate | Card verification and 3D Secure in progress | | `order_scheduled` | Intermediate | Payment authorized, awaiting KYC verification | | `failed_exchange` | Intermediate | Exchange request failed | | `paid` | **Final** | Transaction successfully completed | | `order_failed` | **Final** | Declined by card issuer / acquirer | | `cancelled` | **Final** | Cancelled (funds returned if charged) | **State transitions:** | From | To | Trigger | |---|---|---| | `new` | `pending` | Spend Card verification started | | `new` | `order_failed` | system error | | `pending` | `order_scheduled` | order queued | | `pending` | `failed_exchange` | exchange error | | `pending` | `order_failed` | payment rejected | | `pending` | `cancelled` | system cancelled | | `order_scheduled` | `paid` | KYC passed or not required | | `order_scheduled` | `failed_exchange` | exchange error | | `order_scheduled` | `cancelled` | system cancelled | | `failed_exchange` | `cancelled` | system cancelled | --- ## Fiat Deposit (Spend Card Top-up) Service transaction automatically created when the Spend Card balance is insufficient during a purchase. Charges the shortfall from the user's bank card. **Identify:** Callback: `type=fiat_deposit`, `payment_method=spend_card_topup` | API: not available | Status | Type | Description | |---|---|---| | `new` | Intermediate | Payment data entered, awaiting processing | | `pending` | Intermediate | Card verification and 3D Secure in progress | | `order_scheduled` | Intermediate | Payment authorized, awaiting KYC verification | | `failed_exchange` | Intermediate | Exchange request failed | | `paid` | **Final** | Top-up successfully completed | | `order_failed` | **Final** | Declined by card issuer / acquirer | | `cancelled` | **Final** | Cancelled (funds returned if charged) | **State transitions:** | From | To | Trigger | |---|---|---| | `new` | `pending` | card verification started | | `new` | `order_failed` | system error | | `pending` | `order_scheduled` | order queued | | `pending` | `failed_exchange` | exchange error | | `pending` | `order_failed` | payment rejected | | `pending` | `cancelled` | system cancelled | | `order_scheduled` | `paid` | KYC passed or not required | | `order_scheduled` | `failed_exchange` | exchange error | | `order_scheduled` | `cancelled` | system cancelled | | `failed_exchange` | `cancelled` | system cancelled | --- ## Transaction Relationships ``` Off-Ramp Spend Card open (sell, payment_method=spend_card_open) ├─ Phase 1: deposit created (user sends crypto) ├─ Phase 2: sell created (EUR funding and card issuance) └─ On failure → creates withdraw (return crypto to user) Off-Ramp Spend Card top-up (sell, payment_method=spend_card_topup) ├─ Phase 1: sell created (payout address shown) ├─ Phase 2: deposit created (user sends crypto) ├─ Phase 3: sell updated for Spend Card top-up └─ On failure → creates withdraw (return crypto to user) Spend Card Purchase (buy, payment_method=spend) ├─ If balance insufficient → creates fiat_deposit (charge shortfall from bank card) └─ On success → creates withdraw (send crypto to user) ``` # Event Callbacks In addition to standard transaction callbacks, Mercuryo sends dedicated Spend Card event callbacks. To configure a separate callback URL for these events, contact your integration manager. For callback setup, signature verification, and retry logic, see [Callbacks & Webhooks](./callbacks.md). In callbacks, Spend Card transactions are identified by `type` + `payment_method`. See [Transaction Types](./spend-card-transactions.md#how-transactions-are-identified) for the reference table. --- ## Card Status Change Sent when the card status changes (e.g., activated, blocked). ```json { "event_id": "80b5ab73-897c-4ec5-aab5-48cc3d0acca8", "event_name": "card_status", "card_id": "0c379cbae24f62922", "user": { "uuid": "b19ea88b-ed9f-46ad-bfd9-62ddf63ffe66" }, "status": "active", "created_at": "2024-06-27 08:26:35", "updated_at": "2024-06-27 08:26:41" } ``` --- ## Crypto Top-Up Sent when the card balance is replenished via the sell flow. ```json { "event_id": "80b5ab73-897c-4ec5-aab5-48cc3d0acca8", "event_name": "crypto_top_up", "card_id": "0c379cbae24f62922", "user": { "uuid": "b19ea88b-ed9f-46ad-bfd9-62ddf63ffe66" }, "sell_request": { "id": "0daa380ba39596745", "merchant_transaction_id": "00357ec76de855485", "status": "succeeded", "fiat_amount": "130.34", "fiat_currency": "EUR", "fee": "2", "crypto_amount": "0.02", "crypto_currency": "BTC" }, "refund_address": "0xc83935B7295FEC64E32B62C33C24dFdfE04092A9", "address": "0xc83935B7295FEC64E32B62C33C24dFdfE04092A9", "sender_address": ["0xc83935B7295FEC64E32B62C33C24dFdfE04092A9"], "card_balance": "130.34", "created_at": "2024-06-27 08:26:35", "updated_at": "2024-06-27 08:26:41" } ``` --- ## Card Transaction Sent when the card is used for a payment. ```json { "event_id": "80b5ab73-897c-4ec5-aab5-48cc3d0acca8", "event_name": "card_transaction", "card_id": "0c379cbae24f62922", "user": { "uuid": "b19ea88b-ed9f-46ad-bfd9-62ddf63ffe66" }, "transaction": { "id": "0daa380ba39596745", "type": "PURCHASE", "status": "AUTHORIZED", "decline_reason": null, "local_amount": "123", "local_currency": "EUR", "amount": "123", "currency": "EUR", "fx_rate": null, "mcc": "288", "counterparty_name": "Some Shop Name" }, "created_at": "2024-06-27 08:26:35", "updated_at": "2024-06-27 08:26:41" } ``` **Card transaction statuses:** `AUTHORIZED`, `CLEARED`, `DECLINED`, `REVERSED`, `INVALID` # Widget Partner API The Widget Partner API provides server-side endpoints for building On-Ramp and Off-Ramp integrations — fetching rates and limits, managing user authentication, submitting KYC documents, and tracking transactions. --- ## Base URLs | Environment | Base URL | |---|---| | Production | `https://api.mercuryo.io/v1.6` | | Sandbox | `https://sandbox-api.mrcr.io/v1.6` | --- ## Authentication Two tokens are used depending on the context: | Token | Header | Obtained from | Used for | |---|---|---|---| | `Sdk-Partner-Token` | `Sdk-Partner-Token: ` | Your integration manager | Partner-level requests: sign-up, sign-in | | `Sdk-User-Token` | `Sdk-User-Token: ` | Returned as `bearer_token` on sign-in | User-specific requests: KYC status, document upload | `Sdk-User-Token` expires in **24 hours** in Production. In Sandbox, it does not expire. --- ## API Sections | Section | Description | |---|---| | **Buy** | Rate fetching and pre-fill data for On-Ramp (crypto purchase) flows | | **Sell** | Rate fetching and pre-fill data for Off-Ramp (crypto sell) flows | | **Transaction Data** | Transaction history, status, and detailed transaction information | | **User Widget Authorization** | Silent user sign-up and sign-in via widget partner token | | **User API Authentication** | User sign-in with OTP verification | | **KYC Verification** | User identity verification flows | | **General** | General-purpose and public data endpoints | --- ## Callback Reference Transaction callbacks (webhooks) sent by Mercuryo to your server are documented separately: * [Callback Reference](/api/callbacks) — payload schema, field descriptions, and examples for all transaction types (buy, sell, invoice, withdraw, Spend Card)