Corridor Transfers

One estimate-initiate-confirm flow covering every payout rail, from SEPA to PIX to BI-FAST.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain the corporation token
Creating a RecipientCorridor transfers pay a stored recipient
CapabilitiesEach corridor is gated by its own capability

Overview

A corridor transfer pays a stored recipient over a named payment rail. Unlike the v1 bank transfer, the
rail is explicit — it is the {corridor} path parameter — and the beneficiary is referenced by id
rather than described inline.

Three endpoints, in order:

POST /api/v3/bank/estimate/{corridor}   →  estimation_id
POST /api/v3/bank/initiate/{corridor}   →  transfer_id, required_actions[]
POST /api/v3/bank/confirm/{corridor}    →  only when required_actions is non-empty

Whether the third call is needed depends on which provider serves the corridor.


Corridors

{corridor}RailCurrencyCapabilityProvider
sepaSEPAEURSepaOut3rdPartyBank
achACHUSDAchOut3rdPartyBank
faster-paymentsFaster PaymentsGBPFasterPaymentsOut3rdPartyBank
pixPIXBRLPixOut3rdPartyCPN
speiSPEIMXNSpeiOut3rdPartyCPN
fedwireFEDWIREUSDFedwireOut3rdPartyCPN
swiftWIREUSDSwiftOut3rdPartyCPN
cipsCIPSCNYCipsOut3rdPartyCPN
chatsCHATSHKDChatsOut3rdPartyCPN
fps_hkFPSHKDFpsHkOut3rdPartyCPN
impsIMPS/NEFT/UPIINRImpsOut3rdPartyTriple-A
instapayInstaPay/PESONetPHPInstapayOut3rdPartyTriple-A
bi-fastBI-FASTIDRBiFastOut3rdPartyTriple-A
nipNIPNGNNipOut3rdPartyTriple-A
ippIPPAEDIppOut3rdPartyTriple-A
psePSECOPPseOut3rdPartyTriple-A

Corridor names are literal and lowercase, and their spelling is not uniform:
faster-payments and bi-fast are hyphenated, fps_hk uses an underscore, everything else is a
single word. An unrecognised value is rejected with 400 and
Unknown or unsupported corridor: <value>.

The provider column determines two things: whether account_id is required, and whether a confirm step
follows.

Provideraccount_idConfirm step
Bank — sepa, ach, faster-paymentsRequired. A corporate bank account UUIDNot used
CPN — pix, spei, fedwire, swift, cips, chats, fps_hkNot usedRequired — initiate returns a FundTransfer action
Triple-A — imps, instapay, bi-fast, nip, ipp, pseNot usedNot used

CPN and Triple-A corridors depend on a provider profile capability — CpnExternalProfile and
TripleAExternalProfile respectively. Those are provisioned by Wirex and have no activation endpoint.


Step 1: Estimate

POST /api/v3/bank/estimate/{corridor}

Requires TransactionSu or TransactionCreate.

Path parameters:

ParameterDescription
{corridor}Corridor name from the table above

Request body:

{
  "recipient_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "recipient_payment_details_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "destination_amount": 25000.00,
  "destination_currency": "INR",
  "source_tokens": [
    "0x0774164DC20524Bb239b39D1DC42573C3E4C6976"
  ]
}
FieldTypeRequiredDescription
recipient_idstringYesRecipient UUID. Must belong to this corporation
recipient_payment_details_idstringYesThe payment details on that recipient to pay into. Must be a valid UUID
destination_amountnumberYesAmount the beneficiary receives, in the destination currency
destination_currencystringNoTarget fiat currency. Defaults to the corridor's currency
account_idstringConditionalRequired for the bank-provider corridors — sepa, ach, faster-payments. A bank account UUID
source_tokensarray of stringNoToken addresses to quote against

Response:

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "amount": 25000.00,
  "currency": "INR",
  "expires_at": 1704107700,
  "estimated_amounts": [
    {
      "amount": 300.25,
      "precise_amount": "300250000000000000000",
      "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
      "token_symbol": "WUSD",
      "rate": 83.26,
      "fee_amount": 1.5,
      "fee_precise_amount": "1500000000000000000"
    }
  ]
}
FieldDescription
idThe estimation id. Pass it to initiate as estimation_id
amountAmount the beneficiary receives
currencyDestination fiat currency
expires_atUnix timestamp after which the estimate is no longer valid
estimated_amounts[]One entry per token — the debit if that token is chosen

