Skip to main content
In this guide, you’ll learn how to use the HIFI Sandbox API to simulate money movement. You’ll create users, complete KYC, set up accounts, and execute a series of transfer flows between fiat and stablecoins. ⏱️ Time to complete: 20-30 minutes 🎯 What you’ll build: A complete transfer flow where User A onramps $2 USD to USDC, transfers 1 USDC to User B, then offramps the remaining 1 USDC back to fiat.

Sandbox Environment

This guide uses the Sandbox API where:
  • ✅ All transactions are simulated - no real money moves
  • ✅ KYC applications are typically automatically approved
  • ✅ Transactions complete instantly for testing
  • ✅ You can experiment freely without financial risk
The sandbox behaves identically to production, making it perfect for integration testing.

Prerequisites

Before you start, make sure you have:
  • API keys from the Dashboard (Get API keys)
  • A tool for making API calls (cURL, Postman, or similar)
  • Basic understanding of REST APIs
  • Your sandbox endpoint: https://sandbox.hifibridge.com
All examples in this guide use the sandbox environment. When you’re ready for production, simply replace the sandbox URL with the production endpoint and use your production API keys.

What you’ll build

Here’s what we’ll accomplish in this guide:
  1. User Creation - Create a user and accept Terms of Service
  2. KYC Process - Complete verification to unlock fiat rails
  3. Account Setup - Add virtual accounts (for receiving deposits) and offramp accounts (for withdrawals)
  4. Execute Transfers:
    • Onramp: $2 USD → 2 USDC (User A)
    • Transfer: 1 USDC from User A → User B
    • Offramp: 1 USDC → $1 USD (User A)

Create a User

The User object represents an individual or business on HIFI. All transaction activity is associated with a user object. Creating a user is a two-step process:
  1. Generate a Terms of Service link for your end user to review and accept
  2. Create the user once they’ve accepted the terms
Once created, users are automatically provisioned with cryptocurrency wallet addresses, enabling them to immediately send and receive digital assets.
Once your user has accepted the Terms of Service (and you’ve received the webhook event or confirmed acceptance), create the user account with the Create User endpoint.Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/users \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
{
  "type": "individual",
  "firstName": "John",
  "lastName": "Doe",
  "email": "john@hifibridge.com",
  "dateOfBirth": "1999-01-01",
  "address": {
    "addressLine1": "123 Main St",
    "city": "New York",
    "stateProvinceRegion": "NY",
    "postalCode": "10010",
    "country": "USA"
  },
  "signedAgreementId": "8cb49537-bcf9-41b1-8d8c-c9c200d7341b",
  "requestId": "705f1f8b-a080-467c-b683-174eca409928"
}
'
Response:
{
  "id": "32051b2f-0798-55a7-9c42-b08da4192c97",
  "type": "individual",
  "email": "john@hifibridge.com",
  "name": "John Doe",
  "wallets": {
    "INDIVIDUAL": {
      "POLYGON": {
        "address": "0x1b932E54e77Aeb698144550d5a493Ea99E20Daa7"
      },
      "ETHEREUM": {
        "address": "0xC1c767eaB34b3Cc2C33a354f6Ff2c20fCB98D3C9"
      }
    }
  }
}
User created successfully!What you get:
  • User ID (32051b2f-0798-55a7-9c42-b08da4192c97) - Save this for all future operations
  • Wallet addresses - Pre-provisioned on multiple blockchains, ready to use immediately
wallets
object
  • Account status - Active and ready for KYC verification
Automatic Wallet Provisioning: Every user automatically receives wallet addresses on supported blockchain networks (Polygon, Ethereum, etc.). These wallets are fully functional immediately - no additional setup required.

What’s Next?

Now that you have a user, the next step is to complete KYC verification to unlock the ability to move money between fiat and crypto. Continue to the KYC section below.

Submit KYC

To move money between stablecoins and fiat currencies, users must complete KYC verification to unlock a Rail. What is a Rail? A rail is a payment corridor that enables specific currency conversions. For example, the USD rail enables conversions between USD (fiat) and stablecoins like USDC. In this quickstart, we’ll complete KYC for the USD rail, which allows your user to:
  • Deposit USD and receive USDC (onramp)
  • Send USDC and receive USD (offramp)
Rail-Specific Requirements: Each rail has different KYC requirements based on regulatory needs. Visit our Rails page to learn about available rails and their specific requirements.

