Privy x Due recipe

Using Privy x Due to Move Between Fiat and Stablecoins


This guide walks you through integrating Privy embedded wallets with the Due Network API to seamlessly move value between fiat bank accounts and stablecoins.

Privy provides secure, app-embedded wallets and signing flows, while Due offers global payments infrastructure connecting real-time fiat rails (ACH, SEPA, PIX, Faster Payments, etc.) with stablecoin rails (USDC, EURC, USDT, etc.).

By combining the two, you can:

  • Create and manage customer accounts in Due
  • Provision and link Privy wallets to those accounts
  • Execute crypto → fiat transfers (with Privy signing transfer intents)
  • Execute fiat → crypto transfers (with Due providing banking instructions)
  • Issue virtual accounts in fiat (e.g. virtual EUR IBAN or AED account details)
  • This guide shows the end-to-end flow for both directions, with code samples for each step.

Overview

Due Network provides an API for executing transfers between fiat and cryptocurrency. To complete a transfer, you need to:

  1. Create an account and link a wallet in Due
  2. Create a transfer via Due API
  3. For crypto → fiat transfers: sign the transfer intent using Privy embedded wallet via REST API
  4. Submit the signed intent back to Due API

For more details on the transfer process, see Due Transfer Flow and Recipients.

Prerequisites

Due Platform API

To use the Due API, you need to:

  1. Obtain a Platform API key from Due Network
  2. Create an account for your user, accept TOS, and complete KYC

How to get access: Contact the Due Network team to obtain Platform API credentials: [contact link]

After gaining access, you will have:

  • API_KEY - your API key for authorization

Privy API Setup

To use the Privy REST API, you will need:

  1. Privy App ID - your application identifier
  2. Privy App Secret - secret key for authorization

These can be found in your Privy application settings (App settings > Basics).

For more information about Privy Wallets, see Privy Wallets Overview.

Step 1: Create a Customer Account in Due

First, create a customer account for your user. This can be either an individual or a business.

For detailed information on creating accounts, see the Due documentation.

curl --request POST \
  --url https://api.due.network/v1/accounts \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "type": "individual",
    "email": "[email protected]",
    "details": {
      "firstName": "John",
      "lastName": "Doe"
    }
  }'

Response:

{
  "id": "acc_abc123",
  "type": "individual",
  "email": "[email protected]",
  "status": "active",
  "details": {
    "firstName": "John",
    "lastName": "Doe"
  }
}

Save the id - this is the user's ACCOUNT_ID.

Step 2: Create or Get a Privy Wallet

Create an embedded wallet for the user via Privy API.

For more details, see Privy: Create a Wallet.

Creating a new wallet

curl --request POST \
  --url https://api.privy.io/v1/wallets \
  -u "<your-privy-app-id>:<your-privy-app-secret>" \
  --header 'privy-app-id: <your-privy-app-id>' \
  --header 'Content-Type: application/json' \
  --data '{
    "owner": {
      "user_id": "did:privy:clxduz8al00kql00fva24ggty"
    },
    "chain_type": "ethereum"
  }'

Response:

{
  "id": "i46lv902bx21l4htkva9hjlo",
  "address": "0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
  "chain_type": "ethereum",
  "policy_ids": [],
  "additional_signers": [],
  "exported_at": null,
  "imported_at": null,
  "created_at": 1759419543675,
  "owner_id": null
}

Save the id (wallet_id) and address - they will be needed for the next steps.

Step 3: Link Wallet to Due Account

Now link the Privy wallet to the Due account. This is necessary for executing transfers.

For more details on linking wallets, see the Due Wallets documentation.

curl --request POST \
  --url https://api.due.network/v1/wallets \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "address": "evm:0x1234567890123456789012345678901234567890"
  }'

Response:

{
    "id": "wlt_e3lLDBYiPMHxCv1Q",
    "address": "evm:0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
}

Scenario 1: Crypto → Fiat

Sending cryptocurrency from your wallet to a bank account.

Step 1: Create a Recipient (bank account)

