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 or developer_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:

  1. You, the developer, will create a transfer specifying the source and destination
    1. 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.
  2. Bridge will create a transfer object which will include source_deposit_instructions.
  3. You will instruct your customer to follow the source_deposit_instructions.
  4. Your customer will send a deposit following the source_deposit_instructions.
  5. Bridge will match your customer's deposit to the transfer you created.
  6. 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 the source_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 the from_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 the awaiting_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_fundsfunds_receivedpayment_submittedpayment_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.