KYC Process

The KYC process follows these steps:
  1. Retrieve KYC Requirements - Get the list of required information for your target rail
  2. Update KYC Information - Provide required personal information
  3. Upload KYC Documents - Submit identity verification documents
  4. Submit KYC - Submit the application for review
  5. Check KYC Status - Monitor the approval status
Let’s walk through each step in detail.
First, find out what information is needed for the USD rail by calling the Retrieve KYC Requirements endpoint.Request:
curl --request GET \
     --url 'https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/kyc/requirements?rails=USD' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY'
Response:
{
  "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
  "rails": "USD",
  "type": "individual",
  "required": {
    "firstName": "string",
    "lastName": "string",
    "email": "string",
    "phone": "string",
    "nationality": "string",
    "dateOfBirth": "date",
    "taxIdentificationNumber": "string",
    "documents": {
      "identity": {
        "minCount": 1,
        "acceptedDocTypes": [
          "DRIVERS",
          "ID_CARD",
          "PASSPORT",
          "RESIDENCE_PERMIT"
        ]
      }
    }
  },
  "optional": {},
  "invalid": {
    "message": "fields are either missing or invalid",
    "fields": {
      "phone": "missing",
      "nationality": "missing",
      "taxIdentificationNumber": "missing"
    },
    "documents": {
      "message": "The following document groups are not satisfied: identity",
      "groups": {
        "identity": {
          "minCount": 1,
          "acceptedDocTypes": [
            "DRIVERS",
            "ID_CARD",
            "PASSPORT",
            "RESIDENCE_PERMIT"
          ]
        }
      }
    }
  }
}
Response Fields:
required
object
All mandatory KYC fields needed for the USD rail. These must be provided before submission.
optional
object
Additional fields that aren’t mandatory but may help with approval or enable additional features.
invalid
object
Most Important Field: Lists any missing or invalid data that must be corrected before KYC submission.
Now that we know what’s missing, let’s provide the required information.
Provide the missing personal information using the Update KYC endpoint.Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/kyc \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
{
  "phone": "+18573491112",
  "taxIdentificationNumber": "725569852",
  "nationality": "USA"
}
'
Response:
{
  "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
  "kycInfo": {
    "type": "individual",
    "firstName": "John",
    "lastName": "Doe",
    "nationality": "USA",
    "email": "john@hifibridge.com",
    "phone": "+18573491112",
    "address": {
      "city": "New York",
      "country": "USA",
      "postalCode": "10010",
      "addressLine1": "123 Main St",
      "stateProvinceRegion": "NY"
    },
    "dateOfBirth": "1999-01-01T00:00:00+00:00",
    "taxIdentificationNumber": "725569852",
    "documents": []
  }
}
Personal information updated! Notice the documents array is still empty - we’ll upload those next.
Now we need to upload identity verification documents. This is a two-step process:
  1. Upload the file to get a fileId
  2. Attach the file to the user’s KYC application

Upload a File

First, upload the identity document (driver’s license, passport, etc.) using the Upload a File endpoint.Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/files \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: multipart/form-data' \
     --form 'file=@drivers_license_front.png'
Response:
{
  "id": "file_JzALYV2L1-4LBmaxZ6GCm",
  "createdAt": "2025-09-25T22:05:16.764Z",
  "fileName": "drivers_license_front.png",
  "size": 17834,
  "mimeType": "image/png"
}
Response Fields:
id
string
required
The file ID. Save this - you’ll use it in the next step to attach this document to the user’s KYC application.
fileName
string
Original filename of the uploaded file.
size
number
File size in bytes.
mimeType
string
File type (e.g., image/png, image/jpeg, application/pdf).

Add Documents to KYC

