Transfers
Introduction
The Transfer API enables seamless conversion between fiat and crypto — or within each — across various supported currencies and rails. At its core, the API is designed to abstract away the complexity of cross-rail and cross-currency money movement, supporting:
- Fiat → Crypto
- Crypto → Crypto
- Crypto → Fiat
Transfers a source and a destination, each of which can represent fiat funding sources (e.g. bank accounts, cards) or blockchain-based wallets. The API is generalized, composable, and rails-agnostic, allowing for a variety of transfer flows that suit modern financial applications.
Refer to supported currencies and rails below:
How they work
Depending on the source, Bridge either pulls funds (e.g. Bridge wallet, prefunded account) or generates deposit instructions when a third-party source (e.g. bank, external wallet) is specified.
If the funds reside outside of Bridge, you will receive a source_deposit_instructions
object in the response payload containing deposit bank details or blockchain addresses. Once your customer completes the deposit, Bridge will automatically detect and process the payment to the destination.
Key Concepts
source
: Where the funds come from. This can be a Bridge-controlled wallet, a customer’s crypto wallet, or a fiat source like a bank account.destination
: Where the funds go. This can be any supported rail or chain.on_behalf_of
: Customer identifier. Required when sending/receiving on behalf of a user.developer_fee
ordeveloper_fee_percent
: Monetize transfers via fixed or percentage fees. Refer the Developer Fees section on how to set fees.features
: Optional settings to add flexibility (e.g. allow_any_from_address, flexible_amount).
High Level Flow
For one time transfers, the high level flow is:
- You, the developer, will create a transfer specifying the
source
anddestination
- If the source is a pre-funded account or a bridge wallet, Bridge will pull funds directly from it and skip the above steps -- there is no deposit required.
- Bridge will create a transfer object which will include
source_deposit_instructions
. - You will instruct your customer to follow the
source_deposit_instructions
. - Your customer will send a deposit following the
source_deposit_instructions
. - Bridge will match your customer's deposit to the transfer you created.
- Bridge sends money to the
destination
.
Create Transfer
Request
curl --location --request POST 'https://api.bridge.xyz/v0/transfers' \
--header 'Api-Key: <API Key>' \
--header 'Idempotency-Key: <Unique Idempotency Key>' \
--data-raw '{
"amount": "10.0",
"on_behalf_of": "cust_alice",
"developer_fee": "0.5",
# source object tells Bridge where to expect deposits from.
# can be fiat or crypto
"source": {
"payment_rail": "ach_push",
"currency": "usd",
},
# deposit object tells Bridge where to send funds to.
# can also be fiat or crypto
"destination": {
"payment_rail": "ethereum",
"currency": "usdc",
"to_address": "0xdeadbeef",
},
}'
Response
{
"id": "transfer_123",
"state": "awaiting_funds",
"on_behalf_of": "cust_alice",
"amount": "10.0",
"developer_fee": "0.5",
"source": {
"payment_rail": "ach_push",
"currency": "usd"
},
"destination": {
"payment_rail": "polygon",
"currency": "usdc",
"to_address": "0xdeadbeef"
},
# Very important that your customer follows the source deposit instructions
"source_deposit_instructions": {
"bank_account_number": "123456789", # Bridge's bank account number to send deposits to
"bank_routing_number": "101019644", # Bridge's bank account routing number
# if the transfer requires a specific amount, it's important your customer includes
# the exact amount expected.
"amount": "10.0",
"currency": "usd",
"deposit_message": "BVI7depositmessage", # important that the deposit message is included
},
"receipt": {
"initial_amount": "10.0",
"developer_fee": "0.5",
"exchange_fee": "0.0",
"final_amount": "9.5",
"destination_tx_hash": "0xc0ffee", // A destination tx hash will appear after the transfer is complete
},
"created_at": "2023-05-05T19:39:14.316Z",
"updated_at": "2023-05-05T19:39:15.231Z"
}
Deposit Instructions
Fiat Deposits
Transfer Object
{
...
"source_deposit_instructions": {
"bank_account_number": "123456789", # Bridge's bank account number to send deposits to
"bank_routing_number": "101019644", # Bridge's bank account routing number
# if the transfer requires a specific amount, it's important your customer includes
# the exact amount expected.
"amount": "10.0",
"currency": "usd",
"deposit_message": "BVI7depositmessage", # important that the deposit message is included
},
...
}
- Unique Deposit Message: Due to the limitations of fiat rails, we will create a unique identifier for your customer to attach to the deposit they send to Bridge. We rely on that identifier to match customer deposits to your transfer.
- Direct your customer to send funds to Bridge's bank account, with the required amount and currency, and to include the deposit message (starting with BRG). Your customer can include the unique deposit message as the wire memo/message, ACH push description, SEPA reference, or SPEI memo. The deposit message is unique per transfer and including it with your transaction is required for Bridge to process.
Crypto Deposits
If you receive crypto deposits, remember to configure a crypto return policy! Refer Crypto Return Policies.
Transfer Object
{
...
"source_deposit_instructions": {
"payment_rail": "ethereum", # expected blockchain
"amount": "10.0", # expected amount
"currency": "usdc", # expected currency
"from_address": "0xdeadbeef", # expected address for funds to originate from
"to_address": "0xc0ffee" # Bridge's deposit address to send funds to
},
...
}
- In this instance, we need to request your customer to deposit funds to Bridge from their wallet. The
source_deposit_instructions
object in the response will contain Bridge's deposit address. - You can guide your user to use Metamask or an equivalent wallet to create a blockchain transfer to move funds from their wallet to our deposit address.
Memo Based Blockchains
{
// ...
"source_deposit_instructions": {
"payment_rail": "stellar",
"amount": "10.0",
"currency": "usdc",
"from_address": "GDUY7JALICESTELLARADDRESS",
"to_address": "GDUY7JSBRIDGESTELLARDEPOSITADDRESS",
"blockchain_memo": "123456",
},
// ...
}
- For memo-based blockchains, such as Stellar and Tron, a
blockchain_memo
will be provided as part of thesource_deposit_instructions
in the response. This must be included in the crypto deposit memo. When you forget to include the memo, your off-ramp will experience signficant delays
Transfer Delivery
Once we receive the deposit for your transfer, we will move the funds to the destination. You can use the Transfers API to check the state of the transfer or listen to webhooks.
Advanced Features
Flexible Amounts
To create a Transfer that will match any funds sent using the source deposit instructions regardless of amount, use the flexible_amount
feature.
Because the amount is not known ahead of time, Transfers with the flexible_amount
feature only support a percentage based developer_fee_percent
. Flexible amount Transfers do not support setting a fixed developer_fee
.
Note: Static templates have this feature enabled automatically if no amount is provided when they are created.
Request
curl --location --request POST 'https://api.bridge.xyz/v0/transfers' \
--header 'Api-Key: <API Key>' \
--header 'Idempotency-Key: <Unique Idempotency Key>' \
--data-raw '{
"on_behalf_of": "cust_alice",
"source": {
"payment_rail": "wire",
"currency": "usd",
},
"destination": {
"payment_rail": "ethereum",
"currency": "usdc",
"to_address": "0xdeadbeef",
},
"developer_fee_percent": "1.0", // A 1% developer fee
"features": {
"flexible_amount": true,
}
}'
Response
{
"id": "flexible_transfer_789",
"state": "awaiting_funds",
"on_behalf_of": "cust_alice",
"source": {
"payment_rail": "wire",
"currency": "usd"
},
"destination": {
"payment_rail": "polygon",
"currency": "usdc",
"to_address": "0xdeadbeef"
},
"source_deposit_instructions": {
// ...Bridge banking details
"currency": "usd",
"deposit_message": "BVI7depositmessage",
},
"features": {
"flexible_amount": true
},
"created_at": "2023-05-05T19:39:14.316Z",
"updated_at": "2023-05-05T19:39:15.231Z"
}
After Bridge receives funds and matches them to the Transfer, the amount will be included in API responses
{
"id": "flexible_transfer_789",
"amount": "1337.0",
...
}
Allow Any From Address
"features": {
"allow_any_from_address": true,
}
- By default, Bridge requires the sending address of the wallet sending the funds to be specified upon creating a Transfer.
- For some use cases like consumer sending funds from an exchange, this is difficult or impossible to know ahead of time.
- To support these use cases, Bridge has the
allow_any_from_address
feature. - If this feature is enabled, no
from_address
needs to be specified at Transfer creation time and any funds sent to the source deposit instructions will be matched regardless of thefrom_address
.
Transfer States
The transfer status refers to the current state of a transfer. There are several possible states that a transfer can be in, including:
awaiting_funds
- Bridge is waiting to receive funds from the customer before it can start to process the transfer. This state only exists when Bridge is awaiting funds from the customer (ex: crypto deposits, wires, ACH push).in_review
- This is a temporary state that is rarely triggered. If it does occur, it usually resolves automatically within seconds. However, if we are unable to confirm the transaction information by the end of the 24-hour period, we will reach out to the developer with the next steps.funds_received
- This is an acknowledgment that Bridge has received your funds and is in the process of moving funds on your customer's behalf.payment_submitted
- This means Bridge has sent the payment and is currently awaiting verification. Depending on the payment rail, this can take anywhere from minutes (Crypto) to hours (Wires) to days (ACH push)payment_processed
- The transfer has been completed. The funds have been sent to your specified destination.undeliverable
- Bridge was unable to send the funds to the specified destination. Some examples of this case are an invalid routing number/account number or attempting to send an asset not supported by the destination.returned
- Bridge has sent the payment, but received notification that it wasn't successful. The funds have been returned to Bridge, and the refund to the sender is in process.refunded
- The funds for this transfer have been sent back to the original sender.canceled
- The transfer has been canceled. (Transfers can only be canceled when in theawaiting_funds
state).error
- There was a problem preventing Bridge from processing this transfer. This may require manual intervention to resolve. Please reach out to Bridge if you haven't already been contacted about this transfer.
Please note that a transfer will always progress from awaiting_funds
→ funds_received
→ payment_submitted
→ payment_processed
. It can never go backwards.
canceled
can only be progressed to from awaiting_funds
, and is done by hitting our DELETE /v0/transfers/{transferID}
endpoint.
Source & Destination Details
Source
The source
key can return additional information if available. Specifically, if the transfer is an onramp and the funds have already arrived, we will return information about the fiat deposit.
Wire Deposit Example
{
"source": {
"payment_rail": "wire",
"currency": "usd",
"external_account_id": null,
"bank_beneficiary_name": "name of person sending money",
"bank_routing_number": "routing number of the sending bank",
"bank_name": "name of the sending bank",
"imad": "imad of incoming wire",
"omad": "omad of incoming wire (deprecated)"
}
}
ACH Deposit Example
{
"source": {
"payment_rail": "ach_push",
"currency": "usd",
"external_account_id": null,
"description": "ach description"
}
}
Note that the source payment rail is ach_push
, but in the destination it needs to be ach
. You'll get a 4xx level error when specifying a destination payment rail of ach_push
. Note that in both cases, this corresponds to an ACH push rail.
SEPA Deposit Example
{
"source": {
"bic": "REVOESM2XXX",
"uetr": "53301970-2771-4449-a6c1-c23eafc34a8b",
"reference": "Payment from Joaquin Valente: ",
"iban_last_4": "3628",
"sender_name": "Joaquin Valente",
"payment_rail": "sepa",
"payment_scheme": "sepa_instant",
"recipient_name": "Bridge Building Sp.z.o.o."
},
}
Destination
The destination
field will also return additional information when available. Specifically, if the transfer is an offramp and the payment has been fully processed, we will return information about the outgoing fiat transaction.
Wire Destination Example
{
"destination": {
"payment_rail": "wire",
"currency": "usd",
"external_account_id": "310f23a7-9d15-4a67-b96f-bfe2acf26b00",
"imad": "20240701MMASDFO9000528"
}
}
ACH Destination Example:
{
"destination": {
"payment_rail": "ach",
"currency": "usd",
"external_account_id": "310f23a7-9d15-4a67-b96f-bfe2acf26b00",
"trace_number": "718268532664263"
}
}
Returning Funds via ACH, Wire, and SEPA
If a customer successfully onramped via ACH, Wire, or SEPA transfer but you would like to return those funds back to the customer, create a new transfer with the following params:
curl --location --request POST 'https://api.bridge.xyz/v0/transfers' \
--header 'Api-Key: <API Key>' \
--header 'Idempotency-Key: <Unique Idempotency Key>' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "50.0",
"on_behalf_of": "cust_alice",
"source": {
"payment_rail": "base",
"currency": "usdc", // Must be fungible (1:1) with the original deposit currency
"from_address": "0xdeadbeef"
},
"destination": {
"payment_rail": "fiat_deposit_return", // Initiates a return to the original deposit source
"currency": "usd", // Must match the original deposit currency
"deposit_id": "deposit_123" // UUID of the original completed deposit
}
}'
We will respond back with a transfer object that contains source deposit instructions pointing to a crypto address. After you send the correct amount to that address, we will initiate a return of funds back to the customer.
When processing the transfer creation request, we will enforce the following invariants:
- The source currency must be crypto
- The source currency must be an equivalent currency to the original deposit source currency (ex: USDC <> USD)
- The on_behalf_of must match the original deposit customer
- When the original deposit was via ACH, the transfer amount must match the original deposit amount. For Wire and SEPA returns, the transfer amount must be less than or equal to the original deposit amount.
- The request must be within 60 days of the original deposit.
Updated 1 day ago