KYC
When a user reaches a transaction limit, they have to pass light KYC or full KYC before the payment can be completed. There are two scenarios:
- KYC Before Payment — optional. You verify the user in advance, in your own interface: full KYC with a SumSub share token, or light KYC with an SSN check for US users. The payment then goes through without sending the user to the Mercuryo widget.
- KYC During Payment — fallback. The payment request returns
403001, and you hand the user over to the Mercuryo widget to complete the verification.
The scenarios work together: if a verification can't be completed in advance, or more data is still needed, the payment request falls back to KYC During Payment.
KYC Before Payment
Before the Apple Pay payment, your backend can sign the user in, check whether verification is needed for a specific quote, collect a compliance questionnaire and submit the verification.
- Availability: like the quote, these requests are not available for users from the United Kingdom and the European Economic Area (EEA).
- Authentication:
Sdk-Partner-Tokenfor sign-in and partner-scoped quotes;Sdk-User-Token(returned by sign-in) for all user-scoped calls. These endpoints don't useX-Signature. - User IP: pass the end user's real IP address in the
Sdk-User-IPheader. Required for sign-in, quotes and questionnaires, and for the share-token submission.
Step 1 — Sign in the user
POST /sdk-partner/native/user/sign-in registers or authenticates the user in your partner account and returns bearer_token — use it as the Sdk-User-Token header in the next steps.
| Header | Required | Description |
|---|---|---|
Sdk-Partner-Token |
Yes | Your partner authentication token. |
Sdk-User-IP |
Yes | The end user's real IP address. |
| Field | Type | Description |
|---|---|---|
email |
string |
Required. The user's email. Use the same email you send in the payment request: the verification is attached to this user and applies to their payments. |
first_name |
string |
Conditional. Required together with last_name when the user doesn't have a name yet (a new user, or an existing one without a name). |
last_name |
string |
Conditional. Required together with first_name, see above. |
birthday |
string |
Optional. YYYY-MM-DD. Recommended for new users for better conversion. |
Example request:
POST /v1.6/sdk-partner/native/user/sign-in
Sdk-Partner-Token: YOUR_SDK_PARTNER_TOKEN
Sdk-User-IP: 203.0.113.42
{
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe",
"birthday": "1995-05-25"
}
Example response:
{
"status": 200,
"data": {
"user_uuid4": "3292c753-6809-492a-9acd-1ccdbf2fa91e",
"bearer_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
Step 2 — Get a quote in the user's context
GET /sdk-partner/native/rate with Sdk-User-Token returns a quote calculated for this user, including kyc_limits — whether the user has to pass full KYC to buy this amount.
| Header | Required | Description |
|---|---|---|
Sdk-User-Token |
Yes | bearer_token from Step 1. |
Sdk-User-IP |
Yes | The end user's real IP address. |
The query parameters are the same as for the quote in Native Payment.
Example request:
GET /v1.6/sdk-partner/native/rate?type=buy&from=EUR&to=BTC&amount=100&payment_method=apple&is_total=true&network=BITCOIN
Sdk-User-Token: eyJ...
Sdk-User-IP: 203.0.113.42
Example response:
{
"status": 200,
"data": {
"quote_token": "eyJ...",
"currency": "BTC",
"amount": "0.00151",
"fiat_amount": "100.00",
"fiat_currency": "EUR",
...
"kyc_limits": false
}
}
quote_token— use it in the questionnaire check and asbuy_tokenin the payment request.
If native Apple Pay can't be used for this user, the quote request is rejected. Don't continue with KYC Before Payment — switch to the Mercuryo widget flow instead, so the purchase isn't lost:
| Code | Meaning |
|---|---|
403034 |
The user's country isn't supported. |
403109 |
Apple Pay is not available for this user. |
Step 3 — Complete the compliance questionnaire
Check whether a questionnaire is required for this quote with POST /sdk-partner/native/questionnaires/check-need.
| Header | Required | Description |
|---|---|---|
Sdk-User-Token |
Yes | bearer_token from Step 1. |
Sdk-User-IP |
Yes | The end user's real IP address. |
| Field | Type | Description |
|---|---|---|
quote_token |
string |
Required. quote_token from Step 2. |
Example request:
{
"quote_token": "eyJ..."
}
Example response:
{
"status": 200,
"data": {
"questionnaires": [
{
"type": "HANFA_EEA",
"version": 1,
"questions": [
{
"key": "SOURCE_OF_FUNDS",
"question_text_en": "Source of funds",
"options": [
{"code": "SALARY", "label_en": "Salary or employment income"},
{"code": "SAVINGS", "label_en": "Savings"}
]
}
]
}
]
}
}
questionnaires: []— no questionnaire is required, go to Step 4.- One or more questionnaires — render each returned questionnaire to the user, then submit the answers with a separate
POST /sdk-partner/native/questionnairesrequest for each questionnaire in the array.
Submit the answers with the same headers as check-need:
| Field | Type | Description |
|---|---|---|
type |
string |
Required. The questionnaire's type, unchanged. |
version |
integer |
Required. The questionnaire's version, unchanged. |
questions |
array |
Required. One entry for every question of the questionnaire. |
questions[].key |
string |
Required. The question's key. |
questions[].codes |
array |
Required. Exactly one option code of this question. |
questions[].text |
string |
Conditional. Only for the citizenship country question: ISO 3166-1 alpha-2 country code, lowercase. |
Example request:
{
"type": "HANFA_EEA",
"version": 1,
"questions": [
{"key": "SOURCE_OF_FUNDS", "codes": ["SALARY"]}
]
}
A successful submission returns 201.
Step 4 — Verify the user
Which verification to run depends on kyc_limits from Step 2 and on the user's country:
| Case | Verification |
|---|---|
kyc_limits: true |
Full KYC — Share token |
kyc_limits: false, US user |
Light KYC with SSN — SSN check (US users) |
kyc_limits: false, other users |
Light KYC — the data from Step 1 is enough, go to Step 5 |
Submit the verification, poll its status while it is under_review, then go to Step 5. The verification methods are described in Verification Methods.
Step 5 — Start the payment
Start the Apple Pay payment and send it with quote_token as buy_token and the same email as in Step 1. If more data is still required, the payment request returns 403001 — continue with KYC During Payment.
Verification Methods
Share token
If you have a SumSub integration and the user has already passed full KYC there, reuse this verification by sharing it with Mercuryo. The user 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 integration manager before proceeding.
Setup (once):
- Conclude a contract with SumSub for the Reusable KYC feature.
- Request a partner token from your integration manager.
- In your SumSub account: Partners → Recipients → Add Recipient → enter the partner token.
- Ask your integration manager to verify the setup.
Requirements:
- The applicant must have completed ID document + liveness (selfie) verification with approved status on your side.
- The email of the SumSub applicant must match the email you used to sign in the user.
- Mercuryo can accept a share token only once per applicant (a SumSub limitation).
- Field sets may differ between the systems — only the document type and country are used for matching.
Generating the share token: call the SumSub API with applicantId and forClientId=Mercuryo:
POST https://api.sumsub.com/resources/accessTokens/-/shareToken
See SumSub sharing docs and the video on how it works.
Then submit the share token to Mercuryo with POST /sdk-partner/native/kyc/crypto/share-token:
| Header | Required | Description |
|---|---|---|
Sdk-User-Token |
Yes | bearer_token from Step 1. |
Sdk-User-IP |
Yes | The end user's real IP address. |
| Field | Type | Description |
|---|---|---|
share_token |
string |
Required. The user's SumSub share token. |
Example request:
{
"share_token": "_act-75e78843-3207-4be6-asdb936-9842ae2a0c71"
}
A 200 response means the token was accepted. Then poll the status with GET /sdk-partner/native/kyc/crypto/status while it is under_review.
If the share token can't be used, the response contains one of these codes:
| Code | Meaning | What to do |
|---|---|---|
409001 |
The user's verification is already under review. | Poll the status. |
409002 |
The user already has an unfinished verification application — e.g. a share token with incomplete data was submitted for this user earlier. A share token can no longer be submitted for this user. | Start the payment and verify the user through KYC During Payment. |
403084 |
Share token import isn't available for the user's country. | Start the payment and verify the user through KYC During Payment. |
403082 |
The user's KYC is already complete. | Start the payment. |
403115 |
A verification is already in progress. | Poll the status. |
403114 |
The user's KYC verification is failed. | Stop — the user can't pay. |
SSN check (US users)
For US users, light KYC includes the SSN. Submit the user's data and SSN with POST /sdk-partner/native/kyc/ssn.
| Header | Required | Description |
|---|---|---|
Sdk-User-Token |
Yes | bearer_token from Step 1. |
| Field | Type | Description |
|---|---|---|
first_name |
string |
Required. The user's legal first name. |
last_name |
string |
Required. The user's legal last name. |
birthday |
string |
Required. YYYY-MM-DD. |
ssn |
string |
Required. 9-digit US Social Security Number, digits only. |
address1 |
string |
Required. Street address. |
address2 |
string |
Optional. Apartment, suite, unit, etc. |
city |
string |
Required. City. |
state |
string |
Required. Two-letter US state code. |
zip |
string |
Required. 12345 or 12345-6789. |
Example request:
{
"first_name": "John",
"last_name": "Doe",
"birthday": "1990-05-20",
"ssn": "123456789",
"address1": "1 Infinite Loop",
"address2": "Apt 4",
"city": "Cupertino",
"state": "CA",
"zip": "95014"
}
A 200 response means the check is accepted for processing. Then poll the status with GET /sdk-partner/native/kyc/ssn/status while it is under_review.
If the SSN check can't be started, the response contains one of these codes:
| Code | Meaning | What to do |
|---|---|---|
400027 |
The SSN check request was rejected. | Verify the user with full KYC — a share token or KYC During Payment. |
403116 |
The user's SSN check is failed. | Verify the user with full KYC — a share token or KYC During Payment. |
403082 |
The user's KYC is already complete, the SSN check isn't needed. | Start the payment. |
403115 |
A verification is already in progress. | Poll the status. |
403114 |
The user's full KYC verification is failed. | Stop — the user can't pay. |
Verification status
GET /sdk-partner/native/kyc/{feature}/status returns the verification status.
| Header | Required | Description |
|---|---|---|
Sdk-User-Token |
Yes | bearer_token from Step 1. |
| Parameter | Required | Description |
|---|---|---|
feature (path) |
Yes | crypto — full KYC (share token); ssn — light KYC with SSN. |
Example response:
{
"status": 200,
"data": {
"status": "complete"
}
}
| Status | Meaning | What to do |
|---|---|---|
under_review |
The verification is in progress. | Keep polling. |
complete |
The verification is passed. | Start the payment. |
failed_attempt / incomplete |
The verification isn't finished. | Start the payment — the missing data is collected during the payment, see KYC During Payment. |
failed |
The verification is failed. | ssn: verify the user with full KYC — a share token or KYC During Payment.crypto: stop — the user can't pay. |
KYC During Payment
When POST /sdk-partner/native-mobile-pay/mobile-pay can't be completed without user verification, it returns code 403001. You hand the user over to the full Mercuryo widget, so the transaction isn't lost.
{
"status": 403,
"code": 403001,
"message": "KYC verification needed.",
"data": {
"id": "0fcabca3a43709674",
"status": "pending",
"init_token": "0fcabca4c19121633",
"init_token_type": "type_sdk_silent_sub"
}
}
data.status is pending for non-US users and order_failed for US users — see below.
The response contains a short-lived init_token and init_token_type. They pre-authenticate the user in the Mercuryo widget, so the user goes straight to identity verification without entering their details again.
Non-US user
- A 30-minute hold is placed on the user's funds. The
403001response returns the transaction inpending; it then moves toorder_scheduledand waits there while the user passes KYC. - If the payment isn't completed within 30 minutes of its creation, the transaction is cancelled and the hold is released.
US user
- No hold is placed on the user's funds.
- The transaction fails immediately with status
order_failed. The403001response and the callback both carryorder_failed. - After completing KYC in the widget, the user starts a new Apple Pay payment directly in the widget — the original transaction is not resumed.
Redirecting the User
Redirect the user to the Mercuryo widget immediately after the 403001 response, with your widget_id and the token values from the response:
https://exchange.mercuryo.io/?widget_id=YOUR_WIDGET_ID&init_token=INIT_TOKEN_VALUE&init_token_type=INIT_TOKEN_TYPE_VALUE
| Parameter | Source |
|---|---|
widget_id |
Your Widget ID from the Mercuryo Dashboard |
init_token |
data.init_token from the 403001 response |
init_token_type |
data.init_token_type from the 403001 response |
For US users, the widget must be opened with a Widget Signature v2. In addition to widget_id, init_token and init_token_type, the redirect URL must also include signature, address, ip_address and, if used, merchant_transaction_id — the parameters the signature is calculated from. See the Widget Signature guide for how to generate the signature.
The user lands on the Mercuryo widget with their transaction data pre-filled, completes the required steps (e.g., uploads documents for KYC) and finalizes the purchase.
Outcome
Non-US user
| Scenario | Result |
|---|---|
| The user completes KYC within 30 minutes | The transaction is finalized automatically; the final status is delivered via the callback |
| The user doesn't complete KYC within 30 minutes | The transaction is cancelled and the hold on the user's funds is released |
US user
| Scenario | Result |
|---|---|
| Any case | The transaction has already failed (order_failed) at the moment of the 403001 response; your backend receives a callback for the failed transaction. Once KYC is completed, the user starts a new payment attempt from the widget |