Now attach the uploaded file(s) to the user’s KYC application using the Add Documents endpoint.
Driver’s License Requirements: Most identity documents require both front and back images. For this example, we’ll use the same file ID for both sides, but in production you should upload and attach separate images.
Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/kyc/documents \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
[
  {
    "type": "DRIVERS",
    "subType": "FRONT_SIDE",
    "issuedCountry": "USA",
    "fileId": "file_JzALYV2L1-4LBmaxZ6GCm"
  },
  {
    "type": "DRIVERS",
    "subType": "BACK_SIDE",
    "issuedCountry": "USA",
    "fileId": "file_JzALYV2L1-4LBmaxZ6GCm"
  }
]
'
Request Fields:
type
string
required
Document type. Options: DRIVERS, ID_CARD, PASSPORT, RESIDENCE_PERMIT
subType
string
required
Which side of the document. Options: FRONT_SIDE, BACK_SIDE (passports typically only need FRONT_SIDE)
issuedCountry
string
required
Country that issued the document (ISO country code).
fileId
string
required
The file ID received from the file upload endpoint.
Response:
{
  "count": 2,
  "data": [
    {
      "id": "e477fa56-3e1e-4dac-abe6-008166749f30",
      "type": "DRIVERS",
      "subType": "FRONT_SIDE",
      "issuedCountry": "USA",
      "url": "https://pqgnrjvoqbopfaxmlhlv.supabase.co/storage/v1/object/sign/kyc_documents/...",
      "fileId": "file_JzALYV2L1-4LBmaxZ6GCm"
    },
    {
      "id": "a2190d1d-61af-416d-8fcf-20673da5eaa0",
      "type": "DRIVERS",
      "subType": "BACK_SIDE",
      "issuedCountry": "USA",
      "url": "https://pqgnrjvoqbopfaxmlhlv.supabase.co/storage/v1/object/sign/kyc_documents/...",
      "fileId": "file_JzALYV2L1-4LBmaxZ6GCm"
    }
  ]
}
Response Fields:
count
number
Number of documents successfully attached.
data
array
Array of document objects with IDs, types, and temporary signed URLs for viewing.
Documents uploaded! All required KYC information is now complete. Time to submit for review.
With all fields and documents provided, submit the KYC application to unlock the USD rail using the Submit KYC endpoint.Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/kyc/submissions \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '{"rails": "USD"}'
Response:
{
  "USD": {
    "status": "CREATED",
    "message": "Your KYC application has been successfully created. We will review it shortly."
  }
}
KYC application submitted!What happens next?
  1. Status starts as CREATED (immediate)
  2. Transitions to PENDING (within minutes in sandbox)
  3. Final status: ACTIVE or REJECTED (typically automatic in sandbox, 1-3 business days in production)
Sandbox Auto-Approval: In the sandbox environment, KYC applications are typically automatically approved within minutes for testing purposes. However, certain compliance checks (like PEP screening by banking partners) may still block approval even in sandbox. In production, the review process typically takes 1-3 business days.
You can either:
  1. Poll the Retrieve KYC Statusendpoint
  2. Listen for KYC.STATUS_UPDATE webhook events
Check the current KYC status for the USD rail using the Retrieve KYC Status endpoint.Request:
curl --request GET \
     --url 'https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/kyc/status?rails=USD' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY'
Response:
{
  "status": "ACTIVE",
  "message": "",
  "reviewResult": {
    "reviewAnswer": "APPROVED",
    "reviewRejectType": "",
    "rejectReasons": [],
    "comment": ""
  },
  "details": {
    "identity": {
      "reviewResult": {
        "reviewAnswer": "APPROVED",
        "reviewRejectType": "",
        "rejectReasons": [],
        "comment": ""
      },
      "details": [
        {
          "id": "e477fa56-3e1e-4dac-abe6-008166749f30",
          "type": "DRIVERS",
          "subType": "FRONT_SIDE",
          "reviewResult": {
            "reviewAnswer": "APPROVED",
            "reviewRejectType": "",
            "rejectReasons": [],
            "comment": ""
          }
        },
        {
          "id": "a2190d1d-61af-416d-8fcf-20673da5eaa0",
          "type": "DRIVERS",
          "subType": "BACK_SIDE",
          "reviewResult": {
            "reviewAnswer": "APPROVED",
            "reviewRejectType": "",
            "rejectReasons": [],
            "comment": ""
          }
        }
      ]
    },
    "questionnaire": {
      "reviewResult": {
        "reviewAnswer": "APPROVED",
        "reviewRejectType": "",
        "rejectReasons": [],
        "comment": ""
      }
    },
    "personalInfo": {
      "reviewResult": {
        "reviewAnswer": "APPROVED",
        "reviewRejectType": "",
        "rejectReasons": [],
        "comment": ""
      }
    }
  }
}
KYC approved! The status is ACTIVE, which means the USD rail is now unlocked and the user can:
  • Onramp USD to stablecoins
  • Offramp stablecoins to USD