curl --request POST \
  --url https://api.due.network/v1/recipients \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "name": "Marie Dubois",
    "details": {
      "schema": "bank_sepa",
      "accountType": "individual",
      "firstName": "Marie",
      "lastName": "Dubois",
      "IBAN": "FR1420041010050500013M02606"
    },
    "isExternal": true
  }'

Response:

{
  "id": "rcp_fRlKXtbmyzvRwmY9",
  "label": "Marie Dubois",
  "details": {
    "IBAN": "FR1420041010050500013M02606",
    "accountType": "individual",
    "firstName": "Marie",
    "lastName": "Dubois",
    "schema": "bank_sepa"
  },
  "isExternal": true,
  "isActive": true
}

Step 2: Get a Quote

curl --request POST \
  --url https://api.due.network/v1/transfers/quote \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
  "source": {
    "rail": "base",
    "currency": "USDC"
  },
  "destination": {
    "rail": "sepa",
    "currency": "EUR",
    "amount": "1000"
  }
}'

Response:

{
    "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
    "source": {
        "rail": "base",
        "currency": "USDC",
        "amount": "1177.171875",
        "fee": "5.296875"
    },
    "destination": {
        "rail": "sepa",
        "currency": "EUR",
        "amount": "1000",
        "fee": "4.52"
    },
    "fxRate": 1.1718750000000002,
    "fxMarkup": 5,
    "expiresAt": "2025-10-02T16:40:31.951762984Z"
}

Important: Quotes are valid for 2 minutes. Create a quote immediately before creating the transfer.

Step 3: Create a Transfer

curl --request POST \
  --url https://api.due.network/v1/transfers \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "quote": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
    "sender": "wlt_e3lLDBYiPMHxCv1Q",
    "recipient": "rcp_fRlKXtbmyzvRwmY9",
    "memo": "Invoice#1"
  }'

Response:

{
  "id": "tf_2ghR4UIA56KEGN",
  "ownerId": "acct_DkRHlTg5q8VZM6Gn",
  "status": "awaiting_funds",
  "source": {
    "amount": "1177.171875",
    "fee": "5.296875",
    "currency": "USDC",
    "rail": "base"
  },
  "destination": {
    "amount": "1000",
    "fee": "4.52",
    "currency": "EUR",
    "rail": "sepa",
    "id": "rcp_fRlKXtbmyzvRwmY9",
    "label": "Marie Dubois",
    "details": {
      "IBAN": "FR1420041010050500013M02606",
      "accountType": "individual",
      "firstName": "Marie",
      "lastName": "Dubois",
      "schema": "bank_sepa"
    }
  },
  "fxRate": 1.1718750000000002,
  "fxMarkup": 5,
  "transferInstructions": {
    "kind": "transfer_intent",
    "treasury": "evm:0xbad09Fb9781D9D57E1423231AC51f9bb9e0CABAD"
  },
  "memo": "Invoice#1",
  "createdAt": "2025-10-02T16:38:44.222337642Z",
  "expiresAt": "2025-10-02T18:38:44.222337208Z"
}

Option A: Using Transfer Intent (Signature-based)

Step 4: Create a Transfer Intent

curl --request POST \
  --url https://api.due.network/v1/transfers/tf_2ghR4UIA56KEGN/transfer_intent \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>'

The API will return a transfer intent with a signables array - usually two objects: Permit and PayoutIntent.

