# Deposit to Hyperliquid
Source: https://docs.daimo.com/advanced/hyperliquid
Accept payments from any chain and deposit directly to a Hyperliquid account.
Daimo can bridge funds from any chain to a Hyperliquid (Hypercore) account in one session. It uses a [final-call adapter](/advanced/sessions#contract-calls-calldata) on HyperEVM that forwards USDC into Hypercore via Hyperliquid's `CoreDepositWallet`.
## Quick start
```typescript theme={null}
import { hyperEvmUSDC } from "@daimo/sdk/common";
import { encodeFunctionData } from "viem";
const HYPERCORE_DEPOSIT_ADAPTER = "0x3Df610B9168472EfC3CD37ed5005c0e78946c308";
const calldata = encodeFunctionData({
abi: [
{
name: "deposit",
type: "function",
inputs: [
{ name: "recipient", type: "address" },
{ name: "destinationDex", type: "uint32" },
],
outputs: [],
stateMutability: "nonpayable",
},
],
functionName: "deposit",
args: [
"0xRecipientAddress", // Hypercore recipient
0, // 0 = perps, 0xFFFFFFFF = spot
],
});
const res = await fetch("https://api.daimo.com/v1/sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_API_KEY",
},
body: JSON.stringify({
destination: {
type: "evm",
address: HYPERCORE_DEPOSIT_ADAPTER,
chainId: hyperEvmUSDC.chainId,
tokenAddress: hyperEvmUSDC.token,
amountUnits: "10",
calldata,
},
display: { title: "Deposit to Hyperliquid", verb: "Deposit" }
}),
});
```
## Destination DEX
The `destinationDex` parameter controls where funds land on Hypercore.
| DEX | Value | Use case |
| ----- | --------------------------- | -------------------------------- |
| Perps | `0` | Perpetual futures trading margin |
| Spot | `4294967295` (`0xFFFFFFFF`) | Spot trading balances |
## New account fee
Hyperliquid deducts \$1 from the first deposit to a new account, so a \$10 deposit lands as \~\$9 on Hypercore. Factor this into your `amountUnits` if needed.
## Refund address
Always set `refundAddress` when using calldata. If the adapter call reverts, funds go to the refund address and the session status becomes `bounced`.
# KYC Import
Source: https://docs.daimo.com/advanced/kyc-import
Reuse Sumsub KYC from another integration before the user starts a Daimo fiat flow
KYC import lets a user reuse an identity check they've already completed elsewhere. If your product runs Sumsub KYC, or works with a partner who does, share the approved applicant with Daimo before the user enters a fiat flow.
When applicable, this expedites enrollment for a fiat flow without asking users to repeat the same identity check. Daimo imports the applicant into our Sumsub tenant, confirms that its email matches the email asserted by your server, links it to the Daimo account that proves control of that email, and applies the imported review when the user needs fiat access.
Only Daimo can enable KYC import for an organization. [Contact
us](mailto:support@daimo.com) before you use it in production.
## How it works
1. Your Sumsub integration creates a reusable KYC share token for the approved user. See Sumsub's [Reusable KYC via API](https://docs.sumsub.com/docs/reusable-kyc-via-api) docs for token generation.
2. Your server sends the token and your verified user email to Daimo with [`POST /v1/sumsub/import`](/api-reference/import-sumsub-kyc).
3. Daimo imports the applicant and checks that its email matches the email in your request.
4. If a Daimo account with that email exists, Daimo links the import immediately.
5. If the account does not exist yet, Daimo links it when the user signs in with the same email.
Once linked, the user skips repeat KYC in Daimo-hosted fiat flows when the imported review satisfies the rail's requirements.
Learn more about fiat in the [guide](/guides/fiat).
## Import a share token
Call the import endpoint from your backend with your Daimo API key. Never call it from the client.
```bash theme={null}
curl -X POST https://api.daimo.com/v1/sumsub/import \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"shareToken": "SUMSUB_REUSABLE_KYC_SHARE_TOKEN"
}'
```
The response returns a Daimo import ID:
```json theme={null}
{
"kycImport": {
"id": "02ab4563-07ee-4373-8472-9c7dc1027409"
}
}
```
The endpoint is idempotent for the same imported applicant. If the same email is already tied to a different Sumsub applicant, Daimo returns `409 conflict`.
## Email matching
Send the KYC'd user's email after your product has verified it. The imported Sumsub applicant must include the same valid email after case and surrounding whitespace are normalized. Daimo rejects the import if the two values differ.
Sumsub can copy an email value even when the donor or recipient verification level did not confirm it. Do not treat the imported value as proof of email ownership. Daimo links the import only after the user proves control of the same email while signing in.
The user should sign in to Daimo with the same email they used for the original KYC. If they use a different email, Daimo cannot attach the imported review to their account, and the hosted fiat flow may require additional identity verification.
## When to call it
Call KYC import as soon as you have the share token. You do not need to wait for a Daimo session or a fiat payment attempt.
Good times to call it:
* After the user completes KYC in your app.
* During account linking, before showing Daimo fiat as a payment option.
* During a migration or backfill for users who already completed KYC elsewhere.
## Reference
* [Import Sumsub KYC](/api-reference/import-sumsub-kyc) - import a reusable KYC share token.
* [Fiat](/guides/fiat) - hosted fiat flows where imported KYC may be used for expedited verification.
* [Custom Integration](/guides/custom-integration) - create sessions and drive hosted payment methods directly.
# Advanced Sessions
Source: https://docs.daimo.com/advanced/sessions
Prefilled amounts, contract calls, and payment options.
Advanced options for creating sessions via `POST /v1/sessions`. See also: [Create Session](/api-reference/create-session).
## Prefilled Amount (`amountUnits`)
Pass `destination.amountUnits` to lock the deposit to an exact amount. The user skips amount selection and proceeds directly to the payment flow.
```bash theme={null}
curl -X POST https://api.daimo.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": {
"type": "evm",
"address": "0xYourAddress",
"chainId": 8453,
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amountUnits": "25.00"
},
"display": {
"title": "Deposit to Acme",
"verb": "Deposit"
}
}'
```
| Field | Type | Description |
| ------------- | ---------------- | ------------------------------------------------------------------------------------------------------ |
| `amountUnits` | string, optional | Fixed amount in token units (e.g. `"25.00"` for \$25 USDC). When omitted, the user chooses the amount. |
If the user pays slightly less or more (due to bridge fees or price changes), the session still completes. Any variance is visible in the session response and webhooks.
## Contract Calls (`calldata`)
Pass `destination.calldata` to execute an arbitrary contract call with deposited funds. When using `calldata`, `destination.address` is the contract being called.
### How it works
1. Daimo delivers the destination token to the contract
2. A token approval of the deposit amount is made to the contract
3. The contract is called with the provided `calldata`
4. If the call succeeds, the session completes normally
5. **If the call reverts, funds are sent to `refundAddress` instead, and the session status becomes `bounced`**
### Contract requirements
Your contract must handle variable input amounts. Specifically, it should:
1. Check the allowance of the destination token from `msg.sender`
2. Use `transferFrom` to pull the full allowance
This is true **even when `amountUnits` is set**. While over- or under-payments are rare with a fixed amount, they cannot be guaranteed to never happen.
```solidity theme={null}
function deposit(address token, uint256 /* amount */, address recipient) external {
uint256 allowance = IERC20(token).allowance(msg.sender, address(this));
require(allowance > 0, "no allowance");
IERC20(token).transferFrom(msg.sender, address(this), allowance);
_processDeposit(token, allowance, recipient);
}
```
### Example: generating calldata with viem
```typescript theme={null}
import { encodeFunctionData } from "viem";
const calldata = encodeFunctionData({
abi: [
{
name: "deposit",
type: "function",
inputs: [
{ name: "token", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "recipient", type: "address" },
],
outputs: [],
stateMutability: "nonpayable",
},
],
functionName: "deposit",
args: [tokenAddress, amount, recipientAddress],
});
```
```bash theme={null}
curl -X POST https://api.daimo.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": {
"type": "evm",
"address": "0xYourContract",
"chainId": 8453,
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amountUnits": "100.00",
"calldata": "0x..."
},
"display": {
"title": "Stake USDC",
"verb": "Stake"
},
"refundAddress": "0xYourRefundAddress"
}'
```
### Error handling
If your contract call reverts:
* The session status becomes `bounced`
* Funds are sent to the `refundAddress` you specified
Always provide a `refundAddress` when using calldata to ensure funds are recoverable.
## Payment Options (Legacy)
`display.paymentOptions` is legacy. New integrations should use
[`display.paymentMethods`](/guides/payment-methods). The two are mutually
exclusive. This reference is kept for existing integrators.
Optionally pass `display.paymentOptions` to control which payment methods appear and in what order.
When omitted, all available methods are shown.
### Categories
| Option | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `"AllFiat"` | All eligible fiat methods configured for the org |
| `"AllBank"` | All eligible bank fiat methods configured for the org |
| `"AllAddresses"` | Deposit address options for all supported EVM chains |
| `"AllExchanges"` | All exchange options (Coinbase, Binance USDC, Lemon). Excludes opt-in options like `BinanceUSDT`, `CashApp`, and `MtPelerin` |
| `"AllWallets"` | All supported wallet deeplinks (MetaMask, Trust, Phantom, etc.) |
### Fiat
| Option | Description |
| ------------ | -------------------------- |
| `"Interac"` | Interac hosted fiat flow |
| `"ApplePay"` | Apple Pay hosted fiat flow |
| `"ACH"` | ACH hosted fiat flow |
| `"SEPA"` | SEPA hosted fiat flow |
| `"JPYC"` | JPYC hosted fiat flow |
| `"ARS"` | ARS hosted fiat flow |
| `"BreB"` | Bre-B hosted fiat flow |
### Deposit Addresses
| Option | Description |
| ------------ | ------------------------------------------------------------------------------------------------------ |
| `"Tron"` | Tron USDT deposit address; payment-method responses can include an optional Trust Wallet send deeplink |
| `"Arbitrum"` | Arbitrum deposit address |
| `"Base"` | Base deposit address |
| `"Optimism"` | Optimism deposit address |
| `"Polygon"` | Polygon deposit address |
| `"Ethereum"` | Ethereum deposit address |
| `"BSC"` | BSC (BNB Chain) deposit address |
### Exchanges
| Option | Description |
| --------------- | ----------------------------------------- |
| `"Coinbase"` | Coinbase onramp |
| `"BinanceUSDC"` | Binance Connect (USDC withdrawal) |
| `"BinanceUSDT"` | Binance Connect (USDT withdrawal, opt-in) |
| `"Lemon"` | Lemon cash-out |
### Cash App
| Option | Description |
| ----------- | ------------------------------ |
| `"CashApp"` | Cash App payment via Lightning |
### Wallets
| Option | Description |
| ------------------------------ | ---------------------------------------------------------------- |
| `"MetaMask"` | MetaMask wallet |
| `"Trust"` | Trust Wallet with `USDT on Tron` and `Ethereum` choices |
| `"Phantom"` | Phantom wallet |
| `"Rainbow"` | Rainbow wallet |
| `"BaseApp"` | Base (Coinbase Wallet) |
| `"Bitget"` | Bitget wallet |
| `"OKX"` | OKX wallet |
| `"Zerion"` | Zerion wallet |
| `"ConnectedWallet"` | Use already-connected browser wallet (no prompt, errors if none) |
| `"AutoconnectInjectedWallets"` | Auto-connect injected browser wallet |
When `paymentOptions` is omitted, the default is `["AllWallets", "AllExchanges", "AllAddresses"]`. CashApp, Tron, AllFiat, AllBank, and individual chain addresses must be explicitly included.
**Array order** defines display order. A **single option** skips the selection screen (e.g. `["Lemon"]` goes straight to Lemon).
**Nested arrays** control wallet ordering within a group:
```json theme={null}
{
"paymentOptions": ["AllExchanges", ["MetaMask", "Trust", "Phantom"]]
}
```
This shows exchanges first, then a wallet group with MetaMask, Trust, and Phantom in that order. Nested arrays only support wallets.
```bash theme={null}
curl -X POST https://api.daimo.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": {
"type": "evm",
"address": "0xYourAddress",
"chainId": 8453,
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amountUnits": "10.00"
},
"display": {
"title": "Deposit USDC",
"verb": "Deposit",
"paymentOptions": ["Coinbase", "BinanceUSDC"]
}
}'
```
# Check session
Source: https://docs.daimo.com/api-reference/check-session
/openapi.json put /v1/sessions/{sessionId}/check
Checks the current status of a session. Optionally pass a txHash to hint that the user has sent a transaction, which can speed up detection.
# Create payment method
Source: https://docs.daimo.com/api-reference/create-payment-method
/openapi.json post /v1/sessions/{sessionId}/paymentMethods
Sets the payment method for a session. Transitions the session from requires_payment_method to waiting_payment and returns payment data for the selected method. EVM and Tron return receiver addresses, Solana returns a serialized transaction to sign, exchanges return a hosted payment URL plus waiting copy, and fiat returns a hosted URL.
# Create session
Source: https://docs.daimo.com/api-reference/create-session
/openapi.json post /v1/sessions
Creates a new deposit session. Returns the session object including a client secret for client-side operations.
# Create webhook endpoint
Source: https://docs.daimo.com/api-reference/create-webhook-endpoint
/openapi.json post /v1/webhooks
Creates a new webhook endpoint. The response includes the full HMAC signing secret (shown only on creation). See [webhook verification](https://docs.daimo.com/guides/webhooks#verify-signatures) for more information.
# Delete webhook endpoint
Source: https://docs.daimo.com/api-reference/delete-webhook-endpoint
/openapi.json delete /v1/webhooks/{webhookId}
Soft-deletes a webhook endpoint. It will no longer receive events.
# Import Sumsub KYC
Source: https://docs.daimo.com/api-reference/import-sumsub-kyc
/openapi.json post /v1/sumsub/import
Imports reusable Sumsub KYC from a share token after matching its applicant email to the email asserted by the caller.
# List webhook endpoints
Source: https://docs.daimo.com/api-reference/list-webhook-endpoints
/openapi.json get /v1/webhooks
Lists all active webhook endpoints for the authenticated organization. Secrets are redacted.
# API Overview
Source: https://docs.daimo.com/api-reference/overview
Base URL, authentication, errors, and core types
## Base URL
```
https://api.daimo.com
```
## Authentication
Endpoints use Bearer token authentication with your API key:
```
Authorization: Bearer YOUR_API_KEY
```
Some endpoints accept a **client secret** instead, passed in the request body or query string. Each session has its own client secret, returned when you create or retrieve the session. Client secrets are safe for client-side use since they only grant access to their specific session.
| Credential | Format | Use |
| ------------- | ------ | ----------------------------------------------------------- |
| API key | UUID | Server-side. Create sessions, retrieve full details. |
| Client secret | UUID | Client-side, per-session. Set payment method, check status. |
## Error format
All errors return a JSON body:
```json theme={null}
{
"error": {
"type": "validation_error",
"code": "invalid_parameter",
"message": "invalid session create request",
"param": "body"
}
}
```
| Field | Type | Description |
| --------- | --------- | -------------------------------------------------- |
| `type` | `string` | Error category (see below) |
| `code` | `string` | Machine-readable error code |
| `message` | `string` | Human-readable description |
| `param` | `string?` | The parameter that caused the error, if applicable |
### Error types
| Type | Description |
| ----------------------- | --------------------------------------------------------------- |
| `authentication_error` | Missing or invalid credentials |
| `validation_error` | Invalid request body or parameters |
| `invalid_request_error` | Valid request but cannot be processed (e.g. resource not found) |
| `api_error` | Internal server error |
### HTTP status codes
| Code | Meaning |
| ----- | ---------------------------------------- |
| `200` | Success |
| `201` | Created |
| `400` | Bad request, validation error |
| `401` | Unauthorized, missing or invalid API key |
| `404` | Not found, session does not exist |
| `500` | Internal server error |
## Session object
The full session object (returned with API key authentication):
```typescript theme={null}
{
sessionId: string; // 32-character hex ID
status: SessionStatus; // see below
destination: {
type: "evm";
address: string; // checksum-encoded destination address
chainId: number; // e.g. 8453
chainName: string; // e.g. "Base"
tokenAddress: string; // destination token address
tokenSymbol: string; // e.g. "USDC"
amountUnits?: string; // requested amount in token units
calldata?: string; // hex-encoded calldata for contract calls
delivery?: { // set once funds are delivered
txHash: string;
receivedUnits: string;
};
};
display: {
title: string; // e.g. "Deposit to Acme"
verb: string; // e.g. "Deposit"
};
paymentMethod: PaymentMethod | null;
metadata: Record | null;
clientSecret: string; // per-session client credential
createdAt: number; // unix timestamp (seconds)
expiresAt: number; // unix timestamp (seconds)
}
```
Without an API key, `metadata` and `clientSecret` are omitted.
The `destination` above shows the EVM shape. For a Solana destination (USDC only), `destination` is instead:
```typescript theme={null}
{
type: "solana";
address: string; // base58 destination address
tokenAddress: string; // USDC mint (base58)
tokenSymbol: string; // "USDC"
amountUnits?: string; // requested amount in token units
delivery?: { // set once funds are delivered
txHash: string; // Solana transaction signature
receivedUnits: string;
};
}
```
A Solana destination uses the token mint as `tokenAddress`, omits `chainId`, `chainName`, and `calldata`, and its `delivery.txHash` is a Solana transaction signature.
### SessionStatus
| Value | Description |
| ------------------------- | --------------------------------------------------- |
| `requires_payment_method` | Waiting for user to choose how to deposit |
| `waiting_payment` | Payment method set, waiting for deposit transaction |
| `processing` | Deposit detected, routing funds to destination |
| `succeeded` | Funds delivered to destination |
| `bounced` | Delivery failed, funds returned to refund address |
| `expired` | Session timed out |
### PaymentMethod
Three variants based on the source chain:
**EVM:**
```json theme={null}
{
"type": "evm",
"receiverAddress": "0x...",
"source": {
"address": "0x...",
"chainId": 1,
"chainName": "Ethereum",
"tokenAddress": "0x...",
"tokenSymbol": "USDC",
"sentUnits": "10.00",
"txHash": "0x..."
},
"createdAt": 1700000000
}
```
**Tron:**
```json theme={null}
{
"type": "tron",
"receiverAddress": "T...",
"source": {
"address": "T...",
"chainId": 728126428,
"chainName": "tron",
"tokenAddress": "T...",
"tokenSymbol": "USDT",
"sentUnits": "10.00",
"txHash": "..."
},
"createdAt": 1700000000
}
```
**Solana:**
```json theme={null}
{
"type": "solana",
"source": {
"address": "So1...",
"chainId": 501,
"chainName": "solana",
"tokenAddress": "EPjF...",
"tokenSymbol": "USDC",
"sentUnits": "10.00",
"txHash": "..."
},
"createdAt": 1700000000
}
```
The `source` field is populated once the user's deposit transaction is detected. Before that, only `type`, `receiverAddress` (for EVM/Tron), and `createdAt` are present.
EVM and Tron are receiver-address flows. Solana is a sign-and-send flow: instead of a `receiverAddress`, the response returns a transaction payload for the wallet to execute in one click.
### Create payment method response
When creating a payment method, chain-specific fields are returned alongside the session:
* **Tron:** `tron.receiverAddress` (the Tron USDT address to send to), `tron.expiresAt`, and optionally `tron.deeplinks.trustWallet` for opening Trust Wallet's USDT on Tron send flow
* **Solana:** `solana.serializedTx` (hex-encoded serialized transaction for the wallet to sign and submit)
* **EVM:** no additional fields; the deposit address is the session's EVM deposit address
# Retrieve session
Source: https://docs.daimo.com/api-reference/retrieve-session
/openapi.json get /v1/sessions/{sessionId}
Retrieves a session by ID. With an API key, returns the full Session (including metadata and clientSecret). Without an API key, returns SessionPublicInfo only.
# Retrieve webhook endpoint
Source: https://docs.daimo.com/api-reference/retrieve-webhook-endpoint
/openapi.json get /v1/webhooks/{webhookId}
Retrieves a single webhook endpoint by ID. Secret is redacted.
# Send test event
Source: https://docs.daimo.com/api-reference/send-test-event
/openapi.json post /v1/webhooks/{webhookId}/test
Sends a test webhook event to the specified endpoint. Useful for verifying your webhook handler.
# Custom Integration
Source: https://docs.daimo.com/guides/custom-integration
Build your own deposit UI with the API
Use the Daimo API directly to build a custom deposit experience. This works with any language or framework, no React required.
## Overview
The deposit flow has four steps:
1. **Create a session**: server-side, with your API key
2. **Choose a payment method**: client-side, with the client secret
3. **Wait for deposit**: show the deposit address, poll for status
4. **Handle completion**: process the result
## Step 1: Create a session
Create a session on your server. Never expose your API key to the client.
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": {
"type": "evm",
"address": "0xYourAddress",
"chainId": 8453,
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amountUnits": "10.00"
},
"display": {
"title": "Deposit to Acme",
"verb": "Deposit",
"paymentMethods": { "mode": "auto" }
}
}'
```
```typescript fetch theme={null}
const response = await fetch("https://api.daimo.com/v1/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DAIMO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
destination: {
type: "evm",
address: "0xYourAddress",
chainId: 8453,
tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
amountUnits: "10.00",
},
display: {
title: "Deposit to Acme",
verb: "Deposit",
paymentMethods: { mode: "auto" },
},
}),
});
const { session } = await response.json();
```
To deliver to **Solana** (USDC only), use a Solana destination instead: set `type` to `"solana"`, use a base58 wallet `address` and the USDC mint as `tokenAddress`, and omit `chainId` and `calldata`. Everything else (payment methods, polling, webhooks) is identical.
```json theme={null}
"destination": {
"type": "solana",
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"tokenAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amountUnits": "10.00"
}
```
Pass `session.clientSecret` and `session.sessionId` to your client.
## Step 2: Choose a payment method
On the client, call the payment methods endpoint. This transitions the session from `requires_payment_method` to `waiting_payment`.
**EVM deposit** (any EVM chain):
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/sessions/{sessionId}/paymentMethods \
-H "Content-Type: application/json" \
-d '{
"clientSecret": "SESSION_CLIENT_SECRET",
"paymentMethod": { "type": "evm" }
}'
```
```typescript SDK theme={null}
import { createDaimoClient } from "@daimo/sdk/client";
const daimo = createDaimoClient({ baseUrl: "https://api.daimo.com" });
const result = await daimo.sessions.paymentMethods.create(sessionId, {
clientSecret: "SESSION_CLIENT_SECRET",
paymentMethod: { type: "evm" },
});
```
The response includes a `receiverAddress` in `result.session.paymentMethod`. Display this EVM address to the user - they send tokens to it from any supported chain.
```json theme={null}
{
"session": {
"status": "waiting_payment",
"paymentMethod": {
"type": "evm",
"receiverAddress": "0x1a2B3c4D5e6F...",
"createdAt": 1700000000
}
}
}
```
**Tron USDT deposit:**
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/sessions/{sessionId}/paymentMethods \
-H "Content-Type: application/json" \
-d '{
"clientSecret": "SESSION_CLIENT_SECRET",
"paymentMethod": { "type": "tron", "amountUsd": 10.0 }
}'
```
```typescript SDK theme={null}
const result = await daimo.sessions.paymentMethods.create(sessionId, {
clientSecret: "SESSION_CLIENT_SECRET",
paymentMethod: { type: "tron", amountUsd: 10.0 },
});
```
The response includes a `tron.receiverAddress` in `result.session.paymentMethod`, a temporary Tron address. Display it to the user so they can send USDT to it. If `result.tron.deeplinks.trustWallet` is present, you can open that URL to send the same amount from Trust Wallet's USDT on Tron flow.
```json theme={null}
{
"session": {
"status": "waiting_payment",
"paymentMethod": {
"type": "tron",
"receiverAddress": "TXyz1234...",
"createdAt": 1700000000
}
}
}
```
**Solana deposit:**
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/sessions/{sessionId}/paymentMethods \
-H "Content-Type: application/json" \
-d '{
"clientSecret": "SESSION_CLIENT_SECRET",
"paymentMethod": {
"type": "solana",
"walletAddress": "So1ana...",
"inputTokenMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amountUsd": 10.0
}
}'
```
```typescript SDK theme={null}
const result = await daimo.sessions.paymentMethods.create(sessionId, {
clientSecret: "SESSION_CLIENT_SECRET",
paymentMethod: {
type: "solana",
walletAddress: "So1ana...",
inputTokenMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
amountUsd: 10.0,
},
});
```
The response includes `solana.serializedTx`, a hex-encoded serialized Solana transaction for the user's wallet to sign and submit.
This preserves a one-click wallet experience on Solana: swap, transfer, or bridge actions are bundled into one signed transaction. Unlike EVM and Tron, there is no `receiverAddress` for `type: "solana"`.
```json theme={null}
{
"session": {
"status": "waiting_payment",
"paymentMethod": {
"type": "solana",
"createdAt": 1700000000
}
},
"solana": {
"serializedTx": "0xabc123..."
}
}
```
**Fiat:**
The user pays in local currency via one of the fiat rails your org has enabled (e.g. Interac in Canada, ACH or Apple Pay in the US, or SEPA in Europe). Daimo hosts the payment and identity-verification flow at `fiat.hostedUrl`; open it in a [WebView](/guides/webview-native) or new browser tab. The session progresses through `waiting_payment` → `processing` → `succeeded` like any other payment method, and Daimo delivers the stablecoin to your destination once the fiat transfer settles.
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/sessions/{sessionId}/paymentMethods \
-H "Content-Type: application/json" \
-d '{
"clientSecret": "SESSION_CLIENT_SECRET",
"paymentMethod": { "type": "fiat", "fiatMethod": "interac" }
}'
```
```typescript SDK theme={null}
const result = await daimo.sessions.paymentMethods.create(sessionId, {
clientSecret: "SESSION_CLIENT_SECRET",
paymentMethod: { type: "fiat", fiatMethod: "interac" },
});
```
Pass `paymentMethod.fiatMethod` (one of the [supported rails](/guides/fiat#supported-rails), e.g. `interac`) to pin the hosted flow to one rail. If omitted, the hosted page shows every fiat method enabled for the org.
```json theme={null}
{
"session": {
"status": "waiting_payment",
"paymentMethod": {
"type": "fiat",
"fiatMethod": "interac",
"createdAt": 1700000000
}
},
"fiat": {
"hostedUrl": "https://daimo.com/webview?session=...&cs=...",
"fiatMethod": "interac"
}
}
```
The `hostedUrl` is returned only once from the `createPaymentMethod` call; store it on the client. It is not included when retrieving the session later. See [Native WebView](/guides/webview-native) for loading the hosted URL on iOS and Android, or [React Native](/guides/webview) for `DaimoFrameRN`.
Fiat rails are enabled per-org. To request access, [contact
us](mailto:support@daimo.com).
## Step 3: Wait for session completion
Poll for status updates to the session after the user has paid.
```bash curl theme={null}
curl -X PUT https://api.daimo.com/v1/sessions/{sessionId}/check \
-H "Content-Type: application/json" \
-d '{ "clientSecret": "SESSION_CLIENT_SECRET" }'
```
```typescript SDK theme={null}
const { session } = await daimo.sessions.check(sessionId, {
clientSecret: "SESSION_CLIENT_SECRET",
});
console.log(session.status); // "waiting_payment", "processing", "succeeded"
```
If you know the user's transaction hash, pass it as `txHash` to speed up detection.
## Step 4: Handle completion
Check for terminal statuses:
* `succeeded` - funds delivered
* `bounced` - delivery failed (e.g. contract call reverted). Funds returned to refund address.
* `expired` - session timed out
In the `succeeded` and `bounced` case, `destination.delivery.txHash` refers to the settlement transaction.
# Customization
Source: https://docs.daimo.com/guides/customization
Match the Daimo deposit UI to your brand
Customize the Daimo modal and hosted WebView from the Daimo dashboard. The theme applies automatically when the deposit UI loads a session for your org.
Use customization for brand colors, light and dark mode, QR code colors, state colors, and corner radius. No code changes are required for the org default theme.
## Set an org theme
Open **Dashboard > Customization**.
The page has two editors:
* **Theme**: edit tokens with color pickers and a live preview
* **JSON**: import or edit the full theme object directly
Changes autosave. Reset returns the org to Daimo's default modal theme.
## Light and dark mode
A theme contains `light` and `dark` token sets. Choose Automatic, Light, or Dark in the dashboard customization page. Hosted WebViews and SDK modals load the org setting from the session.
## Theme JSON
Use the JSON editor when you want to copy a theme between environments or keep it in your own config.
```json theme={null}
{
"light": {
"bg": "#00000008",
"surface": "#FFFFFF",
"surfaceSecondary": "#F6F7F9",
"surfaceHover": "#F0F2F5",
"skeleton": "#F6F7F9",
"title": "#111827",
"text": "#111827",
"textSecondary": "#6B7280",
"textMuted": "#9CA3AF",
"success": "#16A34A",
"error": "#DC2626",
"warning": "#F97316",
"accent": "#2563EB",
"placeholder": "#D1D5DB",
"border": "#E5E7EB",
"qrBg": "#FFFFFF",
"qrDot": "#111827",
"radiusSm": "0.5rem",
"radiusMd": "0.75rem",
"radiusLg": "1rem",
"radiusXl": "20px"
},
"dark": {
"bg": "#00000008",
"surface": "#111827",
"surfaceSecondary": "#1F2937",
"surfaceHover": "#374151",
"skeleton": "#1F2937",
"title": "#FFFFFF",
"text": "#FFFFFF",
"textSecondary": "#9CA3AF",
"textMuted": "#6B7280",
"success": "#4ADE80",
"error": "#F87171",
"warning": "#FB923C",
"accent": "#60A5FA",
"placeholder": "#6B7280",
"border": "#374151",
"qrBg": "#111827",
"qrDot": "#FFFFFF",
"radiusSm": "0.5rem",
"radiusMd": "0.75rem",
"radiusLg": "1rem",
"radiusXl": "20px"
}
}
```
Colors must be hex, `rgb()`, or `rgba()`. Radius values must use `px` or `rem`.
## Tokens
| JSON key | CSS variable | Controls |
| ------------------ | --------------------------- | ------------------------------- |
| `bg` | `--daimo-bg` | Page backdrop |
| `surface` | `--daimo-surface` | Main modal surface |
| `surfaceSecondary` | `--daimo-surface-secondary` | Rows, inputs, and subtle panels |
| `surfaceHover` | `--daimo-surface-hover` | Hover states |
| `skeleton` | `--daimo-skeleton` | Loading placeholders |
| `title` | `--daimo-title` | Headings |
| `text` | `--daimo-text` | Primary text |
| `textSecondary` | `--daimo-text-secondary` | Descriptions and row subtitles |
| `textMuted` | `--daimo-text-muted` | Muted helper text |
| `success` | `--daimo-success` | Completed and selected states |
| `error` | `--daimo-error` | Failed states |
| `warning` | `--daimo-warning` | Warning and pending states |
| `accent` | `--daimo-accent` | Focus rings and progress |
| `placeholder` | `--daimo-placeholder` | Placeholder and inactive text |
| `border` | `--daimo-border` | Dividers and outlines |
| `qrBg` | `--daimo-qr-bg` | QR code background |
| `qrDot` | `--daimo-qr-dot` | QR code dots |
| `radiusSm` | `--daimo-radius-sm` | Small controls |
| `radiusMd` | `--daimo-radius-md` | Inputs and compact rows |
| `radiusLg` | `--daimo-radius-lg` | Rows and primary actions |
| `radiusXl` | `--daimo-radius-xl` | Modal container |
State backgrounds and checkmark colors are derived from the state tokens.
# Fiat
Source: https://docs.daimo.com/guides/fiat
Accept deposits from local bank accounts and payment rails via a Daimo-hosted flow
Fiat lets your users deposit through supported local bank and wallet rails.
Daimo hosts identity verification and the deposit UI, settles the fiat transfer,
and delivers the stablecoin to your destination. The user pays in their local
currency and never touches crypto.
Fiat rails are part of the same integration. With
[`paymentMethods: { mode: "auto" }`](/guides/payment-methods), the fiat rails
enabled for your org appear automatically in each user's localized picker,
alongside exchanges and wallets.
Daimo enables fiat rails for each organization. [Contact
us](mailto:support@daimo.com) to request access and enable specific rails.
## Supported rails
| Rail | `fiatMethod` | Region | Currency | Verification | Settlement |
| --------- | ------------ | ------------------------------------------ | -------- | ------------------------------------ | ----------------- |
| Interac | `interac` | Canada | CAD | ID and selfie verification · \~5 min | Instant |
| Apple Pay | `apple_pay` | United States | USD | Phone verification · \~1 min | Instant |
| ACH | `ach` | United States | USD | ID verification · \~3 min | 2–3 business days |
| SEPA | `sepa` | Europe (SEPA) | EUR | ID verification · \~3 min | Instant |
| JPYC | `jpyc` | Japan, Singapore, South Korea, Philippines | JPY | No verification | Instant |
| ARS | `ars` | Argentina | ARS | ID verification · \~3 min | Instant |
| Bre-B | `breb` | Colombia | COP | ID and selfie verification · \~5 min | Instant |
A rail appears for a user when it's offered in their region and enabled for
your org.
## Verification
Each rail carries the verification requirement shown above. First-time users
complete it once inside the hosted flow; returning users skip through.
* **No verification**: the user can pay immediately.
* **Phone verification**: a one-time phone confirmation (\~1 min).
* **ID verification**: name, address, and a government-issued ID (\~3 min).
* **ID and selfie verification**: the above plus a selfie check (\~5 min).
Verification happens entirely on the Daimo-hosted page; you never collect or
store identity data. If your users are already verified elsewhere, see
[KYC Import](/advanced/kyc-import).
## Fiat in auto mode
With `paymentMethods: { mode: "auto" }`:
* Bank rails fold into one Bank transfer entry that expands to the rails
available.
* Apple Pay renders as its own entry.
* Users in a country with no enabled fiat rail see crypto, exchange, and wallet
methods instead.
See [Payment Methods](/guides/payment-methods#what-auto-renders) for the full
per-country breakdown.
## How the hosted flow works
When the user picks a fiat rail, they're handed off to a Daimo-hosted page that
walks them through:
1. **Rail selection**: choose the rail. Skipped if the flow is pinned to one.
2. **Identity verification**: first-time users complete the KYC step for that
rail. Returning users skip through.
3. **Payment**: the user pays through their chosen rail, e.g. an Interac
e-Transfer, ACH debit, Apple Pay charge, or SEPA bank transfer.
4. **Confirmation**: once the fiat payment clears, the page confirms success
and the user returns to your app. Daimo delivers the stablecoin to your
destination.
## Integration
### Modal SDK
Set `paymentMethods` when [creating the session](/api-reference/create-session).
`{ mode: "auto" }` includes every fiat rail enabled for the user's country in
the localized picker; `{ mode: "fixed", type }` pins the session to a single
rail.
```typescript theme={null}
await fetch("https://api.daimo.com/v1/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DAIMO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
destination: {
type: "evm",
address: "0xYourAddress",
chainId: 8453,
tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
amountUnits: "25.00",
},
display: {
title: "Deposit to Acme",
verb: "Deposit",
paymentMethods: { mode: "auto" },
// or pin one rail: { mode: "fixed", type: "Interac" }
},
}),
});
```
Fixed `type` values for fiat: `Interac`, `ApplePay`, `ACH`, `SEPA`, `JPYC`, `ARS`, `BreB`.
See [Payment Methods](/guides/payment-methods) for the full reference.
### Auth prefill
If you already know the user's email or phone, include it in session metadata:
```json theme={null}
{
"metadata": {
"email": "account@daimo.com",
"phone": "+14155552671"
}
}
```
Daimo uses these values as sign-in hints for fiat deposits. Daimo Account uses
an email OTP and a user-owned embedded wallet. A hint can skip the email or
phone entry step, but it cannot skip verification. Signed-out users with
`metadata.email` start at email OTP verification. Apple Pay users with
`metadata.phone` start at SMS OTP verification. Phone numbers must use E.164.
If a hint is missing or invalid, Daimo shows the normal entry step.
### Custom integration
If you're not using the modal, drive the flow yourself: create the session,
then call [`POST /v1/sessions/{id}/paymentMethods`](/api-reference/create-payment-method)
with `{ type: "fiat" }` to get back a `fiat.hostedUrl`.
[Render that URL in a WebView, iframe, or new tab](#rendering-the-hosted-url).
The hosted page handles KYC and payment collection, and Daimo delivers the
stablecoin once the fiat transfer clears. See the full four-step flow for
[custom integrations](/guides/custom-integration).
Pass the `clientSecret` returned by [`POST /v1/sessions`](/api-reference/create-session).
`fiat.hostedUrl` is returned **only once** from `POST /paymentMethods`. It is
**not** returned from `GET /v1/sessions/{id}`. Store it on the client as soon
as you receive it.
Here is the fiat-specific [`POST /paymentMethods`](/api-reference/create-payment-method) request and response:
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/sessions/{sessionId}/paymentMethods \
-H "Content-Type: application/json" \
-d '{
"clientSecret": "SESSION_CLIENT_SECRET",
"paymentMethod": { "type": "fiat", "fiatMethod": "interac" }
}'
```
```typescript SDK theme={null}
const result = await daimo.sessions.paymentMethods.create(sessionId, {
clientSecret: "SESSION_CLIENT_SECRET",
paymentMethod: { type: "fiat", fiatMethod: "interac" },
});
```
The response:
```json theme={null}
{
"session": {
"status": "waiting_payment",
"paymentMethod": {
"type": "fiat",
"fiatMethod": "interac",
"createdAt": 1700000000
}
},
"fiat": {
"hostedUrl": "https://daimo.com/webview?session=...&cs=...",
"fiatMethod": "interac"
}
}
```
Open `fiat.hostedUrl` in a WebView or a new browser tab. When the user finishes,
they return to your app; poll the session or use [webhooks](/guides/webhooks)
for the final status.
Omit `fiatMethod` to let the user pick from every rail enabled for your org, or
set it to one of `interac`, `apple_pay`, `ach`, `sepa`, `jpyc`, `ars`, `breb` to pin the flow to a specific one.
| Input | Hosted page behavior |
| -------------------------------------- | ------------------------------------- |
| `{ type: "fiat" }` | Lists every rail enabled for the user |
| `{ type: "fiat", fiatMethod: "ach" }` | Jumps straight into the ACH flow |
| `{ type: "fiat", fiatMethod: "sepa" }` | Jumps straight into the SEPA flow |
## Rendering the hosted URL
`hostedUrl` points to a mobile-friendly Daimo page. Three ways to render it:
* **Native iOS / Android / React Native app** → load in a WebView. The page
posts session events back via `postMessage`. Full reference and code samples
in [Native WebView](/guides/webview-native); on React Native,
[`DaimoFrameRN`](/guides/webview) handles this for you.
* **Web app** → open in a new tab or redirect. No special integration needed.
* **In-page iframe** → append `?layout=embed` to render inline instead of as a
modal.
## Tracking status
Fiat sessions use the same lifecycle, statuses, and webhooks as every other
session. For a full overview, see [Sessions](/guides/sessions#session-lifecycle)
and [Webhooks](/guides/webhooks).
| Session status | What it means for fiat |
| ----------------- | ----------------------------------------------------------------------- |
| `waiting_payment` | Hosted URL is live; waiting for the user to complete the fiat transfer |
| `processing` | Fiat payment confirmed; Daimo is delivering the stablecoin on-chain |
| `succeeded` | Stablecoin delivered to the destination address |
| `bounced` | On-chain delivery reverted (e.g. contract call failure); funds refunded |
| `expired` | User didn't complete the fiat transfer in time |
Subscribe to `session.processing`, `session.succeeded`, and `session.bounced`
via [webhooks](/guides/webhooks) to drive order fulfillment.
## Reference
* [Payment Methods](/guides/payment-methods) - `auto` and `fixed` modes.
* [Create Session](/api-reference/create-session) - `display.paymentMethods`; optionally pass `metadata.email` or `metadata.phone` to skip fiat auth entry.
* [Create Payment Method](/api-reference/create-payment-method) - request body `{ type: "fiat", fiatMethod? }`, response `fiat.hostedUrl`.
* [Native WebView](/guides/webview-native) - load the hosted URL on iOS and Android.
* [React Native](/guides/webview) - embed with `DaimoFrameRN`.
* [KYC Import](/advanced/kyc-import) - reuse existing verified users.
* [Sessions](/guides/sessions#fiat) - payment method shape and lifecycle details.
# Web
Source: https://docs.daimo.com/guides/modal
Pre-built deposit UI for web apps
For web apps, render the hosted Daimo flow with **`DaimoFrame`**: a drop-in
React component that handles the entire deposit, withdraw, or transfer flow,
from chain selection and wallet connection through transaction signing and
status updates. It works without wallet libraries or providers.
`DaimoFrame` supports two layouts:
* **`modal`** (default): a dimmed, full-screen overlay with a rounded sheet
sized to the content. Daimo owns the presentation, including iOS safe areas.
* **`embed`**: an inline iframe rendered exactly where you place the
component. You own the presentation: wrap it in your own modal, drawer, or
page layout.
## Installation
```bash theme={null}
npm install @daimo/sdk
```
Peer dependencies: `react >= 18`.
## Modal layout
1. Create a session on your server (see [Quickstart](/quickstart))
2. Pass `sessionId` and `clientSecret` to ``
```tsx theme={null}
import { DaimoFrame } from "@daimo/sdk/web";
function DepositPage({ sessionId, clientSecret }) {
const [open, setOpen] = useState(true);
if (!open) return null;
return (
setOpen(false)}
/>
);
}
```
The `sessionId` and `clientSecret` come from the session object returned by
`POST /v1/sessions`.
The modal layout renders the flow in an iframe inside a fixed, dimmed overlay
and sizes itself to the content. It handles iOS safe areas for you, with no
extra CSS.
## Embed layout
Use `layout="embed"` to render the deposit flow inside your own UI, such as a
modal or drawer that matches the rest of your app.
```tsx theme={null}
import { DaimoFrame } from "@daimo/sdk/web";
function DepositDialog({ sessionId, clientSecret, onDismiss }) {
return (
);
}
```
Sizing and dismissal:
* The embed fills its container's width; its height follows the content and
animates as the user moves through the flow. Content centers itself at a
maximum width of 512px, so keep the container at or below that width.
* The embed never scrolls internally. If your container has a fixed height,
give it `overflow-y: auto`.
* You own dismissal: render your own close affordance. `onClose` still fires
if the flow closes itself (for example, after a completed deposit).
## Props
| Prop | Type | Default | Description |
| -------------- | -------------------- | ------------------- | --------------------------------------------------------------- |
| `sessionId` | `string` | - | Unique session ID. Sessions are created server-side. (required) |
| `clientSecret` | `string` | - | Unique client secret, returned at session creation. (required) |
| `layout` | `"modal" \| "embed"` | `"modal"` | Presentation layout: Daimo-owned overlay, or inline in your UI. |
| `onClose` | `() => void` | - | Called when the user dismisses the flow. |
| `baseUrl` | `string` | `https://daimo.com` | Hosted flow origin. Override only for staging / self-hosted. |
## Tracking payment status
`DaimoFrame` surfaces dismissal via `onClose`. To act on payment progress, poll
[`GET /v1/sessions/{id}`](/api-reference/retrieve-session) after the session
completes. See [Step 3 of the Quickstart](/quickstart#step-3-handle-completion).
## Inline React components
If you need the flow rendered as native React components (not an iframe), for
example to skip the payment-method picker, use the lower-level **`DaimoModal`**
component. It renders the same flow directly in your page and exposes richer
hooks.
```tsx theme={null}
import { DaimoSDKProvider, DaimoModal } from "@daimo/sdk/web";
import "@daimo/sdk/web/theme.css";
function App({ sessionId, clientSecret }) {
return (
console.log("deposit initiated")}
onPaymentCompleted={() => console.log("deposit succeeded")}
/>
);
}
```
`DaimoModal` must be wrapped in `DaimoSDKProvider` and requires the theme
stylesheet. Selected props:
For recipient-first stablecoin sending, use the sibling
[`DaimoWithdrawal` component](/guides/withdrawals). It collects the recipient
and destination route before creating an open-amount session.
| Prop | Type | Default | Description |
| -------------------------- | --------- | ------- | -------------------------------------------------------------- |
| `sessionId` | `string` | - | Unique session ID. (required) |
| `clientSecret` | `string` | - | Unique client secret. (required) |
| `embedded` | `boolean` | `false` | Render inline instead of as a floating modal. |
| `defaultOpen` | `boolean` | `true` | Whether the modal starts open. |
| `connectToInjectedWallets` | `boolean` | `false` | Skip the payment method picker; auto-connect injected wallets. |
| `connectToAddress` | `Address` | - | Skip the picker; use an already-connected EVM wallet. |
| `returnUrl` | `string` | - | URL to navigate to after successful payment. |
Event handlers: `onPaymentStarted`, `onPaymentCompleted`, `onOpen`, `onClose`.
## Customization
Set your org theme in the Daimo dashboard to match the deposit flow to your
brand. The SDK loads it automatically from the session.
See [Customization](/guides/customization) for the dashboard flow and token
list.
# Payment Methods
Source: https://docs.daimo.com/guides/payment-methods
Localized payment methods for every user, or a single pinned rail
Daimo localizes the deposit experience to the user's country: Apple Pay and
bank transfer in the US, Interac in Canada, SEPA in Europe, Binance and USDT in
Argentina, plus self-custody wallets and deposit addresses everywhere.
Control what the user sees with `display.paymentMethods` when
[creating a session](/api-reference/create-session):
| Mode | Behavior |
| -------------------------------- | ----------------------------------------------------------------------- |
| `{ "mode": "auto" }` | Localized picker: Daimo picks and orders methods per user. Recommended. |
| `{ "mode": "fixed", "type": … }` | Exactly one payment method, no picker. |
Note that `paymentMethods` supersedes the older `display.paymentOptions`
array. Set one or the other, never both; omit both to keep the current default
behavior. See [migrating from paymentOptions](#migrating-from-paymentoptions).
## Auto mode
`{ "mode": "auto" }` renders a payment method picker localized to the user's
country: which methods appear, in what order, and how they're grouped.
```bash theme={null}
curl -X POST https://api.daimo.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": {
"type": "evm",
"address": "0xYourAddress",
"chainId": 8453,
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amountUnits": "25.00"
},
"display": {
"title": "Deposit to Acme",
"verb": "Deposit",
"paymentMethods": { "mode": "auto" }
}
}'
```
### What auto renders
Featured methods by country:
| User country | Localized methods (in order) |
| -------------------- | ------------------------------------------------------------ |
| United States | Apple Pay, Bank transfer, Cash App, Coinbase |
| Canada | Bank transfer, Coinbase |
| Switzerland | Bank transfer, Mt Pelerin |
| Argentina | Bank transfer, Binance USDT / Lemon / Coinbase, USDT on Tron |
| Brazil | Binance USDT / Coinbase, USDT on Tron |
| Colombia | Bank transfer, Binance USDT / Coinbase, USDT on Tron |
| Mexico | Binance USDT / Coinbase, USDT on Tron |
| European Union | Bank transfer, Binance USDC / Coinbase |
| Japan | Bank transfer, Binance USDT, USDT on Tron |
| Singapore | Bank transfer, Binance USDT / Coinbase, USDT on Tron |
| South Korea | Bank transfer, Binance USDT / Coinbase, USDT on Tron |
| Philippines | Bank transfer, Binance USDT / Coinbase, USDT on Tron |
| Other / unrecognized | Binance USDT / Coinbase, USDT on Tron |
Revolut Ramp is shown only for open-amount sessions. Coinbase is available
broadly except in Japan. Runtime provider availability can also remove a method
from the featured row.
Beyond the featured methods, users can always open the full list: self-custody
wallets, deposit addresses on every supported chain, and more exchanges.
Bank rails fold into a single Bank transfer entry that expands to the rails
available in the user's country. Fiat rails appear only when enabled for your
org; see [Fiat](/guides/fiat).
## Fixed mode
`{ "mode": "fixed", "type": … }` pins the session to one payment method. The
user skips the picker and goes straight into that flow. Use it when you already
know how the user should pay: a Coinbase-only flow, a bank-transfer-only
deposit.
```bash theme={null}
curl -X POST https://api.daimo.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination": {
"type": "evm",
"address": "0xYourAddress",
"chainId": 8453,
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amountUnits": "25.00"
},
"display": {
"title": "Deposit to Acme",
"verb": "Deposit",
"paymentMethods": { "mode": "fixed", "type": "Coinbase" }
}
}'
```
### Fixed types
| `type` | Payment method |
| ----------------- | ----------------------------------------- |
| `ConnectedWallet` | The user's connected browser wallet |
| `Interac` | Interac bank transfer (Canada) |
| `ACH` | ACH bank transfer (US) |
| `SEPA` | SEPA bank transfer (Europe) |
| `ApplePay` | Apple Pay (US) |
| `CashApp` | Cash App (US) |
| `Coinbase` | Coinbase |
| `Binance` | Binance |
| `Lemon` | Lemon (Argentina) |
| `BitgetExchange` | Bitget |
| `BybitExchange` | Bybit |
| `MtPelerin` | Mt Pelerin (Switzerland) |
| `Tron` | USDT deposit on Tron |
| `ARS` | ARS bank transfer (Argentina) |
| `BreB` | Bre-B transfer (Colombia) |
| `JPYC` | JPYC transfer (Japan and more) |
| `RevolutRamp` | Revolut crypto ramp (EEA and Switzerland) |
A fixed session accepts only matching
[`POST /paymentMethods`](/api-reference/create-payment-method) requests: a
session fixed to `Coinbase` rejects `{ "type": "fiat" }`.
Deeplink-only methods such as `RevolutRamp` navigate directly and do not call
`POST /paymentMethods`.
Fixing to a fiat type still requires that rail to be enabled for your org. See
[Fiat](/guides/fiat).
## Migrating from paymentOptions
`display.paymentOptions` keeps working for existing integrations. New
integrations should use `paymentMethods`:
| If you set… | Use instead |
| -------------------------------------------- | ----------------------------------------------------- |
| Nothing | `paymentMethods: { mode: "auto" }` |
| `paymentOptions: ["Coinbase"]` (single rail) | `paymentMethods: { mode: "fixed", type: "Coinbase" }` |
| A curated multi-option array | `paymentMethods: { mode: "auto" }` |
The full legacy reference lives in
[Advanced Sessions](/advanced/sessions#payment-options-legacy).
## Reference
* [Create Session](/api-reference/create-session) - `display.paymentMethods`.
* [Fiat](/guides/fiat) - supported rails, verification, and org configuration.
* [Sessions](/guides/sessions) - lifecycle, statuses, and the credential model.
* [Advanced Sessions](/advanced/sessions#payment-options-legacy) - legacy `paymentOptions` reference.
# Sessions
Source: https://docs.daimo.com/guides/sessions
Core concept: session lifecycle, statuses, and credential model
A **session** represents a single deposit attempt. It tracks the full lifecycle from creation through delivery.
## Session lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> requires_payment_method: Create session
requires_payment_method --> waiting_payment: Set payment method
waiting_payment --> processing: Deposit detected
processing --> succeeded: Funds delivered
processing --> bounced: Delivery failed
requires_payment_method --> expired: Timeout
waiting_payment --> expired: Timeout
```
### Statuses
| Status | Description |
| ------------------------- | ------------------------------------------------------------------------------- |
| `requires_payment_method` | Session created, waiting for the user to choose how to pay |
| `waiting_payment` | Payment method set, waiting for the user's deposit transaction |
| `processing` | Deposit detected, funds are being routed to the destination |
| `succeeded` | Funds delivered to the destination address |
| `bounced` | Delivery failed (e.g. contract call reverted), funds returned to refund address |
| `expired` | Session timed out before a deposit was received |
Terminal statuses: `succeeded`, `bounced`, `expired`. Once terminal, a session cannot change status.
## Credential model
Daimo uses two types of credentials:
### API key
Your account-wide API key. Use it **server-side only** for operations that require full access:
* Creating sessions
* Retrieving full session details (including `clientSecret` and `metadata`)
Pass it as a Bearer token:
```
Authorization: Bearer
```
### Client secret
A per-session token returned when you create a session. It grants limited access to that single session:
* Setting the payment method
* Checking session status
The client secret is safe to expose in browser code. Pass it in the request body (or query string for GET requests):
```json theme={null}
{ "clientSecret": "1f56acb2-c3d0-4632-98be-e5b2b9a4c1de" }
```
## Session object
When retrieved with an API key, the full session includes:
| Field | Type | Description |
| --------------- | ---------------- | ------------------------------------- |
| `sessionId` | `string` | Unique 32-character hex ID |
| `status` | `string` | One of the statuses above |
| `destination` | `object` | Where funds are delivered (see below) |
| `display` | `object` | UI metadata: `title`, `verb` |
| `paymentMethod` | `object \| null` | How the user is paying (see below) |
| `metadata` | `object \| null` | Key-value pairs set at creation |
| `clientSecret` | `string` | Per-session client credential |
| `createdAt` | `number` | Unix timestamp (seconds) |
| `expiresAt` | `number` | Unix timestamp (seconds) |
Without an API key, `metadata` and `clientSecret` are omitted (returns `SessionPublicInfo`).
### Destination
```typescript theme={null}
{
type: "evm",
address: "0x...", // destination address
chainId: 8453, // e.g. Base
chainName: "Base",
tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // e.g. USDC
tokenSymbol: "USDC",
amountUnits: "10.00", // requested amount (optional)
delivery: { // set once funds are delivered
txHash: "0x...",
receivedUnits: "10.00"
}
}
```
Deposits can also be delivered to **Solana** (USDC only):
```typescript theme={null}
{
type: "solana",
address: "7xKX...", // base58 destination address
tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
tokenSymbol: "USDC",
amountUnits: "10.00", // requested amount (optional)
delivery: { // set once funds are delivered
txHash: "5Uu...", // Solana transaction signature
receivedUnits: "10.00"
}
}
```
A Solana destination uses the token mint as `tokenAddress`, omits `chainId`, `chainName`, and `calldata`, and its `delivery.txHash` is a Solana transaction signature.
### Payment methods
Choose the payment method experience when creating the session with
[`display.paymentMethods`](/guides/payment-methods):
* `{ "mode": "auto" }` shows Daimo's localized payment picker. This is the
recommended default.
* `{ "mode": "fixed", "type": … }` pins the session to one method and skips
the picker.
Use `paymentMethods` for new integrations; it supersedes the legacy
`display.paymentOptions` array.
After the user chooses a method (or enters a fixed flow), the modal calls
`POST /v1/sessions/{id}/paymentMethods`. This records the concrete method,
transitions the session to `waiting_payment`, and returns the information needed
to complete the deposit. If you build your own UI, call this endpoint directly.
The payment instructions depend on the concrete method. Common response shapes
are shown below.
#### EVM
Deposit from any of our [supported EVM chains](/supported-chains#destination-chains). The response includes a single-use `receiverAddress` in `session.paymentMethod`. Display this address so the user can send funds to it.
```json theme={null}
{ "type": "evm", "receiverAddress": "0x...", "createdAt": 1700000000 }
```
#### Tron
Deposit USDT from Tron. The response includes a `tron.receiverAddress`, a single-use Tron address where the user sends USDT. It can also include `tron.deeplinks.trustWallet`, a Trust Wallet send link for the same address and amount.
```json theme={null}
{ "type": "tron", "receiverAddress": "T...", "createdAt": 1700000000 }
```
#### Solana
Deposit from Solana. The response includes `solana.serializedTx`, a hex-encoded serialized transaction for the wallet to sign and submit.
This preserves a one-click wallet flow on Solana by bundling actions into one signed transaction.
```json theme={null}
{ "type": "solana", "createdAt": 1700000000 }
```
#### Fiat
Hosted fiat deposit. With `paymentMethods: { mode: "auto" }`, eligible fiat
rails enabled for your org appear automatically in the user's localized picker.
To offer exactly one rail, set `display.paymentMethods` to a fixed selector when
creating the session:
```json theme={null}
{ "mode": "fixed", "type": "Interac" }
```
The user pays in local currency and Daimo delivers the stablecoin to your
destination using the same lifecycle as every other payment method.
The response includes `fiat.hostedUrl`, a URL to a Daimo-hosted page that handles identity verification and the fiat transfer. Open it in a native [WebView](/guides/webview-native) or a new browser tab; the user returns to your app after completing the flow.
```json theme={null}
{ "type": "fiat", "fiatMethod": "interac", "createdAt": 1700000000 }
```
```json theme={null}
{
"fiat": {
"hostedUrl": "https://daimo.com/webview?session=...&cs=...",
"fiatMethod": "interac"
}
}
```
For a custom UI, call `POST /paymentMethods` with `{ "type": "fiat" }` and
optionally a concrete `fiatMethod`. A fixed session requires the matching method
(for example, fixed `Interac` requires `fiatMethod: "interac"`). An auto session
may omit `fiatMethod` to let the hosted page show the enabled rails.
See [Fiat](/guides/fiat#custom-integration) for the request and response, fixed
type mapping, and supported rails.
The `hostedUrl` is returned only once from `createPaymentMethod`; store it on the client. It is not included when retrieving the session later.
Fiat rails are enabled per-org. To request access, [contact
us](mailto:support@daimo.com).
Each payment method gains a `source` object once the user's deposit transaction is detected, containing chain info, token details, and the source transaction hash.
See [Create Payment Method](/api-reference/create-payment-method) for full request/response details.
## Customization
Use the Daimo dashboard to match the modal and hosted WebView to your brand. The org theme applies automatically when the deposit UI loads a session for your org.
See [Customization](/guides/customization) for the dashboard flow and JSON format.
## Polling for status
Use `PUT /v1/sessions/{id}/check` with the client secret to poll for status updates after the user has paid. This is how the modal tracks deposit progress, and how custom integrations can monitor sessions from the client side.
For most integrations, the modal handles polling automatically.
# Webhooks
Source: https://docs.daimo.com/guides/webhooks
Get notified when sessions change status
Webhooks let your server receive real-time notifications when a session transitions to a new status. Instead of polling, Daimo sends a `POST` request to your endpoint with the event payload.
## Event types
| Event | Session status | Trigger | Example use case |
| -------------------- | -------------- | ------------------------------------ | ------------------------------- |
| `session.processing` | `processing` | Deposit detected, funds being routed | Show "payment received" to user |
| `session.succeeded` | `succeeded` | Funds delivered to destination | Fulfill the order, send receipt |
| `session.bounced` | `bounced` | Delivery failed, funds refunded | Alert support, notify customer |
Subscribe to all events with `["*"]` or pick specific types.
## Quickstart
### 1. Register an endpoint
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/daimo",
"events": ["*"]
}'
```
```typescript fetch theme={null}
const response = await fetch("https://api.daimo.com/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DAIMO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/webhooks/daimo",
events: ["*"],
}),
});
const { webhook } = await response.json();
// Save webhook.secret; you'll need it to verify signatures
console.log(webhook.secret);
```
The response includes a `secret`. Store it securely, you'll use it to verify that incoming requests are from Daimo.
### 2. Handle events
Set up a route on your server to receive webhook events:
```typescript theme={null}
import { createServer } from "node:http";
const server = createServer((req, res) => {
if (req.method === "POST" && req.url === "/webhooks/daimo") {
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const event = JSON.parse(body);
// TODO: verify signature (see below)
switch (event.type) {
case "session.succeeded":
// Handle successful delivery
break;
case "session.bounced":
// Handle failed delivery
break;
}
res.writeHead(200).end();
});
}
});
server.listen(4242);
```
### 3. Send a test event
Verify your endpoint is working by sending a test event:
```bash curl theme={null}
curl -X POST https://api.daimo.com/v1/webhooks/{webhookId}/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"eventType": "session.succeeded"}'
```
```typescript fetch theme={null}
await fetch(`https://api.daimo.com/v1/webhooks/${webhookId}/test`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DAIMO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ eventType: "session.succeeded" }),
});
```
Test events include `isTestEvent: true` in the payload so you can filter them out of your business logic.
## Verify signatures
Every webhook delivery includes a `Daimo-Signature` header for verifying authenticity. Always verify signatures in production to ensure requests are from Daimo.
### How it works
The signature header looks like this:
```
Daimo-Signature: t=1700000000,v1=5257a869...
```
To verify a webhook:
1. **Read the raw body.** Don't parse JSON first; you need the exact bytes.
2. **Extract `t` and `v1`** from the `Daimo-Signature` header by splitting on `,` and `=`.
3. **Compute HMAC-SHA256** of `${t}.${rawBody}` using your webhook secret.
4. **Compare** the computed signature to `v1` using `crypto.timingSafeEqual`.
5. **Reject stale timestamps.** If `t` is more than 5 minutes old, discard the event to prevent replay attacks.
### Full verification function
```typescript theme={null}
import * as crypto from "crypto";
const TIMESTAMP_TOLERANCE_SEC = 300; // 5 minutes
function verifyWebhookSignature(
secret: string,
signatureHeader: string,
rawBody: string,
): boolean {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => {
const [k, ...v] = p.split("=");
return [k, v.join("=")];
}),
);
const ts = parts["t"];
const sig = parts["v1"];
if (!ts || !sig) return false;
const tsNum = parseInt(ts, 10);
if (isNaN(tsNum)) return false;
const age = Math.abs(Math.floor(Date.now() / 1000) - tsNum);
if (age > TIMESTAMP_TOLERANCE_SEC) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
try {
return crypto.timingSafeEqual(
Buffer.from(sig, "hex"),
Buffer.from(expected, "hex"),
);
} catch {
return false;
}
}
```
### Complete handler with verification
```typescript theme={null}
import { createServer } from "node:http";
import * as crypto from "crypto";
const WEBHOOK_SECRET = process.env.DAIMO_WEBHOOK_SECRET!;
const server = createServer((req, res) => {
if (req.method === "POST" && req.url === "/webhooks/daimo") {
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const signature = req.headers["daimo-signature"] as string;
if (!verifyWebhookSignature(WEBHOOK_SECRET, signature, body)) {
res.writeHead(400).end("invalid signature");
return;
}
const event = JSON.parse(body);
switch (event.type) {
case "session.succeeded":
// Handle successful delivery
break;
case "session.bounced":
// Handle failed delivery
break;
}
res.writeHead(200).end();
});
}
});
server.listen(4242);
```
## Event payload
### Field reference
| Field | Type | Description |
| -------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | `string` | Unique event ID (UUID). Use for idempotency. |
| `type` | `string` | One of `session.processing`, `session.succeeded`, `session.bounced` |
| `createdAt` | `number` | Unix timestamp (seconds) when the event was created |
| `data.session` | `object` | Session snapshot at event time. Same shape as the [session object](/guides/sessions#session-object), without `clientSecret`. A processing snapshot also has `destination.expectedUnits`. |
| `isTestEvent` | `boolean` | `true` for test events sent via `/test` endpoint. Omitted for real events. |
For `session.processing`, `destination.expectedUnits` is Daimo's best current
estimate of the amount that the destination will receive, in destination token
units. It can differ from the final amount because of price movement, swap
execution, slippage, fees, or subsidy availability. After delivery,
`destination.delivery.receivedUnits` is the authoritative amount.
```json theme={null}
{
"type": "session.processing",
"data": {
"session": {
"status": "processing",
"destination": {
"tokenSymbol": "USDT",
"expectedUnits": "5.791457"
}
}
}
}
```
Here's an example of a `session.succeeded` event:
```json theme={null}
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "session.succeeded",
"createdAt": 1700000000,
"data": {
"session": {
"sessionId": "abcdef1234567890abcdef1234567890",
"status": "succeeded",
"destination": {
"type": "evm",
"address": "0x...",
"chainId": 8453,
"chainName": "Base",
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"tokenSymbol": "USDC",
"amountUnits": "10.00",
"delivery": {
"txHash": "0x...",
"receivedUnits": "10.00"
}
},
"display": {
"title": "Deposit to Acme",
"verb": "Deposit"
},
"paymentMethod": {
"type": "evm",
"receiverAddress": "0x...",
"createdAt": 1700000000
},
"metadata": { "myUserId": "user_123" },
"createdAt": 1700000000,
"expiresAt": 1700003600
}
}
}
```
## Delivery behavior
Every delivery includes these headers:
| Header | Description |
| ----------------- | ------------------------------------------------------------------------------ |
| `Content-Type` | `application/json` |
| `Daimo-Signature` | `t=,v1=` (see [Verify signatures](#verify-signatures)) |
* Daimo waits **10 seconds** for your server to respond.
* Any **2xx** status code counts as success.
* Failed deliveries are retried with **exponential backoff**: the n-th retry waits 2^(n-1) minutes.
* After **10 failed attempts**, the event is marked as failed and no further retries are made.
## Test events
Use `POST /v1/webhooks/{webhookId}/test` to send a test event. You can optionally specify an `eventType` parameter (defaults to `session.succeeded`).
Test events contain `isTestEvent: true` in the payload. Use this flag to skip business logic during testing.
## Best practices
* **Return 200 quickly.** Process events asynchronously if your handler does heavy work. Daimo times out after 10 seconds.
* **Verify signatures.** Always verify the `Daimo-Signature` header in production to confirm requests are from Daimo.
* **Handle test events.** Check `event.isTestEvent` and skip side effects (e.g. order fulfillment) for test events.
* **Be idempotent.** Daimo may deliver the same event more than once. Log processed event IDs and skip duplicates. The `event.id` uniquely identifies each event.
# React Native
Source: https://docs.daimo.com/guides/webview
Embed the Daimo deposit UI in a React Native app
**`DaimoFrameRN`** renders the hosted Daimo deposit, withdraw, or transfer flow
inside a `react-native-webview`, covering chain selection, wallet connection,
signing, and status updates.
If you're not using React Native, see [Native WebView](/guides/webview-native)
to embed the same flow with a raw iOS `WKWebView`, Android `WebView`, or a
plain `react-native-webview`.
## Install
```bash theme={null}
npm install @daimo/sdk react-native-webview
```
`react-native-webview` is a peer dependency. `@daimo/sdk/native` imports nothing
from the web build, so no `react-dom` or wallet libraries are pulled in.
## Modal layout
Create a session on your server (see [Quickstart](/quickstart)), pass its
`sessionId` and `clientSecret` to the client, then render `DaimoFrameRN`:
```tsx theme={null}
import { DaimoFrameRN } from "@daimo/sdk/native";
function DepositButton({ sessionId, clientSecret }) {
const [open, setOpen] = useState(false);
return (
<>