Response Fields:
status
string
Overall KYC status for the rail. Possible values: - CREATED - Application submitted, not yet reviewed - PENDING - Being reviewed by compliance team - ACTIVE - Approved, rail is unlocked - REJECTED - Not approved (see rejectReasons for details)
reviewResult
object
High-level review outcome for the entire application.
details
object
Detailed review results broken down by category (identity documents, personal information, questionnaire). Useful for diagnosing issues if rejected.

Create Accounts

Now that your user has passed KYC, you can add accounts to enable money movement:
  • Virtual Account - For onramping (fiat deposits into stablecoins)
  • Offramp Account - For offramping (sending stablecoins to fiat)

Account Types

FeatureVirtual AccountOfframp Account
PurposeReceive fiat depositsSend fiat withdrawals
DirectionFiat → Crypto (Onramp)Crypto → Fiat (Offramp)
OwnerCreated by HIFI for the userUser’s external bank account
ExampleUser deposits $100 to virtual account → Receives USDCUser sends USDC → Receives $100 in their bank
Let’s set up both account types.
A Virtual Account is a bank account number automatically created by HIFI that your user can deposit money into. When fiat arrives, it’s automatically converted to the specified stablecoin and sent to the user’s wallet.The parameters you specify determine the conversion:
  • sourceCurrency - Fiat currency to deposit (e.g., usd)
  • destinationCurrency - Stablecoin to receive (e.g., usdc)
  • destinationChain - Blockchain network (e.g., POLYGON)
Let’s create a virtual account that converts USD deposits into USDC on Polygon:Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/virtual-accounts \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
{
  "sourceCurrency": "usd",
  "destinationCurrency": "usdc",
  "destinationChain": "POLYGON"
}
'
Response:
{
  "message": "Virtual account created successfully",
  "accountInfo": {
    "id": "938e3b36-3be7-5535-ba12-8d89eb683e6b",
    "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
    "source": {
      "paymentRail": ["ach", "wire", "rtp"],
      "currency": "usd"
    },
    "destination": {
      "chain": "POLYGON",
      "currency": "usdc",
      "walletAddress": "0x1b932E54e77Aeb698144550d5a493Ea99E20Daa7",
      "externalWalletId": null
    },
    "status": "activated",
    "microDeposits": {
      "count": 0,
      "data": []
    },
    "depositInstructions": {
      "bankName": "Cross River Bank",
      "bankAddress": "885 Teaneck Road, Teaneck, NJ 07666",
      "beneficiary": {
        "name": "John Doe",
        "address": "123 Main St, New York, NY, 10010, US"
      },
      "ach": {
        "routingNumber": "021214891",
        "accountNumber": "344176915009"
      },
      "wire": {
        "routingNumber": "021214891",
        "accountNumber": "344176915009"
      },
      "rtp": {
        "routingNumber": "021214891",
        "accountNumber": "344176915009"
      },
      "reference": null,
      "depositBy": null,
      "instruction": "Please deposit USD to the bank account provided. Ensure the beneficiary name matches the account holder name, or the payment may be rejected."
    },
    "settlementRuleId": null
  }
}
Virtual account created! Your user can now deposit USD to onramp into USDC.Response Fields:
id
string
The unique virtual account ID. Save this for retrieving account details and monitoring deposits.
source.paymentRail
array
Supported payment methods for this virtual account: ach, wire, rtp (Real-Time Payments).
source.currency
string
The fiat currency this account accepts (USD in this example).
destination.chain
string
Blockchain where the converted crypto will be sent.
destination.currency
string
stablecoins the user will receive after conversion.
destination.walletAddress
string
The user’s wallet address where USDC will be deposited. This matches the wallet address assigned when the user was created.
status
string
Account status. activated means it’s ready to receive deposits.
depositInstructions
object
Most Important: Bank account details your user needs to send money to. Share these instructions with your user so they can deposit funds via their bank (wire transfer, ACH, or RTP).
How to Use: Provide these deposit instructions to your end user through your app/website. When they send money to this account via their bank, HIFI automatically detects the deposit, converts it to USDC, and sends it to their wallet.
An Offramp Account is your user’s external bank account where they want to receive fiat money. When offramping, their stablecoins are converted to fiat and sent to this account.
Sandbox Pre-Configuration: For this guide, we’ve simplified the bank account details. In production, you’ll collect real bank account information from your users.
Create a USD offramp bank account for Wire transfers:Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/accounts \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
{
  "rail": "offramp",
  "type": "us",
  "accountHolder": {
    "type": "individual",
    "name": "John Doe",
    "phone": "+18573491112",
    "email": "john@hifibridge.com",
    "address": {
      "addressLine1": "123 Main St",
      "city": "New York",
      "stateProvinceRegion": "NY",
      "postalCode": "10010",
      "country": "USA"
    }
  },
  "us": {
    "transferType": "wire",
    "accountType": "checking",
    "accountNumber": "99485843",
    "routingNumber": "011002877",
    "bankName": "HIFI Bank",
    "currency": "usd"
  }
}
'
Request Fields:
rail
string
required
Always offramp for accounts that receive fiat from crypto conversion.
type
string
required
Account type based on country. Options: us, eu, gb, etc.
accountHolder
object
required
Information about the account owner. Important: Must match the user’s KYC information for compliance.
us
object
required
US-specific bank account information.
Response:
{
  "status": "ACTIVE",
  "invalidFields": [],
  "message": "Account created successfully",
  "id": "583eb259-e78b-4f0c-a4b5-a8957876fa6f"
}
Offramp account added!Response Fields:
id
string
required
The unique account ID. Save this - you’ll use it when initiating offramp transfers to this bank account.
status
string
Account status. ACTIVE means it’s ready to receive offramp funds.
invalidFields
array
List of any validation errors. Empty array means all fields are valid.