{
    "id": "ti_24QbulYAT9nfjU",
    "ownerId": "acct_DkRHlTg5q8VZM6Gn",
    "sender": "evm:0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
    "amountIn": "1177.29598",
    "to": {
        "evm:0xbad09Fb9781D9D57E1423231AC51f9bb9e0CABAD": "1177.29598"
    },
    "tokenIn": "USDC",
    "tokenOut": "USDC",
    "networkIdIn": "base",
    "networkIdOut": "base",
    "gasFee": "0",
    "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiMHgxYTM5YWJmZWNiMjdlMjA2MzA5OWNkOTg2ZWRiODllOTJkY2VhNDNiMDdhYmQ5NGI3MWNkZDZkMmFkM2U5NTRhIiwiZXhwIjoxNzU5NDI0NDAyLCJpYXQiOjE3NTk0MjM1MDIsImp0aSI6InRpXzI0UWJ1bFlBVDluZmpVIn0.QBNIttR32pp-lcXaIyYCk0N5xJ52Igzh_AUFX34Ll4s5vbFkuMPkzP6FG_zXbhK0b8X-SWyJe-ibVpRXYrRdCg",
    "signables": [
        {
            "payload": {
                "kind": "typed_data",
                "value": {
                    "types": {
                        "Permit": [
                            {
                                "name": "owner",
                                "type": "address"
                            },
                            {
                                "name": "spender",
                                "type": "address"
                            },
                            {
                                "name": "value",
                                "type": "uint256"
                            },
                            {
                                "name": "nonce",
                                "type": "uint256"
                            },
                            {
                                "name": "deadline",
                                "type": "uint256"
                            }
                        ]
                    },
                    "primaryType": "Permit",
                    "domain": {
                        "name": "USDC",
                        "version": "1",
                        "chainId": "2000",
                        "verifyingContract": "0xA52B297943dd6F3D5fFb41F50040BB2Bc6272F06"
                    },
                    "message": {
                        "owner": "0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
                        "spender": "0xC2E594095801A382894b761b511B44775e1716a6",
                        "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
                        "nonce": "0",
                        "deadline": "1759424402"
                    },
                    "domainKind": "evm:domain:standard",
                    "messageKind": "evm:permitUSDC"
                }
            },
            "hash": "0x7ab1ccbb88a8cf030de759744c4ac249f06b2512242e5624058bcda99daf2576",
            "signer": null
        },
        {
            "payload": {
                "kind": "typed_data",
                "value": {
                    "types": {
                        "PayoutIntent": [
                            {
                                "name": "sender",
                                "type": "address"
                            },
                            {
                                "name": "relay",
                                "type": "address"
                            },
                            {
                                "name": "calls",
                                "type": "bytes[]"
                            },
                            {
                                "name": "nonce",
                                "type": "bytes32"
                            },
                            {
                                "name": "deadline",
                                "type": "uint256"
                            }
                        ]
                    },
                    "primaryType": "PayoutIntent",
                    "domain": {
                        "name": "DuePayout",
                        "version": "1",
                        "chainId": "2000",
                        "verifyingContract": "0xC2E594095801A382894b761b511B44775e1716a6"
                    },
                    "message": {
                        "sender": "0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
                        "relay": "0xBADDA95F65be56Dc4cD737E865a2d35F0B672BAD",
                        "calls": [
                            "0xb9b8bc50000000000000000000000000bad09fb9781d9d57e1423231ac51f9bb9e0cabad000000000000000000000000a52b297943dd6f3d5ffb41f50040bb2bc6272f0600000000000000000000000000000000000000000000000000000000462c1c6c"
                        ],
                        "nonce": "0xae7813992f91fd41c18bed8734c8d1914d13adad6bfb80f656869813cbc170ad",
                        "deadline": "1759424402"
                    },
                    "domainKind": "evm:domain:standard",
                    "messageKind": "evm:paymentIntent"
                }
            },
            "hash": "0x51dc7acb50075ea1ac934409c5d40e52b1744df19922894d35ba261c86777f95",
            "signer": null
        }
    ],
    "nonce": "0xae7813992f91fd41c18bed8734c8d1914d13adad6bfb80f656869813cbc170ad",
    "hash": "0x51dc7acb50075ea1ac934409c5d40e52b1744df19922894d35ba261c86777f95",
    "expiresAt": "2025-10-02T17:00:02.25776447Z",
    "createdAt": "2025-10-02T16:45:02.302661636Z",
    "reference": "tf_2ghR6HgkRHTQB8:deposit"
}

Important: Transfer intents have a limited validity period (see the expiresAt field). Make sure to sign and submit the intent before it expires.

Step 5: Sign Each Signable via Privy API

For each signable in the signables array, make a request to the Privy API.

For more details on signing typed data, see Privy: Sign Typed Data.

