# Get KYC Widget URL
Source: https://docs.payments.sardine.ai/api_reference/identity/consent-widget/get-kyc-widget-url
/api_reference/identity.yaml post /identity/consents/widget
Generates a hosted widget URL that you redirect your user to (or embed as an iframe) to complete KYC or share an existing verified identity.
**Flows**
- `kyc_input` *(default)* — The user verifies their identity from scratch in the Sardine
hosted widget (passport, driver's license, liveness check, etc.). Use this for new users
or when you need specific additional verification steps.
- `kyc_sharing` — The user consents to share an identity already verified by another Sardine
partner. This produces near-instant approval for returning Sardine users.
**Scopes**
Control which data the widget will collect or share:
- `profile` — Basic personal information (name, DOB, address, email, phone) - `doc_kyc` — Government ID document scan + liveness check - `liveness` — Liveness check only - `ssn` — Social Security Number (US users)
**After the Widget**
When the user completes the flow the widget redirects to `successUrl`. Call `GET /identity/entities/{customerId}` to retrieve the verified data.
# Create Customer
Source: https://docs.payments.sardine.ai/api_reference/identity/customer-management/create-customer
/api_reference/identity.yaml post /identity/entities
Creates a new identity record for a customer. The phone number must be unique per client. Returns the Sardine `customerId` which is used in all subsequent identity API calls.
# Get Customer Identity
Source: https://docs.payments.sardine.ai/api_reference/identity/customer-management/get-customer-identity
/api_reference/identity.yaml get /identity/entities/{customerId}
Returns the verified identity data for a customer, including profile, document data, and document images. The response is gated by the user's consent:
- `profile` fields are returned when the `profile` scope was consented - `documentKyc` and `documentData` are returned when the `doc_kyc` scope was consented
If the identity belongs to another client, a valid `kyc_sharing` consent record (consented and not revoked) must exist.
# Search Customer
Source: https://docs.payments.sardine.ai/api_reference/identity/customer-management/search-customer
/api_reference/identity.yaml post /identity/entities/search
Looks up a customer by phone number. Use this to check whether a customer already exists before calling `POST /identity/entities`.
# Send bank information
Source: https://docs.payments.sardine.ai/api_reference/nft/banks/send-bank-information
/api_reference/nft.yaml get /banks/transactions
Simply populate the appropriate field in the plaidData object. Make sure to fill in the correct field corresponding to the correct Plaid API endpoint. For example, if you call the Plaid transactions API, you should set plaidData.transactionsResponse to be the JSON string containing the response. Note that the response fields in plaidData are all optional and you may populate as many or few as you would like, as long as an authResponse is sent at least once per account.
Important note: You must provide an authResponse at least once per customer bank account so that we can store the mapping from bank account to Plaid account ID.
# Create Payout
Source: https://docs.payments.sardine.ai/api_reference/nft/create-payout
/api_reference/nft.yaml post /payouts
Create a Payout where Sardine will send crypto to specified wallet(s) in specified cryptocurrencies.
A Payout can be created associated with a corresponding fiat order (where Sardine has also done the fiat processing) or without one (where Sardine does not do the fiat component).
To trigger Payouts without the fiat processing being done by Sardine, contact your Integration team.
# Get Customers
Source: https://docs.payments.sardine.ai/api_reference/nft/customers/get-customers
/api_reference/nft.yaml get /customers
Fetches list of all customers. Can be filtered by passing `customerId`
# Post Customer Information
Source: https://docs.payments.sardine.ai/api_reference/nft/customers/post-customer-information
/api_reference/nft.yaml post /customers
Send User information to Sardine to store theie information and create a risk profile
# Fetch Payout Information
Source: https://docs.payments.sardine.ai/api_reference/nft/fetch-payout-information
/api_reference/nft.yaml get /payouts/{payout_id}
Fetch payouts information by `order_id` or `payout_id`
1. By `order_id`
Make a GET request to /payouts/order_id={}
2. By `payout_id`
Make a GET request to /payouts/{payout_id}
# Get fiat currencies
Source: https://docs.payments.sardine.ai/api_reference/nft/get-fiat-currencies
/api_reference/nft.yaml get /fiat-currencies
List of fiat currencies supported by Sardine
# Get Geocoverage
Source: https://docs.payments.sardine.ai/api_reference/nft/get-geocoverage
/api_reference/nft.yaml get /geo-coverage
Get list of regions where Sardine is supported, separated by county and state regions
# Get Order(s)
Source: https://docs.payments.sardine.ai/api_reference/nft/get-orders
/api_reference/nft.yaml get /orders
Fetch information about an Order once its completed
The `status` of an Order can be one of the following
`Draft` - This is an open or ongoing order
`Expired`* - The user didn't complete the transaction within the `expiration` time
`Processed`* - The payment has been completed.
`Declined`* - The transacation was declined, due to payment method issues
`Cancelled`* - The transaction did not complete, which could be for a number of reasons, as outlined in the reason codes below.
`Complete`* - The payment is complete and the crypto has been delivered to the user's wallet. A `txHash` will be present to denote successful on chain settlement.
`Refunded` - The User's payment has been refunded to their original payment method
### For Smart Contract Integrations
Orders might end up in `Cancelled` state, due to a number of reasons, which Sardine will share through a `reasonCode` parameter as part of an `error` object in the Order. After an Order has been Cancelled, it will move to the `Refunded` state
The following reason codes might appear
`NETF` - Network Failure
`PRCC` - Price Change beyond threshold
`RISD` - Risk Decline
`NA` - NFT Not Available
Events marked with an * can also be captured via event handlers on the frontend
### Fetching Orders
Full list of Orders is obtained by making a call to the endpoint with no filters.
You have multiple options of filtering orders
1. Filtering by `orderId`
If a `redirect_url` was passed to the Sardine checkout (e.g. https://crytpoapp.com"), when then transaction is completed, Sardine will redirect the user to this url with an `order_id` appended i.e. https://cryptoapp.com?order_id=73103-erhed-317313
This `order_id` can then be used as a filter on this endpoint, this form
https://api.sandbox.sardine.ai/v1/orders/3103-erhed-317313
2. Filtering by `referenceId`
If a `referenceId` was passed in the call to create the `clientToken` for this call, it can now be used to filter for Orders that were created then.
3. Filtering by `startDate` and `endDate`
If `startDate` or `endDate` are passed in YYYY-MM-DD format, the list of Orders will be filtered to those that were created in this range
4. Filtering by `externalUserId`
# Fetch KYC
Source: https://docs.payments.sardine.ai/api_reference/nft/identity/fetch-kyc
/api_reference/nft.yaml get /identityDocuments/verifications/{id}
Retrieve result of document verification
# Add new Bank Account using aggregator token
Source: https://docs.payments.sardine.ai/api_reference/nft/payment-method/add-new-bank-account-using-aggregator-token
/api_reference/nft.yaml post /payment-method/ach
Add new Bank Account to User.
Currently, this can be done in one of two ways
1. Using a processor token from bank aggregator service like Plaid
2. Sending information about the user's bank account collected through a Plaid like service
# Delete Bank Account
Source: https://docs.payments.sardine.ai/api_reference/nft/payment-method/delete-bank-account
/api_reference/nft.yaml delete /payment-method/ach
Delete bank account with the corresponding bank account ID
# Get Bank Account
Source: https://docs.payments.sardine.ai/api_reference/nft/payment-method/get-bank-account
/api_reference/nft.yaml get /payment-method/ach
Get information about Bank Account object
# Post Client Token
Source: https://docs.payments.sardine.ai/api_reference/nft/post-client-token
/api_reference/nft.yaml post /auth/client-tokens
Exchange your `clientId` and `clientSecret` for a `clientToken` that can be be used in frontend code. `clientToken` will be unusable after the `expiresAt` time, which will normally be after 30 min.
To try the request in the mock server on the right, substitute username for clientId and password for clientSecre
# Update NFT Status
Source: https://docs.payments.sardine.ai/api_reference/nft/update-nft-status
/api_reference/nft.yaml get /orders/events
# Update Order
Source: https://docs.payments.sardine.ai/api_reference/nft/update-order
/api_reference/nft.yaml post /orders/events
# Get Enabled Tokens
Source: https://docs.payments.sardine.ai/api_reference/onramp/coverage/get-enabled-tokens
/api_reference/onramp.yaml get /enabled-tokens
Fetch list of tokens supported.
# Get Fiat Currencies
Source: https://docs.payments.sardine.ai/api_reference/onramp/coverage/get-fiat-currencies
/api_reference/onramp.yaml get /fiat-currencies
List of fiat currencies supported by Sardine
# Get Geocoverage
Source: https://docs.payments.sardine.ai/api_reference/onramp/coverage/get-geocoverage
/api_reference/onramp.yaml get /geo-coverage
Get list of regions where Sardine is supported, separated by county and state regions
# Get Supported Tokens
Source: https://docs.payments.sardine.ai/api_reference/onramp/coverage/get-supported-tokens
/api_reference/onramp.yaml get /supported-tokens
Fetch list of tokens supported.
# Create Offramp Order
Source: https://docs.payments.sardine.ai/api_reference/onramp/order-execution/create-offramp-order
/api_reference/onramp.yaml post /offramp
This endpoint is used to initiate an offramp transaction, converting crypto to fiat and transferring funds to the user's connected payment method.
# Create Onramp Order
Source: https://docs.payments.sardine.ai/api_reference/onramp/order-execution/create-onramp-order
/api_reference/onramp.yaml post /onramp
This endpoint is used to initiate an onramp transaction, converting fiat to crypto and delivering the crypto to a destination wallet.
# Get an Order by ID
Source: https://docs.payments.sardine.ai/api_reference/onramp/order/get-an-order-by-id
/api_reference/onramp.yaml get /orders/{orderId}
Fetch information about an Order once its completed
The `status` of an Order can be one of the following
`Draft` - This is an open or ongoing order
`Processed`* - The payment has been completed.
`Declined`* - The transacation was declined, due to payment method issues
`UserCustody` - Crypto purchased for user but is in Sardine's custodied wallet
`Complete` - The payment is complete and the crypto has been delivered to the user's wallet. A `txHash` will be present to denote successful on chain settlement.
`Expired` - Order expired before execution
Events marked with an * can also be captured via event handlers on the frontend
### Fetching Orders
Full list of Orders is obtained by making a call to the endpoint with no filters.
You have multiple options of filtering orders
**1. Filtering by `order_id`**
If a `redirect_url` was passed to the Sardine checkout (e.g. https://crytpoapp.com"), when then transaction is completed, Sardine will redirect the user to this url with an `order_id` appended i.e. https://cryptoapp.com?order_id=73103-erhed-317313
This `order_id` can then be used as a filter on this endpoint
E.g. `/v1/orders/491c113c-4485-47cd-b011-252068b753dc`
**2. Filtering by `referenceId`**
If a `referenceId` was passed in the call to create the `clientToken` for this call, it can now be used to filter for Orders that were created then.
E.g. `/v1/orders?referenceId=42ead95db5aeb6c`
**3. Filtering by `externalUserId`**
If a `externalUserId` was passed in /auth/client-tokens, it can be used to filter for Orders with that ID. This is useful for associating transactions with a user
**3. Filtering by `startDate` and `endDate`**
If `startDate` or `endDate` are passed in YYYY-MM-DD format, the list of Orders will be filtered to those that were created in this range
E.g. `/v1/orders?startDate=2022-08-01&endDate=2022-08-15`
`paymentStatus` field within the `payment` object in the response can be
- Draft
- Pending
For bank transfers, the value could be
- Sent
- Complete
- Returned
- Failed
For card transactions, the value can be
- Authorized
- Captured
- Declined
- Pending3DS
- Failure3DS
- Voided
# Get User Orders
Source: https://docs.payments.sardine.ai/api_reference/onramp/order/get-user-orders
/api_reference/onramp.yaml get /orders
This endpoint retrieves the list of orders placed by the user, with optional filtering based on specific query parameters like userId.
# Get Add Fiat Account Widget URL
Source: https://docs.payments.sardine.ai/api_reference/onramp/payment-method/get-add-fiat-account-widget-url
/api_reference/onramp.yaml get /payment-methods/widgetUrl
This endpoint returns a URL where users can be redirected to connect external fiat payment methods, including credit/debit cards, bank accounts, and SEPA.
# Get Payment Method information
Source: https://docs.payments.sardine.ai/api_reference/onramp/payment-method/get-payment-method-information
/api_reference/onramp.yaml get /payment-methods
Get information about User's payment methods
# Get Quote
Source: https://docs.payments.sardine.ai/api_reference/onramp/quote/get-quote
/api_reference/onramp.yaml get /quotes
Sardine returns a quote on the amount of cryptocurrency that can be purchased or sold (for off-ramp), along with the associated fees, which are as follows
- Network fee - Also known as "gas", paid out to faciliate and validate the transaction
- Processing fee: Used to cover Sardine's cost of money movement, fraud check and compliance
# Create Support Ticket
Source: https://docs.payments.sardine.ai/api_reference/onramp/support/create-support-ticket
/api_reference/onramp.yaml post /supportTickets
This endpoint allows users to file a support ticket with Sardine by providing their contact information and details about the issue.
# Get Client Token
Source: https://docs.payments.sardine.ai/api_reference/onramp/user-onboarding/get-client-token
/api_reference/onramp.yaml post /auth/client-tokens
Exchange your `clientId` and `clientSecret` for a `clientToken` that can be be used in frontend code. `clientToken` will be unusable after the `expiresAt` time, which will normally be after 30 min.
To try the request in the mock server on the right, substitute username for clientId and password for clientSecret.
If `reference_id` is passed, it will be passed to the Order object upon when the transaction happens. It can then be used as a filter in the /orders endpoint
# Get Customers
Source: https://docs.payments.sardine.ai/api_reference/onramp/user-onboarding/get-customers
/api_reference/onramp.yaml get /customers
Fetches list of all customers. Can be filtered by passing `customerId`
# Get KYC Widget URL
Source: https://docs.payments.sardine.ai/api_reference/onramp/user-onboarding/get-kyc-widget-url
/api_reference/onramp.yaml get /kyc/widgetUrl
This endpoint returns a URL that redirects the user to perform document-based KYC verification.
# Post Customer Information
Source: https://docs.payments.sardine.ai/api_reference/onramp/user-onboarding/post-customer-information
/api_reference/onramp.yaml post /customers
Send User information to Sardine to store theie information and create a risk profile
# Supported Assets
Source: https://docs.payments.sardine.ai/coverage/supported_assets
Sardine supports a focused set of digital assets for on-ramp and off-ramp flows, routed through our liquidity provider.
Our current asset coverage is scoped to the tokens supported by our active liquidity provider. Assets marked as **Yes** under *Instant Settlement* can be funded or settled in near real time, reducing risk and improving user experience.
The list below reflects our current default coverage. The definitive, up-to-date list for your account is always the response from [`GET /v1/supported-tokens`](/api_reference), since availability can vary by account configuration and liquidity provider routing.
###
| Token | Networks | Instant Settlement |
| ----- | ------------------ | ------------------ |
| BTC | Bitcoin | Yes |
| ETH | Ethereum | Yes |
| USDC | Ethereum, Arbitrum | Yes |
| USDT | Ethereum, Tron | Yes |
### Key Highlights
* **Stablecoin coverage:** USDC (Ethereum, Arbitrum) and USDT (Ethereum, Tron) are supported with instant settlement.
* **Bitcoin and Ethereum:** Core assets Bitcoin and Ethereum are available for both on- and off-ramps.
* **Reduced coverage from prior provider:** We recently consolidated onto a single liquidity provider, which narrowed asset coverage compared to previous versions of this page. If you relied on a token no longer listed here, reach out to your Sardine contact.
* **Scalable coverage:** Sardine continuously evaluates and adds assets based on demand and compliance requirements.
# Geographic Coverage
Source: https://docs.payments.sardine.ai/coverage/supported_geos
On-ramp and off-ramp coverage across 80+ countries, with compliance and fraud protections built in.
Sardine provides global reach for payments, supporting both consumer and enterprise flows. Our coverage spans North America, Latin America, Europe, Africa, and Asia. We support local regulatory requirements in each market, ensuring secure and compliant transactions.
Partners can enable:
* **On-Ramp:** Allow users to buy crypto or fund stablecoin wallets with local payment methods.
* **Off-Ramp:** Let users or businesses cash out to bank accounts or cards in local currencies.
* **NFT Checkout:** Direct purchase of NFTs with cards or bank accounts where supported
### Supported Markets
| Country | Country Code | On-Ramp | Off-Ramp | NFT Checkout |
| ---------------------------- | ------------ | --------------------- | --------------------- | --------------------- |
| United States (Excl. NY, AK) | US | | | |
| United States (NY, AK) | US | | | |
| Albania | AL | | | |
| Angola | AO | | | |
| Austria | AT | | | |
| Barbados | BB | | | |
| Belgium | BE | | | |
| Belize | BZ | | | |
| Benin | BJ | | | |
| Bolivia | BO | | | |
| Brazil | BR | | | |
| Bulgaria | BG | | | |
| Cambodia | KH | | | |
| Cayman Islands | KY | | | |
| Chile | CL | | | |
| Colombia | CO | | | |
| Comoros | KM | | | |
| Costa Rica | CR | | | |
| Croatia | HR | | | |
| Cyprus | CY | | | |
| Czechia | CZ | | | |
| Denmark | DK | | | |
| Dominica | DM | | | |
| Dominican Republic | DO | | | |
| Ecuador | EC | | | |
| Egypt | EG | | | |
| El Salvador | SV | | | |
| Equatorial Guinea | GQ | | | |
| Estonia | EE | | | |
| Faroe Islands | FO | | | |
| Finland | FI | | | |
| France | FR | | | |
| French Guiana | GF | | | |
| Germany | DE | | | |
| Greece | GR | | | |
| Guinea | GN | | | |
| Guinea-Bissau | GW | | | |
| Guyana | GY | | | |
| Haiti | HT | | | |
| Honduras | HN | | | |
| Hungary | HU | | | |
| Iceland | IS | | | |
| Indonesia | ID | | | |
| Ireland | IE | | | |
| Israel | IL | | | |
| Italy | IT | | | |
| Jamaica | JM | | | |
| Japan | JP | | | |
| Kyrgyzstan | KG | | | |
| Laos | LA | | | |
| Latvia | LV | | | |
| Liechtenstein | LI | | | |
| Lithuania | LT | | | |
| Luxembourg | LU | | | |
| Madagascar | MG | | | |
| Malaysia | MY | | | |
| Maldives | MV | | | |
| Malta | MT | | | |
| Mauritania | MR | | | |
| Mexico | MX | | | |
| Mongolia | MN | | | |
| Mozambique | MZ | | | |
| Netherlands | NL | | | |
| Norway | NO | | | |
| Oman | OM | | | |
| Panama | PA | | | |
| Paraguay | PY | | | |
| Peru | PE | | | |
| Philippines | PH | | | |
| Poland | PL | | | |
| Portugal | PT | | | |
| Romania | RO | | | |
| Saint Kitts and Nevis | KN | | | |
| Saint Martin | MF | | | |
| Saudi Arabia | SA | | | |
| Seychelles | SC | | | |
| Singapore | SG | | | |
| Slovakia | SK | | | |
| Slovenia | SI | | | |
| South Korea | KR | | | |
| Spain | ES | | | |
| Sri Lanka | LK | | | |
| Sweden | SE | | | |
| Switzerland | CH | | | |
| Tanzania | TZ | | | |
| Thailand | TH | | | |
| Trinidad and Tobago | TT | | | |
| Turkey | TR | | | |
| United Arab Emirates | AE | | | |
| United Kingdom | GB | | | |
| Uruguay | UY | | | |
| Uzbekistan | UZ | | | |
| Vanuatu | VU | | | |
| Vietnam | VN | | | |
### Key Highlights
* **United States coverage:** Available in all states, with specific restrictions in New York and Alaska.
* **Europe:** Broad SEPA coverage for both on- and off-ramps.
* **Emerging markets:** Expanding presence in Latin America, Africa, and Asia to support global crypto and stablecoin adoption.
* **Scalable roadmap:** Sardine continuously evaluates and adds new countries based on partner demand and regulatory clearance.
# Payment Methods
Source: https://docs.payments.sardine.ai/coverage/supported_payment_methods
Global coverage across cards, bank transfers, and wallets, all optimized with Sardine’s risk and compliance platform.
Sardine supports a wide range of payment methods so you can meet users wherever they are. You can enable all options or configure only the ones that make sense for your business. Our platform manages fraud, compliance, and authorization for every transaction. The result is higher approval rates, lower fraud, and less friction for your users.
### Support
| Payment Method | Region | On-Ramp | Off-Ramp |
| --------------------------- | ------ | --------------------- | --------------------- |
| Instant ACH / Bank Transfer | US | | |
| Credit / Debit Card | Global | | |
| Apple Pay | Global | | |
| Google Pay | Global | | |
| SEPA | EU | ✓ | |
### Instant ACH and Bank Transfer
Users in the United States can link a bank account and fund transactions instantly with Instant ACH, or use a standard ACH transfer that settles in two to three business days. All connections are made securely through trusted bank-linking providers.
Bank transfers are a cost-effective funding method and give users confidence by keeping full control of their accounts.
### Credit and Debit Cards
Global card network support allows users to fund transactions quickly and securely. Sardine operates as the Merchant of Record, managing authorization, fraud checks, and disputes. This reduces false declines and improves acceptance rates.
Fees vary by market and card type.
### Apple Pay and Google Pay
Users can complete purchases instantly using Apple Pay or Google Pay. These wallet options remove the need to re-enter payment details, create a smoother mobile experience, and increase conversion at checkout.
### SEPA Bank Transfer
For users in the European Union, SEPA transfers provide a reliable way to fund accounts or withdraw directly to their bank. Sardine supports both on-ramp and off-ramp SEPA flows with built-in KYC and fraud protection.
# API Integration
Source: https://docs.payments.sardine.ai/integration_guides/identity/api_integration
Full implementation reference for the Sardine Universal Identity API
## Authentication
All Identity API endpoints use HTTP Basic Auth. Pass your `clientId` as the username and `clientSecret` as the password on every request.
```bash theme={null}
curl https://api.sandbox.sardine.ai/v1/identity/... \
-u "$CLIENT_ID:$CLIENT_SECRET"
```
Never make these calls from a browser or mobile client. Your `clientSecret` must remain server-side only.
***
## Step 1 — Check if the customer exists
Before creating a new customer, check whether one already exists for the user's phone number to avoid duplicate records.
```bash theme={null}
curl -X POST https://api.sandbox.sardine.ai/v1/identity/entities/search \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+14155551234" }'
```
If `customerId` is returned, the customer already exists — skip to [Step 3](#step-3--generate-a-widget-url). If the response is empty, proceed to Step 2.
***
## Step 2 — Create the customer
Register the user with their phone number. The `customerId` returned here is your durable reference to this user in all subsequent API calls.
```bash theme={null}
curl -X POST https://api.sandbox.sardine.ai/v1/identity/entities \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+14155551234" }'
```
```json theme={null}
{
"customerId": "3f8c1a22-1234-4abc-9def-000000000001",
"createdAt": "2026-06-01T10:00:00Z"
}
```
Store `customerId` against your user record in your own database.
***
## Step 3 — Generate a widget URL
Call this endpoint from your backend to get a hosted widget URL for the user. Choose the right `flow` and `scope` for your use case.
```bash theme={null}
curl -X POST https://api.sandbox.sardine.ai/v1/identity/consents/widget \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"customerId": "3f8c1a22-1234-4abc-9def-000000000001",
"successUrl": "https://yourapp.com/kyc/success",
"manualKycUrl": "https://yourapp.com/kyc/manual",
"scope": ["profile", "doc_kyc"],
"flow": "kyc_input"
}'
```
```json theme={null}
{
"widgetUrl": "https://identity.sardine.ai/?client_token=abc123&consent_id=xyz789&success_url=..."
}
```
### Choosing a flow
| Flow | When to use |
| ------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `kyc_input` | New users, or when you need the user to verify additional scopes not yet on file |
| `kyc_sharing` | User has already been verified by another Sardine partner and you want them to consent to share that identity with you |
### Choosing scopes
| Scope | What it collects |
| ---------- | -------------------------------------------------------------- |
| `profile` | Name, date of birth, address, email, phone number |
| `doc_kyc` | Government ID scan (front + back) and biometric liveness check |
| `liveness` | Liveness check only |
| `ssn` | Social Security Number (US users, when required) |
Pass multiple scopes together: `["profile", "doc_kyc"]` is the standard full-KYC combination.
### manualKycUrl
Provide a `manualKycUrl` as a fallback destination if the user cannot be verified automatically (e.g. document scan quality is too low). This is optional but recommended for production.
***
## Step 4 — Redirect the user
Return the `widgetUrl` to your frontend and redirect the user to it, or embed it in an iframe.
```js theme={null}
// Server response to your frontend
res.json({ widgetUrl: data.widgetUrl });
// Frontend redirect
window.location.href = widgetUrl;
```
The widget handles all verification steps. When the user completes (or abandons) the flow, they are redirected to your `successUrl`.
***
## Step 5 — Retrieve the verified identity
After the user returns to your `successUrl`, call this endpoint from your backend to retrieve the verified data.
```bash theme={null}
curl https://api.sandbox.sardine.ai/v1/identity/entities/3f8c1a22-1234-4abc-9def-000000000001 \
-u "$CLIENT_ID:$CLIENT_SECRET"
```
### Response
```json theme={null}
{
"profile": {
"userId": "3f8c1a22-1234-4abc-9def-000000000001",
"clientId": "your-client-id",
"consentId": "b1c2d3e4-5678-4abc-9def-000000000002",
"consentedAt": "2026-06-01T10:30:00Z",
"revokedAt": null,
"primaryIdentity": true,
"fullName": "Jane Smith",
"dateOfBirth": "1990-06-15",
"emailAddress": "jane@example.com",
"phoneNumber": "+14155551234",
"address": {
"street": "123 Main St",
"city": "San Francisco",
"region": "CA",
"postalCode": "94105"
}
},
"documentData": {
"documentType": "DRIVERS_LICENSE",
"documentNumber": "D1234567",
"dateOfBirth": "1990-06-15",
"expiryDate": "2028-06-15",
"issuingCountry": "US",
"firstName": "Jane",
"lastName": "Smith"
},
"documentKyc": {
"front": "",
"back": "",
"selfie": ""
}
}
```
### What's included per scope
| Field | Requires scope |
| ---------------------- | -------------- |
| `profile` | `profile` |
| `documentData` | `doc_kyc` |
| `documentKyc` (images) | `doc_kyc` |
Fields outside the consented scopes are returned as `null`.
### Consent states
If the identity belongs to another client (i.e. `kyc_sharing` flow), the API enforces consent state:
| State | Behaviour |
| ------------------------------------------- | ------------------------------ |
| Consent not found | `400 Consent not found` |
| Consent pending (user has not approved yet) | `400 Pending user consent` |
| Consent revoked | `400 Consent has been revoked` |
| Consent active | `200` with full data |
***
## Error reference
| Status | Message | Resolution |
| ------ | ------------------------------------- | --------------------------------------------------------------------------------- |
| `400` | `Phone number is required` | Include `phoneNumber` in the request body |
| `400` | `Customer already exists` | Use the existing `customerId` from `/identity/entities/search` |
| `400` | `Customer ID is required` | Include `customerId` in the widget request |
| `400` | `Customer not found for customer ID` | Verify the `customerId` was created by this client |
| `400` | `Invalid scope` | Use one of: `profile`, `doc_kyc`, `liveness`, `ssn` |
| `400` | `User already consented to this flow` | The user has already completed `kyc_input` for this client |
| `401` | Unauthorized | Check that `clientId` and `clientSecret` are correct and being sent as Basic Auth |
***
## Go to production
1. Test the full flow end-to-end in sandbox.
2. Confirm identity data is retrieved correctly after widget completion.
3. Contact your Sardine integration contact to complete the review.
4. Swap `api.sandbox.sardine.ai` → `api.sardine.ai` and replace with production credentials.
# Overview
Source: https://docs.payments.sardine.ai/integration_guides/identity/overview
How the Sardine Universal Identity integration works end-to-end
The Sardine Identity integration lets you collect and reuse verified user identities through a hosted widget backed by a simple server-side API. Your backend holds the credentials; your frontend never touches them.
There are two flows:
* **`kyc_input`** — A new user verifies their identity for the first time. The widget collects personal information, captures a government ID, and runs a liveness check.
* **`kyc_sharing`** — A returning Sardine network user has already been verified by another partner. They consent to share their existing verified identity with you. This takes seconds.
Both flows produce the same result: a verified identity you can retrieve via `GET /identity/entities/{customerId}`.
## Architecture
```mermaid theme={null}
sequenceDiagram
actor U as User
participant C as Your Backend
participant S as api.sardine.ai
participant W as identity.sardine.ai
note over C,S: Server-side setup
C->>S: POST /identity/entities (phoneNumber)
S-->>C: { customerId }
note over C,W: Generate widget URL
C->>S: POST /identity/consents/widget (customerId, scope, flow)
S-->>C: { widgetUrl }
C-->>U: Redirect user to widgetUrl
note over U,W: User completes verification
U->>W: Verifies identity (ID scan, liveness, etc.)
W-->>U: Redirects to successUrl
note over C,S: Retrieve verified data
C->>S: GET /identity/entities/{customerId}
S-->>C: { profile, documentData, documentKyc }
```
## Key Concepts
**customerId** — A Sardine-assigned UUID created when you register a user via `POST /identity/entities`. Store this against your own user record; you'll use it for all subsequent API calls.
**scope** — Controls which data the widget collects or shares. Common combinations:
* `["profile"]` — Personal info only (name, DOB, address, email, phone)
* `["profile", "doc_kyc"]` — Full KYC with government ID and liveness check
* `["doc_kyc"]` — Document scan only (for users whose basic profile is already on file)
**flow** — `kyc_input` for new verifications, `kyc_sharing` for reusing an existing verified identity from the Sardine network.
**widgetUrl** — A one-time URL generated server-side. Redirect the user to this URL or embed it in an iframe. The URL encodes a session token and consent ID — it does not need to be kept secret but should be used promptly.
## Next Steps
* [Quickstart](/integration_guides/identity/quickstart) — Go live in four steps
* [API Integration](/integration_guides/identity/api_integration) — Full implementation reference
# Quickstart
Source: https://docs.payments.sardine.ai/integration_guides/identity/quickstart
Get Sardine Universal Identity running in four steps
## 1. Get your credentials
Contact your Sardine integration contact to receive a `clientId` and `clientSecret` for the sandbox environment. All API calls use HTTP Basic Auth with these credentials.
Never expose your `clientSecret` in frontend code. All calls to `api.sardine.ai` must be made from your backend.
## 2. Create a customer
Register the user in the Sardine system using their phone number. Store the returned `customerId` against your own user record.
```bash theme={null}
curl -X POST https://api.sandbox.sardine.ai/v1/identity/entities \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "phoneNumber": "+14155551234" }'
```
```json theme={null}
{
"customerId": "3f8c1a22-1234-4abc-9def-000000000001",
"createdAt": "2026-05-01T10:00:00Z"
}
```
## 3. Generate a widget URL and redirect the user
Request a widget URL from your backend, then redirect the user to it. The widget handles all verification steps.
```bash theme={null}
curl -X POST https://api.sandbox.sardine.ai/v1/identity/consents/widget \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"customerId": "3f8c1a22-1234-4abc-9def-000000000001",
"successUrl": "https://yourapp.com/kyc/success",
"scope": ["profile", "doc_kyc"],
"flow": "kyc_input"
}'
```
```json theme={null}
{
"widgetUrl": "https://identity.sardine.ai/?client_token=abc123&consent_id=xyz789&success_url=..."
}
```
Redirect the user to the `widgetUrl`. When they finish, Sardine redirects them back to your `successUrl`.
## 4. Retrieve the verified identity
Once the user returns to your `successUrl`, fetch their verified data from your backend.
```bash theme={null}
curl https://api.sandbox.sardine.ai/v1/identity/entities/3f8c1a22-1234-4abc-9def-000000000001 \
-u "$CLIENT_ID:$CLIENT_SECRET"
```
```json theme={null}
{
"profile": {
"userId": "3f8c1a22-1234-4abc-9def-000000000001",
"fullName": "Jane Smith",
"dateOfBirth": "1990-06-15",
"emailAddress": "jane@example.com",
"phoneNumber": "+14155551234",
"address": {
"street": "123 Main St",
"city": "San Francisco",
"region": "CA",
"postalCode": "94105"
}
},
"documentData": {
"documentType": "DRIVERS_LICENSE",
"documentNumber": "D1234567",
"issuingCountry": "US"
},
"documentKyc": {
"front": "",
"back": "",
"selfie": ""
}
}
```
***
Once you've completed a successful end-to-end test in sandbox, reach out to your Sardine contact to complete the integration review and receive production credentials.
For a full implementation reference see the [API Integration guide](/integration_guides/identity/api_integration).
# NFT Checkout with Crypto Payout
Source: https://docs.payments.sardine.ai/integration_guides/nft_checkout/nft_checkout_payout
Sardine's NFT checkout widget offers a quick way to integrate the ability to directly purchase NFTs from fiat through a URL, and then settle with crypto.
This is best suited for quick integrations where the developer does not want to build their own UI. The Sardine Risk SDK is natively integrated into the checkout form.
[Example URL](https://crypto.sandbox.sardine.ai/?client_token=123-absc-1231\&show_features=true)
**Goal**
By the end of this guide, you should be able to open a new window with Sardine NFT Checkout, either in a new tab or in a new browser window, and receive payout to a wallet of your choice.
## Implementing NFT Checkout
Before we start, you'll need the following parameters
```json jsonSchema theme={null}
{
"title": "Authorization Parameters",
"type": "object",
"properties": {
"clientId": {
"type": "string",
"description": "A unique Client Identifier issued by Sardine for an integration. This is safe to be exposed to the public internet. This is needed for client side JS"
},
"clientSecret": {
"type": "number",
"description": "The Secret Key is associated with a specific Client ID. It must be kept secret."
},
},
"required": ["clientId", "clientSecret"]
}
```
### 1. Obtain authorization token
The next step is to obtain the `clientToken`, which is a unique identifier for each session and user.
Make a POST request to `/v1/auth/client-tokens` using Basic Auth by passing base64 encoding of `:`
The body of this request is used to send information about the NFT and the user to Sardine. More information about this endpoint can be found [here](https://sardineai.stoplight.io/docs/integrate-payments/branches/main/9ed21dfe5c8ec-post-client-token)
```json jsonSchema theme={null}
{
"title": "clientToken Request",
"type": "object",
"properties": {
"customerId": {
"type": "string",
"description": "ID of Customer that can be passed, in lieu of Sardine creating one"
},
"referenceId": {
"type": "string",
"description": "Unique ID that should be passed to refer to this transaction. Status of this transaction will be fetched using this field as the key"
},
"expiresIn" : {
"type" : "string",
"description" : "Time in seconds until the NFT will expire"
},
"nft" : {
"type" : "object",
"description": "Metadata about NFT that needs to be passed",
"properties": {
"name": {
"type" : "string",
"description" : "Name of the NFT"
},
"collection": {
"type" : "string",
"description" : "Collection the NFT belongs to"
},
"price" : {
"type" : "number",
"description" : "Cost of the NFT in `currencyCode`"
},
"currencyCode" : {
"type" : "string",
"description" : "Fiat currency payment happens in",
"example" : "usd"
},
"imageUrl" : {
"type" : "string",
"description" : "Url which is hosting the image of the NFT"
},
},
"required" : ["name","price","currencyCode","imageUrl","expiration"]
},
"taxRates": {
"type": "object",
"description": "Which contains rates for countries/regions in ISO3166-2 format.",
"properties": {
"US": {
"type": "string",
"description": "Countrycode like US. It's percentage, US: 10 means 10% tax for all US",
"example": "US = 10"
},
"US-NY": {
"type": "string",
"description": "countrycode-subdivisions like US-NY. \"US-NY\": 13 means 13% for NY state.",
"example": "US-NY = 13"
},
"US-CA": {
"type": "string",
"example": "US-CA = 20",
"description": "countrycode-subdivisions like US-CA. \"US-CA\": 20 means 20% for CA state."
}
}
},
"identityPrefill" : {
"type" : "object",
"description" : "User information that can be prefilled into the Checkout UI",
"properties" : {
"firstName" : {
"type" : "string",
"description" : "First name of buyer"
},
"lastName" : {
"type" : "string",
"description" : "Last name of buyer"
},
"dateOfBirth" : {
"type" : "string",
"description" : "Date of Birth of buyer in YYYY-MM-DD format"
},
"emailAddress" : {
"type" : "string",
"description" : "Verified email address of buyer"
},
"phone" : {
"type" : "string",
"description" : "Verified phone number of buyer"
},
"address" : {
"type" : "object",
"properties" : {
"street1" : {
"type" : "string",
"description" : "Street address"
},
"street2" : {
"type" : "string",
"description" : "Suite, Apartment number etc"
},
"city" : {
"type" : "string",
"description" : "City in address"
},
"regionCode" : {
"type" : "string",
"description" : "2 letter state code"
},
"postalCode" : {
"type" : "string",
"description" : "Zip code or equivalent"
},
"countryCode" : {
"type" : "string",
"description" : "2 letter ISO country code"
}
}
}
}
}
},
"required": ["referenceId"]
}
```
A sample request would look like below
```json http theme={null}
{
"method": "POST",
"url": "https://api.sandbox.sardine.ai/v1/auth/client-tokens",
"headers" : {
"Authorization" : "Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0",
},
"body" : {
"referenceId": "42eadcb0-4a93-45af-9c8c-d295db5aeb6c",
"customerId": "adf02ae2-f633-11ec-b939-0242ac120002",
"expiresIn" : 600,
"nft": {
"name": "NFT #1",
"price": 100,
"currencyCode": "USD",
"contractAddress": "0x7fC0344254E1663C2eF24e3c063cbec231525C20",
"imageUrl": "https://gateway.nftcompany.io/ipfsQmSAQm4gbhjSeUk7fuYppHd7Z8dfWpBvnFmqFSKqkrUJPM",
"network" : "ethereum"
},
"identityPrefill": {
"firstName": "John",
"lastName": "Doe",
"dateOfBirth": "2000-01-01",
"emailAddress": "foobar@gmail.com",
"phone": "+19254485826",
"address": {
"street1": "123 Main st",
"street2": "",
"city": "irvine",
"regionCode": "CA",
"postalCode": "02747",
"countryCode": "US"
}
}
}
}
```
If the request is successful, you should receive a response that contains the `clientToken`, which is needed to create the Checkout
Use your base64 encode( clientId:clientSecret ) to make a call
**Constraints**
* referenceId - unique
* expiresIn - 300 to 3600 ( 5min - 1hr)
**Success Response:**
```json theme={null}
{
"clientToken": "",
"expiresAt": "2022-07-07T21:32:29Z"
}
```
**Error Response:**
```json theme={null}
{
"message": "Duplicate referenceId",
"code": "INVALID_PARAMS"
}
```
[A fully complete URL will look like this:](https://crypto.sandbox.sardine.ai/?client_token=\\&show_features=true>)
### 2. User goes through Sardine Checkout
Once the Checkout URL is opened, the Sardine flow takes over and guides the user through the Checkout
### 3. Embed NFT checkout
Once the checkout URL has been generated, it can be embedded into your web app as an iframe, with event handlers to catch events sent by the iframe.
Sample code to embed NFT checkout
Recommended size is width=500, height=700 for new window
```html theme={null}
```
```js theme={null}
import { useRef, useEffect, useCallback } from 'react'
SardineIframe = ({ eventHandler }) => {
const iframeRef = useRef(null)
const postMessageListener = useCallback((event) => {
if(event.source !== iframeRef?.current?.contentWindow) {
return;
}
eventHandler(event.data)
})
useEffect(() => {
window.addEventListener('message', postMessageListener)
return () => {
window.removeEventListener('message', postMessageListener)
}
},
[iframeRef])
return
### 4. Get Confirmation of Trade Status
When the user presses Confirm, Sardine will fire a `order.confirmed` webhook, along with the frontend events which can be caught with event handlers.
Sardine will emit events which can be handled to understand user action
There are three events that can be caught using event handlers.
`Expired` - The user didn't complete the transaction within the `expiration` paramter
```json theme={null}
{
"status" : "Expired",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Processed` - The payment is complete, and the NFT can now be transferred the user.
```json theme={null}
{
"status" : "Processed",
"data" : {
"price": 100,
"orderId": "123e4567-e89b-12d3-a456-426614174001",
"transactionFee": 2,
"networkfee": "0.35"
"currencyCode": "usd",
"paymentMethod": "card",
"createdAt": 12312321312,
"contractAddress": "0x10b195F7Be9B120efd05C58f16650A13f533eA33",
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Declined` - The transaction was declined due to issues with their payment method or risk profile.
```json theme={null}
{
"status" : "Declined",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
Sardine also initiates webhooks which can be used to determine the state of the Checkout. Check [Webhook](/guides/integration/payments/nftCheckout/integrationGuide/webhook-support) for full details.
### 5. Initiate Payout
Sardine allows for payments to be settled in cryptocurrency. Once an Order has been created, it can be used to trigger a Payout. Sardine can accept multiple recipients which receive different percentages of the entire Order. Check [Create Payouts API](https://docs.sardine.ai/docs/integrate-payments/branches/main/b7f4751dbac43-create-payout) for full request and responses.
The amount is determined based on the `orderId` that is passed.
```json http theme={null}
{
"method": "POST",
"url": "https://api.sandbox.sardine.ai/v1/payouts",
"body" : {
"referenceId" : "7ce511d0-c973-4744-b819-d933a248ae51",
"orderId" : "324e02055-8235-4405-bc22-1dd06ac87d4e",
"payoutConfiguration" : {
"recipients" : [
{
"payoutType" : "crypto",
"walletAddress" : "0x18739187238123123123",
"tokenAddress" : "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"network" : "ethereum",
"percentage" : "100"
}
]
}
},
"headers" : {
"Authorization" : "Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0",
}
}
```
Once a Payout has been created, webhooks are sent to confirm the different states of the payout. Check [Webhooks](/guides/integration/payments/nftCheckout/integrationGuide/webhook-support) for more information on which webhooks will be triggered. The state of each Payout can also be queried through the [Fetch Payout API](https://docs.sardine.ai/docs/integrate-payments/branches/main/170ea3df096fc-fetch-payout-information)
### 6 . Testing and Verification
Once the defined URL has been set and triggered, it should open an iframe that goes through the user flows for Sardine's NFT checkout. This should match the screen show on the \[User Flows Page]
theme: success
> You should now be able to route users to the Sardine Checkout Widget via your defined URL where they can instantly buy NFTs!
# NFT Checkout
Source: https://docs.payments.sardine.ai/integration_guides/nft_checkout/nft_checkout_web
Sardine's NFT checkout widget offers a quick way to integrate the ability to directly purchase NFTs from fiat through a URL.
This is best suited for quick integrations where the developer does not want to build their own UI. The Sardine Risk SDK is natively integrated into the checkout form.
[Example URL](https://crypto.sandbox.sardine.ai/?client_token=123-absc-1231\&show_features=true)
**Goal**
By the end of this guide, you should be able to open a new window with Sardine NFT Checkout, either in a new tab or in a new browser window.
## Implementing NFT Checkout
Before we start, you'll need the following parameters
```json jsonSchema theme={null}
{
"title": "Authorization Parameters",
"type": "object",
"properties": {
"clientId": {
"type": "string",
"description": "A unique Client Identifier issued by Sardine for an integration. This is safe to be exposed to the public internet. This is needed for client side JS"
},
"clientSecret": {
"type": "number",
"description": "The Secret Key is associated with a specific Client ID. It must be kept secret."
},
},
"required": ["clientId", "clientSecret"]
}
```
### 1. Obtain authorization token
The next step is to obtain the `clientToken`, which is a unique identifier for each session and user.
Make a POST request to `/v1/auth/client-tokens` using Basic Auth by passing base64 encoding of `:`
The body of this request is used to send information about the NFT and the user to Sardine. More information about this endpoint can be found [here](https://sardineai.stoplight.io/docs/integrate-payments/branches/main/9ed21dfe5c8ec-post-client-token)
```json jsonSchema theme={null}
{
"title": "clientToken Request",
"type": "object",
"properties": {
"customerId": {
"type": "string",
"description": "ID of Customer that can be passed, in lieu of Sardine creating one"
},
"referenceId": {
"type": "string",
"description": "Unique ID that should be passed to refer to this transaction. Status of this transaction will be fetched using this field as the key"
},
"expiresIn" : {
"type" : "string",
"description" : "Time in seconds until the NFT will expire"
},
"nft" : {
"type" : "object",
"description": "Metadata about NFT that needs to be passed",
"properties": {
"name": {
"type" : "string",
"description" : "Name of the NFT"
},
"collection": {
"type" : "string",
"description" : "Collection the NFT belongs to"
},
"price" : {
"type" : "number",
"description" : "Cost of the NFT in `currencyCode`"
},
"currencyCode" : {
"type" : "string",
"description" : "Fiat currency payment happens in",
"example" : "usd"
},
"imageUrl" : {
"type" : "string",
"description" : "Url which is hosting the image of the NFT. This the string must be URL encoded".
},
},
"required" : ["name","price","currencyCode","imageUrl","expiration"]
},
"taxRates": {
"type": "object",
"description": "Which contains rates for countries/regions in ISO3166-2 format.",
"properties": {
"US": {
"type": "string",
"description": "Countrycode like US. It's percentage, US: 10 means 10% tax for all US",
"example": "US = 10"
},
"US-NY": {
"type": "string",
"description": "countrycode-subdivisions like US-NY. \"US-NY\": 13 means 13% for NY state.",
"example": "US-NY = 13"
},
"US-CA": {
"type": "string",
"example": "US-CA = 20",
"description": "countrycode-subdivisions like US-CA. \"US-CA\": 20 means 20% for CA state."
}
}
},
"identityPrefill" : {
"type" : "object",
"description" : "User information that can be prefilled into the Checkout UI",
"properties" : {
"firstName" : {
"type" : "string",
"description" : "First name of buyer"
},
"lastName" : {
"type" : "string",
"description" : "Last name of buyer"
},
"dateOfBirth" : {
"type" : "string",
"description" : "Date of Birth of buyer in YYYY-MM-DD format"
},
"emailAddress" : {
"type" : "string",
"description" : "Verified email address of buyer"
},
"phone" : {
"type" : "string",
"description" : "Verified phone number of buyer"
},
"address" : {
"type" : "object",
"properties" : {
"street1" : {
"type" : "string",
"description" : "Street address"
},
"street2" : {
"type" : "string",
"description" : "Suite, Apartment number etc"
},
"city" : {
"type" : "string",
"description" : "City in address"
},
"regionCode" : {
"type" : "string",
"description" : "2 letter state code"
},
"postalCode" : {
"type" : "string",
"description" : "Zip code or equivalent"
},
"countryCode" : {
"type" : "string",
"description" : "2 letter ISO country code"
}
}
}
}
}
},
"required": ["referenceId"]
}
```
A sample request would look like below
```json http theme={null}
{
"method": "POST",
"url": "https://api.sandbox.sardine.ai/v1/auth/client-tokens",
"headers" : {
"Authorization" : "Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0",
},
"body" : {
"referenceId": "42eadcb0-4a93-45af-9c8c-d295db5aeb6c",
"customerId": "adf02ae2-f633-11ec-b939-0242ac120002",
"expiresIn" : 600,
"nft": {
"name": "NFT #1",
"price": 100,
"currencyCode": "USD",
"contractAddress": "0x7fC0344254E1663C2eF24e3c063cbec231525C20",
"imageUrl": "https://gateway.nftcompany.io/ipfsQmSAQm4gbhjSeUk7fuYppHd7Z8dfWpBvnFmqFSKqkrUJPM",
"network" : "ethereum"
},
"identityPrefill": {
"firstName": "John",
"lastName": "Doe",
"dateOfBirth": "2000-01-01",
"emailAddress": "foobar@gmail.com",
"phone": "+19254485826",
"address": {
"street1": "123 Main st",
"street2": "",
"city": "irvine",
"regionCode": "CA",
"postalCode": "02747",
"countryCode": "US"
}
}
}
}
```
If the request is successful, you should receive a response that contains the `clientToken`, which is needed to create the Checkout
Use your base64 encode( clientId:clientSecret ) to make a call
**Constraints**
* referenceId - unique
* expiresIn - 300 to 3600 ( 5min - 1hr)
**Success Response:**
```
{
"clientToken": "",
"expiresAt": "2022-07-07T21:32:29Z"
}
```
```
**Error Response:**
{
"message": "Duplicate referenceId",
"code": "INVALID_PARAMS"
}
```
[A fully complete URL will look like this:](https://crypto.sandbox.sardine.ai/?client_token=123-asd-456\&show_features=true)
Additionally, `address` can be passed which is the receiving wallet address of the user
### 2. Understand Post transaction events
Sardine will emit events which can be handled to understand user action
`expired` - The user didn't complete the transaction within the `expiration` parameter
```json theme={null}
{
"status" : "expired",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`processed` - The payment is complete, and the NFT can now be transferred the user.
```json theme={null}
{
"status" : "processed",
"data" : {
"price": 100,
"orderId": "123e4567-e89b-12d3-a456-426614174001",
"transactionFee": 2,
"networkfee": "0.35"
"currencyCode": "usd",
"paymentMethod": "ACH",
"createdAt": 12312321312,
"contractAddress": "0x10b195F7Be9B120efd05C58f16650A13f533eA33",
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`declined` - The transaction was declined due to issues with their payment method or risk profile.
```json theme={null}
{
"status" : "declined",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
### 3. Embed NFT checkout
Once the checkout URL has been generated, it can be embedded into your web app as an iframe, with event handlers to catch events sent by the iframe.
Sample code to embed NFT checkout
Recommended size is width=500, height=700 for new window
```html theme={null}
```
```js theme={null}
import { useRef, useEffect, useCallback } from 'react'
SardineIframe = ({ eventHandler }) => {
const iframeRef = useRef(null)
const postMessageListener = useCallback((event) => {
if(event.source !== iframeRef?.current?.contentWindow) {
return;
}
eventHandler(event.data)
})
useEffect(() => {
window.addEventListener('message', postMessageListener)
return () => {
window.removeEventListener('message', postMessageListener)
}
},
[iframeRef])
return
### 4. Get Confirmation of Trade Status
After the purchase has been completed, the `referenceId` or the `clientToken` can be used to fetch information about the respective transaction by calling the [Orders](https://sardineai.stoplight.io/docs/nft-checkout/branches/v1/d3bac9812c078-get-order-informatiom) endpoint
The order can have one of these statuses
`draft` - This is an open or ongoing order
`processed` - The payment has been completed.
`expired` - The user didn't complete the transaction within the `expiration` time
`declined` - The transacation was declined, due to payment method issues
`complete` - The payment is complete and the NFT has been delivered to the user
`cancelled` - The order is cancelled
```json http theme={null}
{
"method": "GET",
"url": "https://api.sandbox.sardine.ai/v1/orders",
"query" : {
"referenceId" : "7ce511d0-c973-4744-b819-d933a248ae51"
},
"headers" : {
"Authorization" : "Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0",
}
}
```
Once the status changes to completed, the NFT has been transferred
### 5 . Testing and Verification
Once the defined URL has been set and triggered, it should open an iframe that goes through the user flows for Sardine's NFT checkout. This should match the screen show on the \[User Flows Page]
You should now be able to route users to the Sardine Checkout Widget via your defined URL where they can instantly buy NFTs!
# NFT Checkout Webhooks
Source: https://docs.payments.sardine.ai/integration_guides/nft_checkout/webhooks
### Webhook Events
After every order that is confirmed by a user, Sardine will fire off a webhook to a URL designated by the developer.
A sample webhook ⬇️
```json theme={null}
{
"eventType": "order.processed",
"id": "90024712-0aae-46e8-9534-48007baa610d",
"order": {
"id": "6d5e2058-7a30-46c5-bbfe-b5e20d427e5a",
"referenceId": null,
"status": "Processed"
}
}
```
**For NFT Checkout, the different webhooks that can be expected are:**
| webhook | description |
| :--------- | :--------------------------------------------------------------- |
| draft | Client has created an order and redirected user to purchase |
| expired | User did not confirm the purchase and order expired |
| declined | User attempted a purchase but their payment attempt was declined |
| cancelled | Order was cancelled |
| processing | Order was confirmed, waiting for payment |
| processed | Payment was processed successfully |
| complete | Order was executed and delivered to user |
A sample webhook ⬇️
```json theme={null}
{
"eventType": "payout.funded",
"id": "90024712-0aae-46e8-9534-48007baa610d",
"order": {
"id": "6d5e2058-7a30-46c5-bbfe-b5e20d427e5a",
"referenceId": null,
"status": "funded"
}
}
```
**For Payouts, the different webhooks that can be expected are:**
* **payout.funded** - The payout has been funded by Sardine
* **payout.complete** - They payout has settled onchain to the specified wallet
* **payout.declined** - The payout was declined
**To set up your webhook:**
1. Provide your webhook URL to your Sardine Integration Manager
2. Your Sardine IM will set this up and then provide you with a **signing\_secret**
**In order to verify the webhook notification, follow these instructions:**
1. Construct the signedContent by concatenating the id, timestamp and payload, separated by the full-stop character (.). In code, it will look something like: `signedContent = "${webhook-id}.${webhook-timestamp}.${body}"` where body is the raw body of the request.
2. To calculate the expected signature, you need to perform an HMAC hash on the `signedContent` from above using the base64 portion of your signing secret (this is the part after the whsec\_ prefix) as the key. So if your signing secret is: `whsec_ABCDmcQ8DpB7J6Yn4eZqkt48KRPy3a8n`, you'll want to use `ABCDmcQ8DpB7J6Yn4eZqkt48KRPy3a8n`
3. This generated signature should match what is sent in the `webhook-signature` header; make sure to remove the version prefix and delimiter (e.g. v1,) before verifying the signature.
Please note that to compare the signatures, it's recommended to use a constant-time string comparison method in order to prevent timing attacks.
# Onboarding Guide
Source: https://docs.payments.sardine.ai/integration_guides/onboarding_and_testing/client_onboarding
### 1. Account Approval
Prior to integration, we require a quick KYB questionnaire to be reviewed by our compliance team. This questionnaire covers basic details regarding your business, use case and coverage needs. We may follow up with additional questions to ensure we can approve your integration.
### 2. Get Your Sandbox API Credentials
Once approved by our compliance team, we are ready to create your account and provide credentials to kick off integration.
You will issued two sandbox API keys: a `clientID` and a `clientSecret`.
* The `clientID` is Sardine's internal configuration ID for your account.
* The `clientSecret` key will be used to authenticate into our APIs.
If you have been approved by compliance but have not received your sandbox API credentials, please reach out to your Sardine contact.
### 3. Integrating our widget
Once you have received your sandbox credentials, please reference the integration guides and resources below to begin the integration. If there are any questions, please don't hesitate to reach out to your Sardine contact.
### 4. Testing in Sandbox
Once you have completed integration, you can test onboarding a user and completing a transaction in our sandbox environment before going live. You can find sample user testing credentials and payment methods in our [Testing Credentials Page](/guides/integration/payments/Onboarding/testingcredentials)
### 5. Going Live
Once you have finished integrating in sandbox, a member of our integrations team will set up a quick call to review your integration. Once they have approved your implementation, Sardine will provide a production set of API keys.
Once you have the integration in a live environment, our integrations team will perform a final set of production tests to ensure your integration is working properly.
# Issue Reporting
Source: https://docs.payments.sardine.ai/integration_guides/onboarding_and_testing/issue_reporting
If you find an error or issue in the product please do the following:
* If possible, attempt to reproduce the bug while screen recording
* In the hamburger menu, click on Support and file a ticket
* Record the Session ID associated to when you discovered/reproduced the issue:
* In Chrome, [open the Developer Tools](https://developer.chrome.com/docs/devtools/open/) and click the tab at the top that says Console
* In the console window underneath the top tab, copy-paste the following command:
* `srdn.storage.readSession().public_key || srdn.pageLoad.clientToken`
* You should receive a Session ID that looks like '4453d3ae-efe6-4a23-9a98-dd89a0e41376’
Submit a request to Sardine with a description of the issue you found, the Session ID and attach the screen recording, if possible
# Testing Credentials
Source: https://docs.payments.sardine.ai/integration_guides/onboarding_and_testing/test_user_credentials
The onboarding and payment linking process in sandbox matches the user experience in production. In order to facilitate testing in sandbox, we have provided to following credentials to be used.
### Create a Test Account
* Our sandbox user registration is designed to intake any phone number starting with 99 as a test user
* The OTP for the test account will be last 6 digits of the test phone number
* Ex. Phone number is 991-234-5678. OTP will be 345678
**Note**: Try different test numbers starting with +199 and any 8 digit number following that number. Do not use the example +19912345678 as it will fail and not process the payment.
Use a random number following the above scheme as common numbers like 991-234-5678 are likely to have been used already
* If you use a previously registered number, you will be considered an existing user and login via the phone number provided + OTP.
### KYC Information
Please use the following information when filling out test user details
**Name** - Any value
**Date of Birth** - Any value where user is above 18 years old
**Address** - Any physical address (No P.O. Boxes)
**Email address**
Sardine allows you to test the flow for different risk levels by appending a risk level modifier to the email address. any email address can be used
| Risk Level | Description | Modifier | Example |
| ----------- | -------------------------- | ------------ | --------------------------------------------------------------- |
| Low Risk | No docKYC + high limits | +low-risk | [steve+low-risk@test.com](mailto:steve+low-risk@test.com) |
| Medium Risk | DocKYC + lower limits | +medium-risk | [steve+medium-risk@test.com](mailto:steve+medium-risk@test.com) |
| High Risk | DocKYC + minimal/no limits | +high-risk | [steve+high-risk@test.com](mailto:steve+high-risk@test.com) |
**SSN** (US Only) - Any random 9 digit number
### Wallet Address
Sardine does extra monitoring to make sure that any sanctioned or high risk wallets are restricted. To simulate this, the following wallets can be used
| Risk Level | Wallet Address |
| -------------- | ------------------------------------------ |
| Very High Risk | 0xaAaAaA1234567890000000000000000000000000 |
| High Risk | 0xaaAAAa1234567890000000000000000000000001 |
| Medium Risk | 0xAaaaAA1234567890000000000000000000000002 |
| Low Risk | 0xaaaaaA1234567890000000000000000000000003 |
### Payment Methods
**Credit / Debit Card**
| Country | Card Type | Card Number |
| ------- | --------- | ---------------- |
| US | Debit | 4659105569051157 |
| FR | Credit | 4010056200000018 |
Other card details:
* Any expiry date in the future
* Any CVV code
* Any postal/zip code
**Bank Transfer**
| Payment Type | Description | User Name | Password |
| ------------ | -------------------- | ----------- | ---------- |
| ACH | Standard credentials | user\_good | pass\_good |
| ACH | High balance user | custom\_50k | pass\_good |
**Apple Pay**
In order to test Apple Pay in sandbox, you will need to create an Apple Pay testing account. Please reach out to Sardine for additional support if you wish to set up and test Apple Pay in sandbox.
# API Integration
Source: https://docs.payments.sardine.ai/integration_guides/onofframps/api_integration
# API Integration Guide
Welcome! This guide will walk you through integrating with our API — from authentication and setup to placing orders and receiving updates.
## 1. Obtain Credentials
To access the API, you must first obtain your **API key** and **secret**. These authenticate your requests and track usage.
### Steps:
* **Contact the Sardine integration team.** They will generate a `client_id` and `client_secret` for your use.
* Store both the `client_id` and `client_secret` securely.
> **Security Tip:** Never expose your secret in frontend apps. Use a backend service to interact with the API.
## 2. Retrieve Supported Geo Coverage, Assets, and Payment Methods
Use the following endpoints to fetch dynamic metadata for your UI:
* `GET /v1/geo-coverage` — Supported countries/regions.
* `GET /v1/supported-tokens` — Available cryptocurrencies and fiat currencies.
Use this info to populate dropdowns and validate inputs client-side.
## 3. Onboard a User
Before placing an order, the user must be onboarded and registered in the system.
### Endpoint
```
POST /v1/customers
```
### Sample Payload
```json theme={null}
{
"email": "user@example.com",
"phone": "+1234567890",
"country": "US"
}
```
### Response
Returns a `customerId` which must be used in all user-specific operations.
## 4. User Verification (KYC)
Depending on jurisdiction or transaction volume, KYC verification may be required.
### Endpoint
```
POST /v1/onramp/customers/{id}/verify
```
You may be asked to:
* Upload an ID document.
* Submit a selfie.
* Redirect to a hosted KYC flow.
### Response
Returns a `verification_status`:
* `pending`
* `approved`
* `rejected`
## 5. Fetch a Quote
Before placing an order, fetch a quote to show the user a guaranteed rate and fee breakdown.
### Endpoint
```
POST /v1/quotes
```
### Sample Payload
```json theme={null}
{
"user_id": "abc123",
"asset": "BTC",
"fiat_currency": "USD",
"amount": "100.00",
"payment_method": "card"
}
```
### Response Includes:
* Exchange rate
* Fees
* Quote expiry
* `quote_id` (used when placing an order)
## 6. Execute an Order
Place an order based on an approved quote.
### Endpoint
```
POST /v1/orders
```
### Sample Payload
```json theme={null}
{
"user_id": "abc123",
"quote_id": "quote789",
"payment_method": "card",
"destination_wallet": {
"type": "crypto",
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"asset": "BTC"
}
}
```
### Response:
Includes `order_id` and the initial status.
## 7. Get Status Updates via Webhooks
Set up webhooks to get real-time updates on order progress and compliance events.
### Steps:
* Register your webhook URL in the Developer Portal.
* Your endpoint should be HTTPS and respond with `2xx`.
### Sample Webhook Payload:
```json theme={null}
{
"event": "order.status.updated",
"order_id": "order123",
"status": "completed",
"timestamp": "2025-04-17T12:34:56Z"
}
```
> Validate webhook signatures and implement retry logic for maximum reliability.
## 8. Fetch the Order Status
Poll this endpoint for order updates if webhooks aren’t available or for manual checks.
### Endpoint
```
GET /v1/orders/{order_id}
```
### Response Includes:
* Current status: `pending`, `processing`, `completed`, `failed`
* Timestamps
* Payment and asset transfer details
## You're All Set!
Once these steps are implemented, you’ll have full integration from onboarding to real-time tracking. For additional topics like error handling, retries, or sandbox mode, check out the [Extended Developer Docs](https://your-api-domain.com/docs).
# Mobile Integration
Source: https://docs.payments.sardine.ai/integration_guides/onofframps/mobile_integration
You can quickly integrate Sardine Crypto On-ramp fiat via a mobile interface. For Mobile URL Webview implemenations, the checkout widget is generated via URL.
\*\*\*Goal
By the end, you should be able to embed Sardine On-ramp into your mobile app through a WebView
### 0. Integrate the Risk SDK
Our Risk SDK is a **mandatory** requirement to help us fight fraud via device intelligence and behavior biometrics. Sardine’s proprietary technology is adept at this very task and is instrumental in identifying risky devices, behavior, and tools that are used during these sessions (example: VPNs, emulators, remote desktop protocols etc.)
Benefits of adding Risk SDK
* Higher limts for good users
* Better fraud identification
* Less friction for good users
Select the Mobile SDK as per your need:
| |
| :----------------------------------------------------------------------------------------------: |
| [Android](https://docs.sardine.ai/docs/risk-sdk/e719e8d815e00-android-sdk) |
| [iOS](https://docs.sardine.ai/docs/risk-sdk/e9b0663c649da-swift-sdk) |
| [ReactNative](https://docs.sardine.ai/docs/risk-sdk/bb3b2fe867a26-react-native-i-os-android-sdk) |
| [Flutter](https://docs.sardine.ai/docs/risk-sdk/7c1376a9c9263-flutter) |
If you are unable to view the documents in the links above, please reach out to your Sardine representative to get access.
### 1. Obtain authorization token
Before we start, you'll need the following credentials
```yaml json_schema theme={null}
$ref: "../../../models/Authorization-Parameters.yaml"
```
You will need to obtain a `clientToken`, which is a unique identifier for each session and user.
Make a POST request to `/client-tokens` using Basic Auth by passing base64 encoding of `:`
```json http theme={null}
{
"method": "POST",
"url": "https://api.sandbox.sardine.ai/v1/auth/client-tokens",
"headers" : {
"Authorization" : "Basic MTY3NDRkZGMtYThhMy00OGIyLWE4ZTktNjA2YWU4OTk1NTM5OmYyMGJhNGRiLTczYzItNDk0Mi04NDAyLWRhNzc4OTllNzY2Mg==",
}
}
```
If the request is successful, you should receive a response that contains the `clientToken` and `expiresAt` field
```
client_token = response["clientToken"]
expires_at = response["expiresAt"]
orderId = response["orderId"]
```
### 2. Determine your widget configuration and create URL.
Sardine's widget can be configured based on any parameters that are passed through. Please use [the configuration reference guide](/guides/integration/payments/OnOffRamps/GettingStarted/configuration).
By default, if no parameters are passed, users will be redirected to our standard checkout widget.
`client_token` is the only required parameter. Additional parameters that are passed will autofill the widget for the user and allow them to skip some screens.
### 3. Implement the web checkout widget
Once the URL has been generated, it can be embedded into your web app as a hyperlink. Below, we have included samples of different ways of integrating the checkout
To assist with development, we have created some code samples which illustrate how your code can be structured to create the checkout url and handle the finished trade.
`allowsInlineMediaPlayback` should be enabled either via code or through storyboard
1. Start by importing Webview
2. Craft the URL based on preferred parameters
3. Create event handlers that will catch the `processed`,`expired` and `declined` events
4. Call the WebView component with parameters set as such. These have been set for the optimal user expereince.
Full code should look as such
```js theme={null}
import { WebView } from ‘react-native-webview’;
const uri = "https://crypto.sandbox.sardine.ai/?address=0xjadadhadskaj2123&fiat_amount=1000&&asset_type=usdc&network=ethereum&client_token=123-asd-456"; // URL prepared from above step
const javaScriptFunction = `
document.addEventListener('processed', function(data) {
const d = data.detail;
const v = d ? JSON.stringify(d) : "";
window.ReactNativeWebView && window.ReactNativeWebView.postMessage(v);
});
document.addEventListener('expired', function(data) {
const d = data.detail;
const v = d ? JSON.stringify(d) : "";
window.ReactNativeWebView && window.ReactNativeWebView.postMessage(v);
});
document.addEventListener('declined', function(data) {
const d = data.detail;
const v = d ? JSON.stringify(d) : "";
window.ReactNativeWebView && window.ReactNativeWebView.postMessage(v);
});
`
if (uri) {
let cryptoURL = uri;
if(Platform.OS == "android") {
cryptoURL = `${uri}&android_package_name=com.your-app-pakage-name`
} else if(Platform.OS == "ios") {
cryptoURL = `${uri}&plaid_redirect_url=https://your-domain-here.com`
}
return (
this.webView = webView}
source={{ uri: cryptoURL }}
mediaPlaybackRequiresUserAction={false}
allowInlineMediaPlayback
allowsBackForwardNavigationGestures
javaScriptEnabled={true}
injectedJavaScript={javaScriptFunction}
originWhitelist={[‘*’]}
onMessage={event => {
const orderData = JSON.parse(event.nativeEvent.data);
if(orderData) {
setTimeout(() => {
// handle code for order success
this.handleOrderSuccess(orderData)
}, 2000);
}
}}
/>
);
}
```
```swift theme={null}
import UIKit
import WebKit
class CryptoWebController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
// Private variables
private var webView : WKWebView?
private var cryptoAddress = ""
private var fiatAmount = ""
private var assetType = ""
private var network = ""
private var supportedTokens = ""
private var clientToken = ""
// Error response
private let errorResponse = CryptoResponse(status: false, data: nil)
// Public variables
public var completion : ((CryptoResponse)->())?
// Constants
private enum CONSTANTS : String {
case STATUS = "orderStatus"
case SUCCESS = "order-success"
case FAILURE = "order-fail"
case TITLE = "Crypto ACH"
case DISMISS = "Dismiss"
case ALERT_TITLE = "Confirmation"
case ALERT_MESSAGE = "Are you sure you want to dismiss?"
case ACTION_YES = "Yes"
case ACTION_NO = "No"
}
convenience init(withAddress address: String, fiatAmount: String, assetType: String, network: String, supportedTokens: String, clientToken: String) {
self.init()
self.cryptoAddress = address
self.fiatAmount = fiatAmount
self.assetType = assetType
self.network = network
self.supportedTokens = supportedTokens
self.clientToken = clientToken
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = CONSTANTS.TITLE.rawValue
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: CONSTANTS.DISMISS.rawValue, style: .plain, target: self, action: #selector(dismissAction))
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let baseURL = URL(string:"https://crypto.sandbox.sardine.ai") else {
return
}
let scheme = baseURL.scheme
let host = baseURL.host
let path = "/alpha/6ccfb278-5f94-44fe-bf33-4ea81325713b"
let queryItems = [
URLQueryItem(name: "address", value: cryptoAddress),
URLQueryItem(name: "fiat_amount", value: fiatAmount),
URLQueryItem(name: "asset_type", value: assetType),
URLQueryItem(name: "network", value: network),
URLQueryItem(name: "supported_tokens", value: supportedTokens),
URLQueryItem(name: "client_token", value: clientToken),
URLQueryItem(name: "plaid_redirect_url", value: "https://your-domain-here.com"),
]
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = queryItems
guard let cryptoURL = urlComponents.url else {
return
}
let cryptoRequest = URLRequest(url: cryptoURL)
let preferences = WKPreferences()
let contentController = WKUserContentController()
preferences.javaScriptEnabled = true
let source: String = """
window.onload = function() {
window.document.addEventListener("\(CONSTANTS.SUCCESS.rawValue)", function(data) {
const d = data.detail;
const v = d ? JSON.stringify(d) : "";
const val = '"' + `${v}` + '"';
window.webkit.messageHandlers.\(CONSTANTS.STATUS.rawValue).postMessage(`${val}`);
});
window.document.addEventListener("\(CONSTANTS.FAILURE.rawValue)", function(data) {
window.webkit.messageHandlers.\(CONSTANTS.STATUS.rawValue).postMessage("\(CONSTANTS.FAILURE.rawValue)");
});
}
"""
let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
contentController.addUserScript(script)
contentController.add(self, name: CONSTANTS.STATUS.rawValue)
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
configuration.userContentController = contentController
self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)
self.webView!.allowsBackForwardNavigationGestures = true
self.webView!.uiDelegate = self
self.webView!.navigationDelegate = self
self.view = webView
self.webView!.load(cryptoRequest)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
completion?(errorResponse)
}
@objc private func dismissAction() {
let alert = UIAlertController(title: CONSTANTS.ALERT_TITLE.rawValue, message: CONSTANTS.ALERT_MESSAGE.rawValue, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: CONSTANTS.ACTION_YES.rawValue, style: .cancel, handler: { _ in
self.navigationController?.dismiss(animated: true, completion: nil)
self.completion?(self.errorResponse)
}))
alert.addAction(UIAlertAction(title: CONSTANTS.ACTION_NO.rawValue, style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case CONSTANTS.STATUS.rawValue:
let isFailure = "\(message.body)" == CONSTANTS.FAILURE.rawValue
if !isFailure {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.navigationController?.dismiss(animated: true, completion: nil)
let stringValue = String("\(message.body)".dropFirst().dropLast())
var successResponse = CryptoResponse(status: true, data: nil)
guard let strData = stringValue.data(using: .utf8) else {
self.completion?(successResponse)
return
}
guard let d = try? JSONDecoder().decode(CryptoDetails.self, from: strData) else {
self.completion?(successResponse)
return
}
successResponse.data = d
self.completion?(successResponse)
}
}
default:
break
}
}
}
class CryptoManager {
public class func initiateCheckout(withAddress address: String, fiatAmount: String, assetType: String, network: String, supportedTokens: String, clientToken: String, completion : @escaping ((CryptoResponse)->())) {
DispatchQueue.main.async {
let cryptoVC = CryptoWebController(withAddress: address, fiatAmount: String, assetType: String, network: String, supportedTokens: String, clientToken: String)
cryptoVC.completion = completion
let navigationVC = UINavigationController(rootViewController: cryptoVC)
navigationVC.modalPresentationStyle = .fullScreen
self.present(navigationVC, animated: true, completion: nil)
}
}
}
```
### 4. Confirmation of Order Status
Sardine can fire off events that can be caught by event handlers that can be leveraged to update the user on the order status outside of the Sardine widget
There are two ways to get Order status, either via polling an orders endpoint or providing a redirect URL that we will send the events to
Developers can poll to the [/orders](https://docs.sardine.ai/docs/integrate-payments/900670db39399-get-order-s) endpoint to check its status with the `order_id`
```json http theme={null}
{
"method": "GET",
"url": "https://api.sandbox.sardine.ai/v1/orders/17983781730123",
"query" : {
"clientToken" : "7ce511d0-c973-4744-b819-d933a248ae51"
},
"headers" : {
"Authorization" : "Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0",
}
}
```
Pass a `redirect_url` parameter in the URL when intiating the widget. Once the transaction is complete, Sardine will make a call to the 'redirect\_url'
`Expired` - The user didn't complete the transaction within the time
```json theme={null}
{
"status" : "Expired",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Processed` - The payment is complete
```json theme={null}
{
"status" : "Processed",
"data" : {
"price": 100,
"orderId": "123e4567-e89b-12d3-a456-426614174001",
"transactionFee": 2,
"networkfee": "0.35"
"currencyCode": "usd",
"paymentMethod": "ACH",
"createdAt": 12312321312,
"address": "0x10b195F7Be9B120efd05C58f16650A13f533eA33",
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Declined` - The transaction was declined due to issues with their payment method or risk profile.
```json theme={null}
{
"status" : "Declined",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
There may be a delay between when an order was successful and the crypto is delivered to the user's wallet. This delay is due to the time required for transactions to settle on the chain.
### 5. Testing and Verification
Once the defined URL has been set and triggered, it should open a window that goes through the user flows for Sardine's on ramp.
A developer can use the sample information provided in the [Testing Payment Flows](/guides/integration/payments/Onboarding/testingcredentials) page to test the integration.
### 6. Going Live in Production
Once you have completed end-to-end testing in sandbox, you can go live by swapping to a set of production keys and updating your URLs. Please see the linked guide for a step-by-step of going live in production.
[Going to Production](/guides/integration/payments/OnOffRamps/GettingStarted/goingtoproduction)
You should now be able use an integrated Sardine Checkout Widget!
# On/Off-ramp Product Overview
Source: https://docs.payments.sardine.ai/integration_guides/onofframps/overview
The Crypto On/Off-ramp is a hosted widget with APIs/webhooks that can be integrated into your app or website to enable users to purchase or sell crypto.
### Architecture Diagram
```mermaid theme={null}
sequenceDiagram
actor U as User
participant C as Client
participant S as crypto.sardine
note right of U: Request Quote
C->>S: Call v1/quotes
S-->>C: Return quote
note right of U: Redirect user to Sardine Widget
C->>S: Create URL and redirect user to Sardine widget
U-->S: User completes onboarding and payment method linking
note right of U: User initiates purchase order
U-->S: User confirms order
S-->>U: Sardine redirects user to client page
S->>C: Sardine sends order created event webhook
C->>U: Client shows user that order is in progress
note right of U: order completes
S-->>C: Sardine sends order completed event webhook
C-->>U: Clients shows user completed transaction status
```
```mermaid theme={null}
sequenceDiagram
actor U as User
participant C as Client
participant S as crypto.sardine
note right of U: Return supported geos/currencies
C->>S: Call [insert endpoint for quotes]
note right of U: Request Quote
C->>S: Call [insert endpoint for quotes]
note right of U: Redirect user to Sardine Widget
C->>S: Call [insert redirect URL directions]
note right of U: User initiates sell order
S-->>U: Sardine redirects user to client page
S->>C: [insert order webhook details]
note right of U: Client gets transaction signature from user
C->>U: Client initiates user to sign deposit transaction within client app UX
S->>C: [insert order webhook details]
note right of U: order completes
S-->>C: [insert webhook details]
C-->>U: Clients shows updated transaction status
```
```mermaid theme={null}
sequenceDiagram
actor U as User
participant C as Client
participant S as crypto.sardine
note right of U: Return supported geos/currencies
note right of U: Request Quote
C->>S: Call [insert endpoint for quotes]
note right of U: Redirect user to Sardine Widget
C->>S: Call [insert redirect URL directions]
note right of U: User initiates sell order
S-->>U: Sardine redirects user to client page
S->>C: [insert order webhook details]
note right of U: User initiates crypto deposit
U->>S: User deposits crypto to Sardine wallet
S->>C: [insert order webhook details]
note right of U: order completes
S-->>C: [insert webhook details]
C-->>U: Clients shows updated transaction status
```
# Quickstart
Source: https://docs.payments.sardine.ai/integration_guides/onofframps/quickstart
Get your crypto on/off-ramp up and running quickly
### 1. Integrate the widget
* Configure your integration using the [URL Configuration parameters](/guides/integration/payments/OnOffRamps/GettingStarted/configuration)
* Reference the [Best Practices](/guides/integration/payments/OnOffRamps/GettingStarted/best-practices) guide to set up the UX for your users
### 2. Request a quote
### 3. Conduct a test transaction
### 4. Complete integration review and go to production
Once you have successfully completed a test transaction all that is left is to complete a quick integration review, get your production credentials and go-live
* Reach out to your Sardine contact to request a review + complete any remaining KYB items
* Once the review is completed, you will be given a set of production API keys
* Replace the API keys and you're ready to go live
# Crypto On/Off-ramp Testing Guide
Source: https://docs.payments.sardine.ai/integration_guides/onofframps/testing
This testing guide will allow you to quickly test out your sandbox integration before receiving production keys and going live. Additional testing credentials are provided [here](/guides/integration/payments/Onboarding/testingcredentials)
### 1. Create a new user in sandbox
* Our sandbox user registration is designed to intake any phone number starting with 96 as a test user
* The OTP for the test account will be last 6 digits of the test phone number.
* Example: Phone number is 961-234-5678. OTP will be 345678
* If the test number does not work or does not bring you to the KYC registration page, please try a different number or reach out to Sardine to reset the login.
### 2. Input KYC information
**Name -** Any value
**Date of Birth -** Any value where user is above 18 years old
**Nationality -** Any nationality
**Address -** Any physical address (No P.O. Boxes) that matches the nationality
### 3. Input additional user info
For US Users:
**SSN -** Any random 9 diget number
For Non-US users:
**Questionnaire -** Any answer provided is acceptable
### 4. Link a payment method
Use one of the below testing cards
**Credit / Debit Card**
| Country | Card Type | Card Number |
| ------- | --------- | ---------------- |
| US | Debit | 4659105569051157 |
| FR | Credit | 4010056200000018 |
Other card details:
* Any expiry date in the future
* Any CVV code
* Any postal/zip code
Additional payment methods are provided [here](/guides/integration/payments/Onboarding/testingcredentials)
### 5. Create an order and link the wallet address
Once you've successfully created a new user and linked a payment method, create and then confirm the order.
If you have not passed a wallet address parameter when initializing the widget, please use the following wallet address:
**Low Risk Wallet Address -** 0xaaaaaA1234567890000000000000000000000003
### 5. Confirm Order completion
Once you have confirmed the order, you should see a screen like this.
You can exit or be redirected out of the Sardine widget. Once you have exited the widget you should receive updates on the order status via webhook.
# Web Integration
Source: https://docs.payments.sardine.ai/integration_guides/onofframps/web_integration
Sardine's web checkout widget offers a quick way to integrate our Crypto On/off-Ramp via a URL
This is best suited for quick integrations where the developer does not want to build their own UI. The Sardine Risk SDK is natively integrated into the checkout flow.
**Goal**
By the end of this guide, you should be able to open a new window with Sardine On/Off-ramp, either in a new tab or in a new browser window.
### 1. Obtain authorization token
Before we start, you'll need the following credentials
```
yaml json_schema
$ref: "../../../models/Authorization-Parameters.yaml"
```
You will need to obtain the `clientToken`, which is a unique identifier for each session and user.
Make a POST request to `/client-tokens` using Basic Auth by passing base64 encoding of `:`
```json http theme={null}
{
"method": "POST",
"url": "https://api.sandbox.sardine.ai/v1/auth/client-tokens",
"headers" : {
"Authorization" : "Basic MTY3NDRkZGMtYThhMy00OGIyLWE4ZTktNjA2YWU4OTk1NTM5OmYyMGJhNGRiLTczYzItNDk0Mi04NDAyLWRhNzc4OTllNzY2Mg==",
}
}
```
If the request is successful, you should receive a response that contains the `clientToken` and `expiresAt` field
```
client_token = response["clientToken"]
expires_at = response["expiresAt]
```
### 2. Determine your widget configuration and create URL.
Sardine's widget can be configured based on any parameters that are passed through. Please use [the configuration reference guide](/guides/integration/payments/OnOffRamps/GettingStarted/configuration).
By default, if no parameters are passed, users will be redirected to our standard checkout widget.
`client_token` is the only required parameter. Additional parameters that are passed will autofill the widget for the user and allow them to skip some screens.
**Supported tokens in Sandbox**
Currently Sardine only supports a subset of tokens available in production on its sandbox environment. We recommend using the GET /supported-tokens endpoint to fetch the list of tokens available
### 3. Implement the web checkout widget
Once the URL has been generated, it can be embedded into your web app as a hyperlink. Below, we have included samples of different ways of integrating the checkout
If embedding an iframe, make sure to pass camera and geolocation permission `allow="camera *;geolocation *"` for proper KYC collection
Recommended size is width=500, height=700 for new window
```js theme={null}
import logo from './logo.svg';
import './App.css';
function App() {
const params = `popup,scrollbars=0,resizable=0,status=0,location=0,toolbar=0,menubar=0,width=500,height=700`;
const clientToken = "7ce511d0-c973-4744-b819-d933a248ae51";
const url = `https://crypto.sandbox.sardine.ai/?address=0x10b195F7Be9B120efd05C58f16650A13f533eA33&fiat_amount=1000&asset_type=usdc&network=ethereum&client_token=${clientToken}`
const handleClick = () => {
window.open(url, '_blank', params);
}
return (
//Make sure to allow camera and geolocation access
```
### 4. Confirmation of Order Status
Sardine will fire off events that can be caught by event handlers that can be leveraged to update the user on the order status
There are two ways to get Order status
Developers can poll to the [/orders](https://docs.sardine.ai/docs/integrate-payments/900670db39399-get-order-s) endpoint to check its status with the `order_id`
```json http theme={null}
{
"method": "GET",
"url": "https://api.sandbox.sardine.ai/v1/orders/17983781730123",
"query" : {
"clientToken" : "7ce511d0-c973-4744-b819-d933a248ae51"
},
"headers" : {
"Authorization" : "Basic Y2xpZW50SWQ6Y2xpZW50U2VjcmV0",
}
}
```
Pass a `redirect_url` parameter in the URL when intiating the widget. Once the transaction is complete, Sardine will make a call to the 'redirect\_url'
`Expired` - The user didn't complete the transaction within the time
```json theme={null}
{
"status" : "Expired",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Processed` - The payment is complete
```json theme={null}
{
"status" : "Processed",
"data" : {
"price": 100,
"orderId": "123e4567-e89b-12d3-a456-426614174001",
"transactionFee": 2,
"networkfee": "0.35"
"currencyCode": "usd",
"paymentMethod": "ACH",
"createdAt": 12312321312,
"address": "0x10b195F7Be9B120efd05C58f16650A13f533eA33",
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Declined` - The transaction was declined due to issues with their payment method or risk profile.
```json theme={null}
{
"status" : "Declined",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
For the off ramp to function, the User needs to send crypto to a deposit address generated by Sardine. This address is sent in a frontend event and also via webhook, and can be displayed to the User or a transaction can be created for the User to sign to send the crypto to Sardine.
The Order will be in `draft` status with the following JSON
```json theme={null}
{
"status" : "Draft",
"data" : {
"orderId" : "479813897123-adada89131-12312312",
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c",
"assetType" : "ETH",
"network" : "ethereum",
"total" : "1.2",
"depositAddress" : "0x4713jkadba83131312",
"fiatCurrency" : "USD",
"fiatAmount" : 2200.413
}
}
```
### Track Progress of Order
Once Sardine receives the crypto at the `depositAddress` specified, we will send a webhook to acknowledge receipt of the funds
`Expired` - The user didn't complete the transaction within the `expiration` paramter
```json theme={null}
{
"status" : "Expired",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Processed` - The payment is complete
```json theme={null}
{
"status" : "Processed",
"data" : {
"price": 100,
"orderId": "123e4567-e89b-12d3-a456-426614174001",
"transactionFee": 2,
"networkfee": "0.35"
"currencyCode": "usd",
"paymentMethod": "ACH",
"createdAt": 12312321312,
"address": "0x10b195F7Be9B120efd05C58f16650A13f533eA33",
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
`Declined` - The transaction was declined due to issues with their payment method or risk profile.
```json theme={null}
{
"status" : "Declined",
"data" : {
"referenceId" : "42eadcb0-4a93-45af-9c8c-d295db5aeb6c"
}
}
```
### 5. Testing and Verification
Once the defined URL has been set and triggered, it should open a window that goes through the user flows for Sardine's on ramp.
A developer can use the sample information provided in the [Testing Payment Flows](/guides/integration/payments/Onboarding/testingcredentials) page to test the integration.
### 6. Going Live in Production
Once you have completed end-to-end testing in sandbox, you can go live by swapping to a set of production keys and updating your URLs. Please see the linked guide for a step-by-step of going live in production.
[Going to Production](/guides/integration/payments/OnOffRamps/GettingStarted/goingtoproduction)
You should now be able to route users to the Sardine Checkout Widget via your defined URL where they can instantly buy crypto tokens!
# On/Off-ramp Webhooks
Source: https://docs.payments.sardine.ai/integration_guides/onofframps/webhooks
### Webhook Events
After every order that is confirmed by a user, Sardine will fire off a webhook to a URL designated by the developer.
| webhook | description |
| ---------- | ---------------------------------------------------------------- |
| draft | User has created an order |
| expired | User did not confirm the purchase |
| declined | User attempted a purchase but their payment attempt was declined |
| processing | Order was confirmed, waiting for payment |
| processed | Payment was processed successfully |
| complete | Order was executed and delivered to user |
# Getting Started
Source: https://docs.payments.sardine.ai/overview/getting_started
To get started with Sardine, you'll need an API key. Please contact our team at [sardine.ai/contact](https://www.sardine.ai/contact) to request one.
When reaching out, please include the following details to help us set up your account faster:
### Company Information
* **Business Type:** (e.g., Wallet, Exchange, NFT Marketplace)
* **Location:** Where is your company incorporated?
* **Custody Model:** Are you custodial or non-custodial?
* **Use Case:** Briefly describe how you plan to use Sardine.
* **Licensing:** Are you or your partners subject to regulatory authorization? If yes, please list held licenses.
### Product Interest
* **Products:** (e.g., Crypto On-ramp, Off-ramp, NFT Checkout)
* **Payment Methods:** Which payment methods do you need? (e.g., Card, ACH)
* **Geographies:** Which countries do you need to support?
* **Tokens:** Which tokens/chains do you need to support?
Once we receive your request, our team will review your information and reach out with your sandbox API keys and next steps.
# Introduction to Sardine Payments
Source: https://docs.payments.sardine.ai/overview/intro
A complete Merchant of Record solution with hosted or headless options, powered by Sardine's risk and compliance platform.
Sardine Payments is a complete Merchant of Record (MoR) solution for crypto, Web3, and fiat use cases. We handle onboarding, payments, settlement, and compliance so you can launch fast and scale globally. Choose between our fully hosted checkout or headless APIs for a custom experience.
Our payments are built on Sardine's industry-leading risk and compliance platform. We apply the same fraud, KYC, and AML capabilities used by top banks to every transaction. The result? Higher approval rates, stronger fraud protection, and less operational overhead.
**Traditional payment solutions often struggle with:**
* **High friction** during onboarding that hurts user experience
* **Low approval rates** and high fees that frustrate customers
* **Limited visibility** into performance and decline reasons
## Key Features
We use real-time risk scoring to minimize document checks. Less friction leads to higher conversion and a smoother user experience.
Adaptive risk models allow for higher transaction limits for trusted users. This boosts average order value and retention.
Select the integration that fits your needs. Use our hosted checkout to launch in minutes, or our APIs for a fully custom experience.
Track approvals, conversion, and fraud rates in real-time. Our dashboards give you the insights needed to optimize performance.
# Pricing
Source: https://docs.payments.sardine.ai/overview/pricing
Simple, transparent pricing designed for scale.
Sardine provides flexible pricing tailored to your business model, transaction volume, and geographic coverage. Our pricing is structured to maximize approvals and minimize costs, with fraud protection and compliance included.
### What influences pricing
* **Payment method:** ACH, cards, and local payment rails each have different cost structures.
* **Region and coverage:** Fees may vary depending on where your users are located and which currencies are supported.
* **Volume and limits:** Higher transaction volumes and enterprise-scale flows may qualify for discounted rates.
* **FX conversions:** A small fee may apply when transactions involve currency conversion.
### Next steps
To receive a detailed pricing proposal for your use case, please contact us. Our team will work with you to define the right structure for your integration.
# NFT Checkout
Source: https://docs.payments.sardine.ai/products/nft_checkout
Enable direct NFT purchases with card or bank transfer, powered by Sardine's payments and risk platform.
Sardine's NFT Checkout allows users to buy NFTs directly on marketplaces and minting pages using credit cards or bank transfers. We remove the need for users to acquire crypto elsewhere, handling payments, compliance, and fraud protection entirely in the background.
By embedding Sardine, you eliminate friction for first-time buyers and reduce cart abandonment, all while maintaining robust security controls.
## See it in Action
[Autograph](https://autograph.io/), a leading sports NFT platform, uses Sardine to enable seamless NFT purchases with traditional payment methods.
[](https://www.loom.com/share/e8a08b4584c54ffba9514891cf466306)
## Why Sardine?
**Key benefits of Sardine NFT Checkout:**
* **All-in-One Flow:** We handle payments, compliance, and crypto payouts in a single solution.
* **Higher Approvals:** Advanced fraud models increase acceptance rates while blocking bad actors.
* **Streamlined Experience:** Users purchase instantly without switching platforms or pre-funding wallets.
## Next Steps
* [Contact us](https://www.sardine.ai/contact) to get your sandbox API key.
* Read our [Integration Guides](/integration_guides/nft_checkout/nft_checkout_web) to start building.
# Crypto-to-Fiat Off-Ramp
Source: https://docs.payments.sardine.ai/products/offramp
Instantly convert crypto to fiat and settle funds into bank accounts, backed by Sardine's risk and compliance platform.
Sardine Off-Ramp allows users and businesses to convert digital assets into fiat and access funds quickly via bank account or card. As the Merchant of Record, we handle KYC, compliance, settlement, and fraud liability, letting you focus on your product.
Unlike providers that require manual bank entry or sending crypto to unknown addresses, Sardine embeds a seamless off-ramp experience directly into your application.
## Why Sardine?
**Key benefits of Sardine Off-Ramp:**
* **Faster Experience:** Users link accounts once and transact without manual steps.
* **Instant Payouts:** Funds settle into bank accounts or cards in minutes, not days.
* **Lower Fees:** Efficient risk and payment routing keep costs low and retention high.
* **Enterprise Ready:** Ideal for consumer withdrawals, stablecoin redemptions, and global payouts for fintechs and institutions.
## Next Steps
* [Contact us](https://www.sardine.ai/contact) to get your sandbox API key.
* Read our [Integration Guides](/integration_guides/onofframps/quickstart) to start building.
# Fiat-to-Crypto On-Ramp
Source: https://docs.payments.sardine.ai/products/onramp
A complete Merchant of Record on-ramp for consumer and enterprise crypto purchases, with built-in risk and compliance.
Sardine offers a comprehensive on-ramp solution for both consumers and enterprises. Whether your users are buying crypto via card or bank transfer, or your business is funding stablecoin wallets, we handle the full stack: Merchant of Record, KYC, compliance, payments, and fraud liability.
By combining payments with our industry-leading risk platform, we maximize approval rates, minimize fraud, and ensure a seamless experience for all use cases.
## Why Sardine?
**Key benefits of Sardine On-Ramp:**
* **Higher Approval Rates:** Dynamic risk models and progressive limits allow more trusted users to transact with higher limits.
* **Lower Fees, Less Fraud:** Embedded fraud detection keeps costs down while maintaining robust security.
* **Faster Onboarding:** Reusable KYC reduces friction, streamlining flows for both consumers and enterprises.
## See it in Action
Sardine powers on-ramps for some of the largest wallets and Web3 applications, including MetaMask. We enable users to instantly purchase crypto with higher approvals and fewer declines.
[**Watch how MetaMask users buy crypto with Sardine**](https://metamask.io/)
Beyond consumer wallets, our infrastructure supports fintechs, stablecoin issuers, and institutions for funding treasuries and payout flows at scale.
## Next Steps
* [Contact us](https://www.sardine.ai/contact) to get your sandbox API key.
* Read our [Integration Guides](/integration_guides/onofframps/quickstart) to start building.
# Universal Identity
Source: https://docs.payments.sardine.ai/products/universal_identity
Verify once, transact everywhere. Sardine's reusable identity layer lifts conversion, lowers verification costs, and gives users full control over their data.
Most onboarding flows ask the same user to prove who they are, over and over. **Universal Identity** changes that. Users verify themselves once with Sardine, and from then on can instantly approve a share with any partner in the Sardine network — no repeated document uploads, no repeated selfie checks, no repeated waiting.
For businesses, that means higher approvals, lower compliance costs, and a smoother first impression. For users, it means an interruption-free experience they actually trust.
## Why Universal Identity?
* **Higher conversion.** Every additional onboarding step costs you users. Reusing an existing Sardine verification skips that friction entirely.
* **Lower verification costs.** Pay only when new data is required — repeat users cost a fraction of a fresh KYC run.
* **Stronger fraud signals.** A network-wide view of verified users improves outcomes for every partner.
* **User-owned consent.** Users see exactly what is being shared and with whom, and can revoke at any time.
## How it Works
On their first transaction with a Sardine partner, the user verifies their identity — confirming their personal information, capturing a government ID, and completing a biometric liveness check.
Their verified identity is encrypted and stored on Sardine's platform, ready to be reused with their explicit consent.
The next time the user transacts — with the same partner, or any other Sardine partner — they review and approve the share. Already-verified steps are skipped.
Once approved, the partner receives the verified attributes and the transaction continues without interruption.
### Phone Verification
Every new verification starts with a phone check inside your product. Users enter their number, receive a one-time code, and confirm ownership — never redirected away from your experience.
### Document Verification
For partners that require a government-issued ID, users pick the document type they have on hand and capture it directly in the widget. Sardine verifies the document against official databases before the transaction proceeds.
## What Gets Verified
Universal Identity covers everything you need for compliant onboarding. Partners choose what they require, and users supply only what is missing.
* **Personal information** — Legal name, date of birth, address, email, phone.
* **Government ID** — Driver's license, passport, national ID, or other government-issued documents, verified against official databases.
* **Biometric liveness** — A real-person selfie check, matched against the captured ID.
* **SSN** — Collected for US users when required by regulation.
## A Network That Gets Better with Every User
Once a user is verified, their identity becomes reusable across every Sardine-powered partner they choose to share with. Each successful verification also strengthens the network's fraud signals, which benefits every partner in turn.
* **Frictionless sharing.** When a partner requests information the user has already verified, the user sees exactly what will be shared and can approve in seconds.
* **Smart top-up.** If only part of what's needed is already on file, the user completes only the missing pieces.
## Built for Any Experience
Universal Identity drops into web, mobile web, and native mobile applications. It's available as either an embedded widget that lives inside your existing checkout, or a hosted page that you can link to — whichever fits your experience best.
## Branded for Your Product
Universal Identity is designed to feel like a native part of your product, not a third-party tool.
* **Themed to your brand.** Light, dark, or auto modes; configurable primary color, type, and corner radius; your logo in place of Sardine's.
* **Your language and tone.** Partner name, support links, privacy policy, and terms-of-service references all come from you.
* **Localized for your users.** Available in English, Spanish, Portuguese, French, German, and Japanese.
## Real-Time Status Updates
Your systems stay in sync with each user's verification status automatically. Sardine notifies you the moment a verification completes, fails, or requires review — so you can update transaction state without polling.
## Manage Your Identity, On Your Terms
The Sardine Identity Wallet gives every user a clear, plain-language view of their verified identity and exactly who they've shared it with. They can update their personal information, audit every connected partner, and revoke any sharing at any time — no fine print, no support ticket.
## Benefits at a Glance
* **For Users:** A consistent, interruption-free experience across every Sardine-powered platform, with full control over what is shared.
* **For Businesses:** Higher conversion, lower compliance and verification cost, and access to a growing network of pre-verified users.
* **For the Ecosystem:** A larger pool of verified users strengthens fraud signals and improves outcomes for every partner.
## Next Steps
* [Contact us](https://www.sardine.ai/contact) to enable Universal Identity for your application.
* Ready to build? Read the [Integration Guides](/integration_guides/onboarding_and_testing/client_onboarding) for technical setup.
# FAQ
Source: https://docs.payments.sardine.ai/user/user_faq