Make Transfers

Your user can now perform three types of money movement:
  1. Onramp - Convert fiat → stablecoin (via virtual account)
  2. Crypto Transfer - Send stablecoin between wallets
  3. Offramp - Convert stablecoin → fiat (to bank account)

Transfer Flows

We’ll demonstrate the entire flow between two users:
  • User A (John Doe) - The user we just created (32051b2f-0798-55a7-9c42-b08da4192c97)
  • User B (Jane Doe) - A pre-existing test user in sandbox (30669fcc-b15e-4137-b4fc-9e8f7f659a87)
The flow:
  1. User A onramps $2 USD → 2 USDC
  2. User A transfers 1 USDC → User B
  3. User A offramps 1 USDC → $1 USD to their bank account
Sandbox Simulation: No real money moves in sandbox. All transfers are simulated, but the API requests and responses are identical to production.
In production, onramping happens when your user sends real money from their bank to the virtual account. HIFI detects the deposit and automatically triggers the conversion.In sandbox, we use a simulation endpoint to mimic an incoming deposit without requiring real bank transfers.

Onramp Flow

Your user would send money via their bank app or wire transfer to:
  • Bank: Cross River Bank
  • Beneficiary: John Doe
  • Beneficiary Address: 123 Main St, New York, NY, 10010, US
  • Routing Number: 021214891
  • Account Number: 344176915009
  • Amount: $2.00

Simulate Deposit (Sandbox)

Since we’re in sandbox, use the simulate endpoint to create a test deposit:Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/users/32051b2f-0798-55a7-9c42-b08da4192c97/virtual-accounts/938e3b36-3be7-5535-ba12-8d89eb683e6b/simulate-deposit \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
{
  "paymentRail": "wire",
  "source": {
    "routingNumber": "021000021",
    "accountNumber": "516843515316",
    "name": "Jane Doe",
    "bankName": "JP Morgan Chase"
  },
  "amount": "2",
  "requestId": "32639f89-5fcc-4e31-8abe-0e710ba2e4a1",
  "reference": "This is a test deposit"
}
'
Request Fields:
paymentRail
string
required
How the money is being sent: wire, ach, or rtp
source
object
required
Simulates the sender’s bank information. This represents the bank account sending money TO the virtual account.
amount
string
required
Amount of USD being deposited (will convert to equivalent USDC).
requestId
string
required
Unique identifier for this deposit simulation (UUID).
Response:
{
  "message": "Sandbox deposit triggered"
}
Deposit simulated!

Monitor Onramp