Signing the first signable (Permit):

curl --request POST \
  --url https://api.privy.io/v1/wallets/<wallet_id>/rpc \
  -u "<your-privy-app-id>:<your-privy-app-secret>" \
  --header 'privy-app-id: <your-app-id>' \
  --header 'Content-Type: application/json' \
  --data '{
    "method": "eth_signTypedData_v4",
    "params": {
      "typed_data": {
        "domain": {
          "name": "USDC",
          "version": "1",
          "chainId": "2000",
          "verifyingContract": "0xA52B297943dd6F3D5fFb41F50040BB2Bc6272F06"
        },
        "types": {
          "Permit": [
            {"name": "owner", "type": "address"},
            {"name": "spender", "type": "address"},
            {"name": "value", "type": "uint256"},
            {"name": "nonce", "type": "uint256"},
            {"name": "deadline", "type": "uint256"}
          ]
        },
        "primary_type": "Permit",
        "message": {
          "owner": "0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
          "spender": "0xC2E594095801A382894b761b511B44775e1716a6",
          "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
          "nonce": "0",
          "deadline": "1759424402"
        }
      }
    }
  }'

Response from Privy:

{
  "method": "eth_signTypedData_v4",
  "data": {
    "signature": "0xd99802ab7a14b535ad0bf9c69a7cfd862797a5f3b48270db20fdbd7a170a433e79970506944a3289e1ee2c7d0324b6097d9f67c51816792dbbac0f8c2c8af4b11b",
    "encoding": "hex"
  }
}

Signing the second signable (PayoutIntent):

curl --request POST \
  --url https://api.privy.io/v1/wallets/<wallet_id>/rpc \
  -u "<your-privy-app-id>:<your-privy-app-secret>" \
  --header 'privy-app-id: <your-app-id>' \
  --header 'Content-Type: application/json' \
  --data '{
    "method": "eth_signTypedData_v4",
    "params": {
      "typed_data": {
        "domain": {
          "name": "DuePayout",
          "version": "1",
          "chainId": "2000",
          "verifyingContract": "0xC2E594095801A382894b761b511B44775e1716a6"
        },
        "types": {
          "PayoutIntent": [
            {"name": "sender", "type": "address"},
            {"name": "relay", "type": "address"},
            {"name": "calls", "type": "bytes[]"},
            {"name": "nonce", "type": "bytes32"},
            {"name": "deadline", "type": "uint256"}
          ]
        },
        "primary_type": "PayoutIntent",
        "message": {
          "sender": "0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
          "relay": "0xBADDA95F65be56Dc4cD737E865a2d35F0B672BAD",
          "calls": [
            "0xb9b8bc50000000000000000000000000bad09fb9781d9d57e1423231ac51f9bb9e0cabad000000000000000000000000a52b297943dd6f3d5ffb41f50040bb2bc6272f0600000000000000000000000000000000000000000000000000000000462c1c6c"
          ],
          "nonce": "0xae7813992f91fd41c18bed8734c8d1914d13adad6bfb80f656869813cbc170ad",
          "deadline": "1759424402"
        }
      }
    }
  }'

Response from Privy:

{
  "method": "eth_signTypedData_v4",
  "data": {
    "signature": "0xa1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456789012345678901234567890abcdef1234567890abcdef1234567890abcdef1b",
    "encoding": "hex"
  }
}

Step 6: Submit the Signed Transfer Intent

After obtaining signatures for both signables, submit the transfer intent back to the Due API:

curl --request POST \
  --url https://api.due.network/v1/transfer_intents/submit \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "id": "ti_24QbulYAT9nfjU",
    "ownerId": "acct_DkRHlTg5q8VZM6Gn",
    "sender": "evm:0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
    "amountIn": "1177.29598",
    "to": {
      "evm:0xbad09Fb9781D9D57E1423231AC51f9bb9e0CABAD": "1177.29598"
    },
    "tokenIn": "USDC",
    "tokenOut": "USDC",
    "networkIdIn": "base",
    "networkIdOut": "base",
    "gasFee": "0",
    "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiMHgxYTM5YWJmZWNiMjdlMjA2MzA5OWNkOTg2ZWRiODllOTJkY2VhNDNiMDdhYmQ5NGI3MWNkZDZkMmFkM2U5NTRhIiwiZXhwIjoxNzU5NDI0NDAyLCJpYXQiOjE3NTk0MjM1MDIsImp0aSI6InRpXzI0UWJ1bFlBVDluZmpVIn0.QBNIttR32pp-lcXaIyYCk0N5xJ52Igzh_AUFX34Ll4s5vbFkuMPkzP6FG_zXbhK0b8X-SWyJe-ibVpRXYrRdCg",
    "signables": [
      {
        "payload": {
          "kind": "typed_data",
          "value": {
            "types": {
              "Permit": [
                {"name": "owner", "type": "address"},
                {"name": "spender", "type": "address"},
                {"name": "value", "type": "uint256"},
                {"name": "nonce", "type": "uint256"},
                {"name": "deadline", "type": "uint256"}
              ]
            },
            "primaryType": "Permit",
            "domain": {
              "name": "USDC",
              "version": "1",
              "chainId": "2000",
              "verifyingContract": "0xA52B297943dd6F3D5fFb41F50040BB2Bc6272F06"
            },
            "message": {
              "owner": "0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
              "spender": "0xC2E594095801A382894b761b511B44775e1716a6",
              "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
              "nonce": "0",
              "deadline": "1759424402"
            },
            "domainKind": "evm:domain:standard",
            "messageKind": "evm:permitUSDC"
          }
        },
        "hash": "0x7ab1ccbb88a8cf030de759744c4ac249f06b2512242e5624058bcda99daf2576",
        "signature": "0xd99802ab7a14b535ad0bf9c69a7cfd862797a5f3b48270db20fdbd7a170a433e79970506944a3289e1ee2c7d0324b6097d9f67c51816792dbbac0f8c2c8af4b11b"
      },
      {
        "payload": {
          "kind": "typed_data",
          "value": {
            "types": {
              "PayoutIntent": [
                {"name": "sender", "type": "address"},
                {"name": "relay", "type": "address"},
                {"name": "calls", "type": "bytes[]"},
                {"name": "nonce", "type": "bytes32"},
                {"name": "deadline", "type": "uint256"}
              ]
            },
            "primaryType": "PayoutIntent",
            "domain": {
              "name": "DuePayout",
              "version": "1",
              "chainId": "2000",
              "verifyingContract": "0xC2E594095801A382894b761b511B44775e1716a6"
            },
            "message": {
              "sender": "0xcF5AaaBe14Ba42d9D765C8f2b9099c3b69a25321",
              "relay": "0xBADDA95F65be56Dc4cD737E865a2d35F0B672BAD",
              "calls": [
                "0xb9b8bc50000000000000000000000000bad09fb9781d9d57e1423231ac51f9bb9e0cabad000000000000000000000000a52b297943dd6f3d5ffb41f50040bb2bc6272f0600000000000000000000000000000000000000000000000000000000462c1c6c"
              ],
              "nonce": "0xae7813992f91fd41c18bed8734c8d1914d13adad6bfb80f656869813cbc170ad",
              "deadline": "1759424402"
            },
            "domainKind": "evm:domain:standard",
            "messageKind": "evm:paymentIntent"
          }
        },
        "hash": "0x51dc7acb50075ea1ac934409c5d40e52b1744df19922894d35ba261c86777f95",
        "signature": "0xa1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456789012345678901234567890abcdef1234567890abcdef1234567890abcdef1b"
      }
    ],
    "nonce": "0xae7813992f91fd41c18bed8734c8d1914d13adad6bfb80f656869813cbc170ad",
    "hash": "0x51dc7acb50075ea1ac934409c5d40e52b1744df19922894d35ba261c86777f95",
    "expiresAt": "2025-10-02T17:00:02.25776447Z",
    "createdAt": "2025-10-02T16:45:02.302661636Z",
    "reference": "tf_2ghR6HgkRHTQB8:deposit"
  }'