account_id here is a plain UUID, not the composite <accountId>:<detailsId> the v1 transfer
takes.
Send the first half only. The composite form is rejected with
Invalid account ID format.


Step 2: Initiate

POST /api/v3/bank/initiate/{corridor}

Requires TransactionSu or TransactionCreate.

Request body:

{
  "estimation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "source_token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
  "reference": "Invoice 2024-0042"
}
FieldTypeRequiredDescription
estimation_idstringYesThe id from Step 1
source_token_addressstringYesToken to debit. Must be one quoted in the estimate
referencestringNoComment attached to the transfer

Response:

{
  "transfer_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "status": "pending",
  "required_actions": [
    {
      "type": "FundTransfer",
      "data": {
        "chain_id": 8453,
        "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
        "destination_address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
        "amount": "300250000000000000000",
        "expires_at": 1704107700
      }
    }
  ]
}
FieldDescription
transfer_idIdentifier of the transfer. Required by confirm
statusTransfer status, e.g. pending
required_actions[]Actions the corporation must perform before the transfer proceeds. Empty when none
required_actions[].typeAction type. FundTransfer is the only one currently returned
required_actions[].data.chain_idChain the funding transfer must be made on
required_actions[].data.token_addressToken to send
required_actions[].data.destination_addressAddress to send it to
required_actions[].data.amountAmount in the token's smallest unit, as a string
required_actions[].data.expires_atUnix timestamp after which the funding window closes

An empty required_actions array means the transfer is already moving. Bank and Triple-A
corridors settle from the corporation wallet without a separate funding step. Do not call confirm
for them.


Step 3: Confirm — CPN Corridors Only

When initiate returns a FundTransfer action, send amount of token_address to
destination_address on chain_id before expires_at, then report the transaction hash.

POST /api/v3/bank/confirm/{corridor}

Requires TransactionSu or TransactionCreate.

Request body:

{
  "transfer_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "actions": [
    {
      "type": "FundTransfer",
      "data": {
        "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
      }
    }
  ]
}
FieldTypeRequiredDescription
transfer_idstringYestransfer_id from initiate
actionsarrayYesOne entry per action returned by initiate
actions[].typestringYesEcho the action type — FundTransfer
actions[].data.tx_hashstringYesHash of the funding transaction

Response:

{
  "transfer_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "status": "processing",
  "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}
FieldDescription
transfer_idThe transfer being confirmed
statusStatus after confirmation, e.g. processing
tx_hashThe funding transaction hash that was accepted

Code

const estimateResponse = await fetch(`${baseUrl}/api/v3/bank/estimate/pix`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    recipient_id: recipientId,
    recipient_payment_details_id: paymentDetailsId,
    destination_amount: 5000.00,
    source_tokens: [tokenAddress]
  })
});
const estimate = await estimateResponse.json();

const initiateResponse = await fetch(`${baseUrl}/api/v3/bank/initiate/pix`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    estimation_id: estimate.id,
    source_token_address: tokenAddress,
    reference: 'Invoice 2024-0042'
  })
});
const initiated = await initiateResponse.json();

// CPN corridors return a FundTransfer action — fund it, then confirm
if (initiated.required_actions.length > 0) {
  const txHash = await sendFundingTransfer(initiated.required_actions[0].data);

  await fetch(`${baseUrl}/api/v3/bank/confirm/pix`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${corporationToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      transfer_id: initiated.transfer_id,
      actions: [{ type: 'FundTransfer', data: { tx_hash: txHash } }]
    })
  });
}
estimate = requests.post(
    f"{base_url}/api/v3/bank/estimate/pix",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={
        "recipient_id": recipient_id,
        "recipient_payment_details_id": payment_details_id,
        "destination_amount": 5000.00,
        "source_tokens": [token_address],
    },
).json()

initiated = requests.post(
    f"{base_url}/api/v3/bank/initiate/pix",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={
        "estimation_id": estimate["id"],
        "source_token_address": token_address,
        "reference": "Invoice 2024-0042",
    },
).json()