HIFI will now process the deposit and create an onramp transaction. You’ll receive a ONRAMP.CREATE webhook event:Webhook Event Payload:
{
  "transferType": "ONRAMP",
  "transferDetails": {
    "id": "47f14814-cf3f-4e04-bccf-e3f8e6f5c4b9",
    "requestId": "8e02ad32-2fdb-4757-876f-9f7183ca595b",
    "createdAt": "2025-09-26T02:52:01.129Z",
    "updatedAt": "2025-09-26T02:52:01.129Z",
    "status": "FIAT_PENDING",
    "failedReason": null,
    "source": {
      "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
      "currency": "usd",
      "amount": 2,
      "accountId": null,
      "user": {
        "email": "john@hifibridge.com",
        "lastName": "Doe",
        "firstName": "John",
        "businessName": null
      },
      "bankInfo": {
        "bankName": "JP MORGAN CHASE",
        "senderName": "Jane Doe",
        "routingNumber": "021000021",
        "accountNumber": "516843515316",
        "imad": "20250925SIM3E6A1638944",
        "omad": "601040282819436691228450357132228324",
        "bankAddress": "257 Dalton Groves, Barton City, MI 48075",
        "description": "THIS IS A TEST D",
        "paymentRail": "wire"
      }
    },
    "destination": {
      "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
      "currency": "usdc",
      "chain": "POLYGON",
      "walletAddress": "0x1b932E54e77Aeb698144550d5a493Ea99E20Daa7",
      "externalWalletId": null,
      "amount": null,
      "user": {
        "email": "john@hifibridge.com",
        "lastName": "Doe",
        "firstName": "John",
        "businessName": null
      }
    },
    "receipt": {
      "transactionHash": null
    },
    "developerFee": null,
    "virtualAccountId": "938e3b36-3be7-5535-ba12-8d89eb683e6b",
    "quoteInformation": {
      "sendGross": {
        "amount": "2.00",
        "currency": "usd"
      },
      "sendNet": {
        "amount": "2.00",
        "currency": "usd"
      },
      "railFee": {
        "amount": "0.00",
        "currency": "usdc"
      },
      "receiveGross": {
        "amount": "2.00",
        "currency": "usdc"
      },
      "receiveNet": {
        "amount": "2.00",
        "currency": "usdc"
      },
      "rate": "1.00",
      "expiresAt": "N/A"
    },
    "depositInfo": null
  }
}
Important Fields:
transferDetails.id
string
Unique identifier for this onramp transaction. Use this to query transaction status.
transferDetails.status
string
Current status of the onramp. Progression: - FIAT_PENDING - Waiting for fiat to settle - CRYPTO_PENDING - Converting and sending crypto - COMPLETE - USDC delivered to wallet
transferDetails.source
object
Source of the onramp transfer (fiat deposit details).
transferDetails.destination
object
Destination of the onramp transfer (crypto wallet details).
transferDetails.receipt
object
Transaction receipt information.
transferDetails.quoteInformation
object
Conversion details including exchange rate, fees, and net amounts sent/received.
Wait for the status to update to COMPLETE, then check User A’s wallet balance to see the 2 USDC!
Now User A will send 1 USDC to User B using the Create a crypto transfer endpoint.Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/wallets/transfers \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
{
  "source": {
    "userId": "32051b2f-0798-55a7-9c42-b08da4192c97"
  },
  "destination": {
    "userId": "30669fcc-b15e-4137-b4fc-9e8f7f659a87"
  },
  "requestId": "a40ea2aa-7937-4be9-bb1f-b75f1489bcc6",
  "amount": 1,
  "currency": "usdc",
  "chain": "POLYGON"
}
'
Request Fields:
requestId
string
required
Unique identifier (UUID) for this transfer request. Ensures idempotency - retrying with the same ID won’t create duplicate transfers.
source
object
required
Source of the crypto transfer.
destination
object
required
Destination of the crypto transfer.
amount
number
required
Amount of stablecoins to transfer.
currency
string
required
stablecoins to transfer (e.g., usdc, usdt).
chain
string
required
Blockchain network for the transfer (e.g., POLYGON, ETHEREUM).
Response:
{
  "transferType": "WALLET.TRANSFER",
  "transferDetails": {
    "id": "1a1ad1dd-ad72-4f3f-910b-c45dcf09875f",
    "requestId": "a40ea2aa-7937-4be9-bb1f-b75f1489bcc6",
    "createdAt": "2025-09-26T03:04:11.092Z",
    "updatedAt": "2025-09-26T03:04:11.092Z",
    "chain": "POLYGON",
    "currency": "usdc",
    "contractAddress": "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582",
    "status": "CREATED",
    "failedReason": null,
    "source": {
      "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
      "walletAddress": "0x1b932E54e77Aeb698144550d5a493Ea99E20Daa7",
      "walletType": "INDIVIDUAL",
      "user": {
        "email": "john@hifibridge.com",
        "lastName": "Doe",
        "firstName": "John",
        "businessName": null
      }
    },
    "destination": {
      "userId": "30669fcc-b15e-4137-b4fc-9e8f7f659a87",
      "walletAddress": "0x1b932E54e77Aeb688144550d5a493Ea99E20Daa7",
      "user": {
        "email": "Jane@hifibridge.com",
        "lastName": "Doe",
        "firstName": "Jane",
        "businessName": null
      }
    },
    "amount": 1,
    "amountIncludeDeveloperFee": 1,
    "receipt": {
      "transactionHash": null,
      "userOpHash": null
    }
  }
}
Response Fields:
transferDetails.id
string
Unique transfer ID. Save this to check status using the Retrieve a crypto transfer endpoint.
transferDetails.status
string
Transfer status. Starts as CREATED, then progresses through: - CREATED - Transfer initiated - PENDING - Being processed on blockchain - COMPLETE - Successfully delivered - FAILED - Transfer failed (see failedReason)
transferDetails.source
object
Details about the sender including wallet address and user information.
transferDetails.destination
object
Details about the recipient including wallet address and user information.
transferDetails.receipt
object
Transaction receipt information.
transferDetails.failedReason
string
If status is FAILED, this explains why (e.g., insufficient balance, invalid address).
Monitoring Transfers: Subscribe to WALLET.TRANSFER.UPDATE webhook events or poll the Retrieve endpoint to track status until COMPLETE.
Finally, User A will convert their remaining 1 USDC back to USD and send it to their bank account. This is a two-step process:
  1. Create offramp request - Get a quote for the conversion
  2. Accept the quote - Execute the offramp