Response:

{
  "id": "ti_24QbulYAT9nfjU",
  "status": "payment_submitted",
  "txHashIn": "0x1234567890abcdef...",
  "createdAt": "2025-10-02T16:45:02Z"
}

Check Status

curl --request GET \
  --url https://api.due.network/v1/transfers/tf_2ghR4UIA56KEGN \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Due-Account-Id: <ACCOUNT_ID>'

Option B: Using Funding Address (Direct Transfer)

As an alternative to creating and signing a transfer intent, you can use a funding address. This method is simpler and doesn't require signatures - you just send a transaction directly to a temporary address provided by Due.

For more details, see Due: Stablecoin to Fiat Transfers.

Step 4: Create a Funding Address

curl --request POST \
  --url https://api.due.network/v1/transfers/tf_2ghR4UIA56KEGN/funding_address \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>'

Response:

{
  "details": {
    "address": "0x2Fdb8B341f6c26Ee829455A9F25c83F037beb684",
    "schema": "evm"
  }
}

Important:

  • Send the exact amount specified in the transfer
  • Use a single transaction only
  • The transaction must originate from the wallet address linked to your Due account
  • The funding address is temporary and valid only until the transfer expires

Step 5: Send Transaction via Privy

Use Privy's send transaction API to send the exact transfer amount to the funding address.

For details on how to send a transaction, see Privy: Send a Transaction.

Note: Once Due receives the correct payment to the funding address, the transfer will be processed automatically without requiring manual submission.

Check Status

curl --request GET \
  --url https://api.due.network/v1/transfers/tf_2ghR4UIA56KEGN \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Due-Account-Id: <ACCOUNT_ID>'

Scenario 2: Fiat → Crypto

Sending fiat funds from a banking app to a cryptocurrency wallet.

You can send funds to your linked wallet or create a separate recipient:

  • Option 1: Send to your linked wallet - use your wallet ID (e.g., wlt_e3lLDBYiPMHxCv1Q) as the recipient
  • Option 2: Create a new recipient for a different wallet address and use its recipient ID (e.g., rcp_...)

Step 1: Get a Quote

curl --request POST \
  --url https://api.due.network/v1/transfers/quote \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "source": {
      "rail": "ach",
      "currency": "USD",
      "amount": "100"
    },
    "destination": {
      "rail": "base",
      "currency": "USDC"
    }
  }'

Response:

{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "source": {
    "rail": "ach",
    "currency": "USD",
    "amount": "100.00",
    "fee": "0.50"
  },
  "destination": {
    "rail": "base",
    "currency": "USDC",
    "amount": "99.50",
    "fee": "0.00"
  },
  "fxRate": 1.0,
  "fxMarkup": 0,
  "expiresAt": "2025-10-02T16:42:31.951762984Z"
}

Important: Quotes are valid for 2 minutes. Create a quote immediately before creating the transfer.

Step 2: Create a Transfer

Create the transfer, specifying the recipient - either your linked wallet ID or a recipient ID:

curl --request POST \
  --url https://api.due.network/v1/transfers \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "quote": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
    "recipient": "wlt_e3lLDBYiPMHxCv1Q",
    "memo": "Wallet funding"
  }'

The response will contain instructions for the bank transfer:

{
  "id": "tf_xyz789fiat2crypto",
  "ownerId": "acct_DkRHlTg5q8VZM6Gn",
  "status": "awaiting_funds",
  "source": {
    "amount": "100.00",
    "fee": "0.50",
    "currency": "USD",
    "rail": "ach"
  },
  "destination": {
    "amount": "99.50",
    "fee": "0.00",
    "currency": "USDC",
    "rail": "base",
    "id": "wlt_e3lLDBYiPMHxCv1Q",
    "label": "My Crypto Wallet",
    "details": {
      "address": "0x57d63D45f3ec73cfb835E51d5Aef8a389777B70E",
      "schema": "evm"
    }
  },
  "fxRate": 1.0,
  "fxMarkup": 0,
  "bankingDetails": {
    "accountNumber": "123456789",
    "routingNumber": "021000021",
    "reference": "TRF-XYZ789FIAT2CRYPTO",
    "bankName": "Due Network"
  },
  "memo": "Wallet funding",
  "createdAt": "2025-10-02T16:40:00.000000000Z",
  "expiresAt": "2025-10-02T18:40:00.000000000Z"
}