# CPN corridors return a FundTransfer action — fund it, then confirm
if initiated["required_actions"]:
    tx_hash = send_funding_transfer(initiated["required_actions"][0]["data"])

    requests.post(
        f"{base_url}/api/v3/bank/confirm/pix",
        headers={
            "Authorization": f"Bearer {corporation_token}",
            "Content-Type": "application/json",
        },
        json={
            "transfer_id": initiated["transfer_id"],
            "actions": [{"type": "FundTransfer", "data": {"tx_hash": tx_hash}}],
        },
    )
estimateBody, _ := json.Marshal(map[string]interface{}{
    "recipient_id":                 recipientID,
    "recipient_payment_details_id": paymentDetailsID,
    "destination_amount":           5000.00,
    "source_tokens":                []string{tokenAddress},
})

estimateReq, _ := http.NewRequest("POST", baseURL+"/api/v3/bank/estimate/pix", bytes.NewBuffer(estimateBody))
estimateReq.Header.Set("Authorization", "Bearer "+corporationToken)
estimateReq.Header.Set("Content-Type", "application/json")

estimateResp, _ := http.DefaultClient.Do(estimateReq)
defer estimateResp.Body.Close()

var estimate BankCorridorEstimateResponse
json.NewDecoder(estimateResp.Body).Decode(&estimate)

initiateBody, _ := json.Marshal(map[string]string{
    "estimation_id":        estimate.Id,
    "source_token_address": tokenAddress,
    "reference":            "Invoice 2024-0042",
})

initiateReq, _ := http.NewRequest("POST", baseURL+"/api/v3/bank/initiate/pix", bytes.NewBuffer(initiateBody))
initiateReq.Header.Set("Authorization", "Bearer "+corporationToken)
initiateReq.Header.Set("Content-Type", "application/json")

initiateResp, _ := http.DefaultClient.Do(initiateReq)
defer initiateResp.Body.Close()

var initiated BankCorridorExecuteResponse
json.NewDecoder(initiateResp.Body).Decode(&initiated)

What Happens Next

  1. The corporation wallet is debited — directly for bank and Triple-A corridors, by the funding
    transfer for CPN corridors.
  2. The payout is submitted on the rail.
  3. The movement appears in GET /api/v1/activity/feed.
  4. Progress is delivered to POST {your_webhook_base_url}/v2/webhooks/activities.

There is no endpoint that returns a transfer by transfer_id. Track completion through the activity
feed.


Limitations

  • There is no endpoint that lists available corridors at runtime. Use the table above and confirm
    availability against the corporation's capability list.
  • There is no cancel endpoint. A funded CPN transfer cannot be recalled through the API.
  • Failing to confirm a CPN transfer before expires_at abandons it. The funding transfer, if already
    sent, is a reconciliation matter with Wirex.

Error Handling

{
  "error_reason": "ErrorGeneral",
  "error_description": "Request failed validation",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "corridor", "details": "Unknown or unsupported corridor: sepa-instant" }
  ]
}

Validation Errors (400)

Error Details KeyDescriptionResolution
corridorUnknown or unsupported corridor: <value>Use a name from the corridor table exactly
corridorUnknown provider for corridor: <value>Platform configuration issue. Contact Wirex
recipient_idInvalid recipient ID formatSend a UUID
recipient_idRecipient not found or does not belong to youThe recipient belongs to another corporation, or was deleted
recipient_payment_details_idInvalid recipient Payment Details IDSend the payment_details[].id from the recipient
account_idInvalid account ID formatBank corridors take a plain account UUID, not the composite id
Error ReasonDescriptionResolution
ErrorGeneralCapability is not activeThe corridor's capability is not Active. For CPN and Triple-A corridors this usually means the provider profile is missing

Permission Errors (403)

Error ReasonDescriptionResolution
ErrorPermissionDeniedUser does not have required permissionsAll three endpoints need TransactionCreate or TransactionSu

Server Errors (500)

Error ReasonDescriptionResolution
ErrorGeneralFailed to read tokensThe token catalogue was unavailable. Retry
ErrorGeneralEstimate failureThe provider rejected the quote — unsupported destination, or an amount outside its limits
ErrorGeneralInitiate failureThe estimate expired or was already used. Re-estimate
ErrorGeneralConfirm failureThe funding transfer was not matched. Verify the hash, the amount and the destination address

Did this page help you?