Create Offramp Request

Create an offramp request using the Create an offramp endpoint:Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/offramps \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY' \
     --header 'content-type: application/json' \
     --data '
{
  "source": {
    "currency": "usdc",
    "chain": "POLYGON",
    "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
    "amount": 1
  },
  "destination": {
    "currency": "usd",
    "accountId": "583eb259-e78b-4f0c-a4b5-a8957876fa6f",
    "userId": "32051b2f-0798-55a7-9c42-b08da4192c97"
  },
  "requestId": "b08e27be-c086-4b84-a321-307ed9f265e1"
}
'
Request Fields:
requestId
string
required
Unique identifier (UUID) for this offramp request.
source
object
required
Source of the offramp (crypto side).
destination
object
required
Destination of the offramp (fiat side).
Response:
{
  "transferType": "OFFRAMP",
  "transferDetails": {
    "id": "b838908b-95d0-4ebb-a2c6-8f0c142bcdd7",
    "requestId": "b08e27be-c086-4b84-a321-307ed9f265e1",
    "createdAt": "2025-09-26T03:15:57.766Z",
    "updatedAt": "2025-09-26T03:15:58.463Z",
    "status": "OPEN_QUOTE",
    "failedReason": null,
    "error": null,
    "errorDetails": null,
    "source": {
      "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
      "chain": "POLYGON",
      "currency": "usdc",
      "amount": 1,
      "walletAddress": "0x1b932E54e77Aeb698144550d5a493Ea99E20Daa7",
      "user": {
        "email": "john@hifibridge.com",
        "lastName": "Doe",
        "firstName": "John",
        "businessName": null
      }
    },
    "destination": {
      "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
      "amount": 1,
      "currency": "usd",
      "user": {
        "email": "john@hifibridge.com",
        "lastName": "Doe",
        "firstName": "John",
        "businessName": null
      },
      "accountId": "583eb259-e78b-4f0c-a4b5-a8957876fa6f"
    },
    "receipt": {
      "transactionHash": null
    },
    "developerFee": null,
    "quoteInformation": {
      "rate": "1.00",
      "sendNet": {
        "amount": "1.00",
        "currency": "usdc"
      },
      "sendGross": {
        "amount": "1.00",
        "currency": "usdc"
      },
      "receiveNet": {
        "amount": "1.00",
        "currency": "usd"
      },
      "receiveGross": {
        "amount": "1.00",
        "currency": "usd"
      },
      "expiresAt": "2025-09-27T03:15:58.112Z"
    },
    "depositInformation": []
  }
}
Response Fields:
transferDetails.id
string
Unique offramp transaction ID. Save this - you’ll need it to accept the quote and check status.
transferDetails.status
string
Current status: OPEN_QUOTE means you have an active quote that needs to be accepted.
transferDetails.quoteInformation
object
Critical: Contains the conversion rate and amounts. Review this before accepting.
transferDetails.error
string
Error message if the offramp request failed.
transferDetails.source
object
Source of the offramp transfer (crypto wallet details).
transferDetails.destination
object
Destination of the offramp transfer (fiat account details).
transferDetails.receipt
object
Transaction receipt information.
transferDetails.errorDetails
string
Detailed error information for troubleshooting.
Quote Expiration: Quotes typically expire after 15-30 minutes. If your quote expires before you accept it, you’ll need to create a new offramp request to get a fresh quote with updated rates.

Accept the Quote

After reviewing the quote, accept it to start the offramp process using the Accept Quote endpoint:Request:
curl --request POST \
     --url https://sandbox.hifibridge.com/v2/offramps/b838908b-95d0-4ebb-a2c6-8f0c142bcdd7/quote/accept \
     --header 'accept: application/json' \
     --header 'authorization: Bearer YOUR_API_KEY'
Response:
{
  "transferType": "OFFRAMP",
  "transferDetails": {
    "id": "b838908b-95d0-4ebb-a2c6-8f0c142bcdd7",
    "requestId": "b08e27be-c086-4b84-a321-307ed9f265e1",
    "createdAt": "2025-09-26T03:15:57.766Z",
    "updatedAt": "2025-09-26T03:15:58.463Z",
    "status": "CRYPTO_INITIATED",
    "failedReason": null,
    "error": null,
    "errorDetails": null,
    "source": {
      "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
      "chain": "POLYGON",
      "currency": "usdc",
      "amount": 1,
      "walletAddress": "0x1b932E54e77Aeb698144550d5a493Ea99E20Daa7",
      "user": {
        "email": "john@hifibridge.com",
        "lastName": "Doe",
        "firstName": "John",
        "businessName": null
      }
    },
    "destination": {
      "userId": "32051b2f-0798-55a7-9c42-b08da4192c97",
      "amount": 1,
      "currency": "usd",
      "user": {
        "email": "john@hifibridge.com",
        "lastName": "Doe",
        "firstName": "John",
        "businessName": null
      },
      "accountId": "583eb259-e78b-4f0c-a4b5-a8957876fa6f"
    },
    "receipt": {
      "transactionHash": null
    },
    "developerFee": null,
    "quoteInformation": {
      "rate": "1.00",
      "sendNet": {
        "amount": "1.00",
        "currency": "usdc"
      },
      "expiresAt": "2025-09-27T03:15:58.112Z",
      "sendGross": {
        "amount": "1.00",
        "currency": "usdc"
      },
      "receiveNet": {
        "amount": "1.00",
        "currency": "usd"
      },
      "receiveGross": {
        "amount": "1.00",
        "currency": "usd"
      }
    },
    "depositInformation": []
  }
}
Quote accepted! The status has changed to CRYPTO_INITIATED.Offramp Status Progression:
  1. OPEN_QUOTE - Quote generated, waiting for acceptance
  2. CRYPTO_INITIATED - Processing crypto conversion
  3. CRYPTO_PENDING - Waiting for blockchain confirmation
  4. FIAT_PENDING - Converting to fiat
  5. COMPLETE - USD sent to bank account
Monitoring Offramp Status:You can track the offramp progress by:
  • Polling: Call Retrieve an offramp with the transaction ID
  • Webhooks: Subscribe to OFFRAMP.UPDATE events for real-time notifications
In the sandbox, the offramp will complete automatically. In production, it may take 1-3 business days for the fiat to arrive in the user’s bank account.

🎉 Congratulations!

You’ve successfully completed the HIFI API quickstart! By following the steps above, you can now:
  • ✅ Create users and handle Terms of Service acceptance
  • ✅ Complete the KYC process to unlock fiat rails
  • ✅ Set up virtual accounts for onramping
  • ✅ Add offramp bank accounts for withdrawals
  • ✅ Execute the complete transfer flow: onramp → transfer → offramp

Next Steps

Ready to build your integration? Here’s what to explore next:
  • Webhooks - Set up real-time event notifications for transaction updates
  • Rails Overview - Learn about additional currency rails
  • Error Handling - Understand error codes and how to handle failures
  • API Reference - Explore all available endpoints and parameters