Note: For fiat → crypto transfers, signing is not required. The user transfers funds from their banking app to the specified Due account with the reference code. Due automatically converts and sends the cryptocurrency to the specified wallet.

Check Status

curl --request GET \
  --url https://api.due.network/v1/transfers/tf_xyz789fiat2crypto \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Due-Account-Id: <ACCOUNT_ID>'

Scenario 3: Virtual Accounts (Fiat to Crypto On-Ramp)

Virtual accounts provide automatic currency conversion for deposits. When you create a virtual account, you receive unique banking details that customers can use to deposit funds, which are automatically converted to your desired cryptocurrency.

For more details, see Due: Virtual Accounts.

Example 1: EUR → EURC

Create a virtual account that accepts EUR deposits via SEPA and automatically converts them to EURC on Base.

Step 1: Create Virtual Account

curl --request POST \
  --url https://api.due.network/v1/virtual_accounts \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "destination": "wlt_e3lLDBYiPMHxCv1Q",
    "schemaIn": "bank_sepa",
    "currencyIn": "EUR",
    "railOut": "base",
    "currencyOut": "EURC",
    "reference": "customer_eur_onramp"
  }'

Response:

{
  "id": "va_abc123",
  "destination": "wlt_e3lLDBYiPMHxCv1Q",
  "schemaIn": "bank_sepa",
  "currencyIn": "EUR",
  "railOut": "base",
  "currencyOut": "EURC",
  "reference": "customer_eur_onramp",
  "bankingDetails": {
    "accountNumber": "DE89370400440532013000",
    "accountName": "Due Network",
    "bankName": "Deutsche Bank",
    "IBAN": "DE89370400440532013000",
    "BIC": "DEUTDEFF"
  },
  "status": "active",
  "createdAt": "2025-10-02T16:00:00Z"
}

Customers can now deposit EUR to the provided IBAN. Funds will automatically be converted to EURC and sent to your wallet on Base.

Example 2: AED → USDC

Create a virtual account that accepts AED deposits and automatically converts them to USDC on Base.

Step 1: Create Virtual Account

curl --request POST \
  --url https://api.due.network/v1/virtual_accounts \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'Due-Account-Id: <ACCOUNT_ID>' \
  --data '{
    "destination": "wlt_e3lLDBYiPMHxCv1Q",
    "schemaIn": "bank_uae",
    "currencyIn": "AED",
    "railOut": "base",
    "currencyOut": "USDC",
    "reference": "customer_aed_onramp"
  }'

Response:

{
  "id": "va_xyz789",
  "destination": "wlt_e3lLDBYiPMHxCv1Q",
  "schemaIn": "bank_uae",
  "currencyIn": "AED",
  "railOut": "base",
  "currencyOut": "USDC",
  "reference": "customer_aed_onramp",
  "bankingDetails": {
    "accountNumber": "1234567890",
    "accountName": "Due Network",
    "bankName": "Emirates NBD",
    "IBAN": "AE070331234567890123456",
    "swiftCode": "EBILAEAD"
  },
  "status": "active",
  "createdAt": "2025-10-02T16:00:00Z"
}

Customers can now deposit AED to the provided IBAN. Funds will automatically be converted to USDC and sent to your wallet on Base.


Transfer Status Tracking

After submission, you can track the transfer status:

curl --request GET \
  --url https://api.due.network/v1/transfers/transfer_crypto_fiat_123 \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Due-Account-Id: <ACCOUNT_ID>'

Possible Statuses

For a complete list of possible transfer statuses and their descriptions, see the Due API documentation.

Using Webhooks

To automatically receive notifications about status changes, configure webhooks in Due (see webhooks documentation).

Additional Resources

Due Network

Privy

Getting Help

If you have questions or need help with integration: