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:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| Api Basics | Required headers and request configuration |
| Authentication | How to obtain the corporation token |
| Creating a Recipient | Corridor transfers pay a stored recipient |
| Capabilities | Each 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} | Rail | Currency | Capability | Provider |
|---|---|---|---|---|
sepa | SEPA | EUR | SepaOut3rdParty | Bank |
ach | ACH | USD | AchOut3rdParty | Bank |
faster-payments | Faster Payments | GBP | FasterPaymentsOut3rdParty | Bank |
pix | PIX | BRL | PixOut3rdParty | CPN |
spei | SPEI | MXN | SpeiOut3rdParty | CPN |
fedwire | FEDWIRE | USD | FedwireOut3rdParty | CPN |
swift | WIRE | USD | SwiftOut3rdParty | CPN |
cips | CIPS | CNY | CipsOut3rdParty | CPN |
chats | CHATS | HKD | ChatsOut3rdParty | CPN |
fps_hk | FPS | HKD | FpsHkOut3rdParty | CPN |
imps | IMPS/NEFT/UPI | INR | ImpsOut3rdParty | Triple-A |
instapay | InstaPay/PESONet | PHP | InstapayOut3rdParty | Triple-A |
bi-fast | BI-FAST | IDR | BiFastOut3rdParty | Triple-A |
nip | NIP | NGN | NipOut3rdParty | Triple-A |
ipp | IPP | AED | IppOut3rdParty | Triple-A |
pse | PSE | COP | PseOut3rdParty | Triple-A |
Corridor names are literal and lowercase, and their spelling is not uniform:
faster-paymentsandbi-fastare hyphenated,fps_hkuses an underscore, everything else is a
single word. An unrecognised value is rejected with400and
Unknown or unsupported corridor: <value>.
The provider column determines two things: whether account_id is required, and whether a confirm step
follows.
| Provider | account_id | Confirm step |
|---|---|---|
Bank — sepa, ach, faster-payments | Required. A corporate bank account UUID | Not used |
CPN — pix, spei, fedwire, swift, cips, chats, fps_hk | Not used | Required — initiate returns a FundTransfer action |
Triple-A — imps, instapay, bi-fast, nip, ipp, pse | Not used | Not 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:
| Parameter | Description |
|---|---|
{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"
]
}| Field | Type | Required | Description |
|---|---|---|---|
recipient_id | string | Yes | Recipient UUID. Must belong to this corporation |
recipient_payment_details_id | string | Yes | The payment details on that recipient to pay into. Must be a valid UUID |
destination_amount | number | Yes | Amount the beneficiary receives, in the destination currency |
destination_currency | string | No | Target fiat currency. Defaults to the corridor's currency |
account_id | string | Conditional | Required for the bank-provider corridors — sepa, ach, faster-payments. A bank account UUID |
source_tokens | array of string | No | Token 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"
}
]
}| Field | Description |
|---|---|
id | The estimation id. Pass it to initiate as estimation_id |
amount | Amount the beneficiary receives |
currency | Destination fiat currency |
expires_at | Unix timestamp after which the estimate is no longer valid |
estimated_amounts[] | One entry per token — the debit if that token is chosen |
account_idhere 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"
}| Field | Type | Required | Description |
|---|---|---|---|
estimation_id | string | Yes | The id from Step 1 |
source_token_address | string | Yes | Token to debit. Must be one quoted in the estimate |
reference | string | No | Comment 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
}
}
]
}| Field | Description |
|---|---|
transfer_id | Identifier of the transfer. Required by confirm |
status | Transfer status, e.g. pending |
required_actions[] | Actions the corporation must perform before the transfer proceeds. Empty when none |
required_actions[].type | Action type. FundTransfer is the only one currently returned |
required_actions[].data.chain_id | Chain the funding transfer must be made on |
required_actions[].data.token_address | Token to send |
required_actions[].data.destination_address | Address to send it to |
required_actions[].data.amount | Amount in the token's smallest unit, as a string |
required_actions[].data.expires_at | Unix timestamp after which the funding window closes |
An empty
required_actionsarray means the transfer is already moving. Bank and Triple-A
corridors settle from the corporation wallet without a separate funding step. Do not callconfirm
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"
}
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
transfer_id | string | Yes | transfer_id from initiate |
actions | array | Yes | One entry per action returned by initiate |
actions[].type | string | Yes | Echo the action type — FundTransfer |
actions[].data.tx_hash | string | Yes | Hash of the funding transaction |
Response:
{
"transfer_id": "64120850-73a1-4df5-a074-d463258c9deb",
"status": "processing",
"tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}| Field | Description |
|---|---|
transfer_id | The transfer being confirmed |
status | Status after confirmation, e.g. processing |
tx_hash | The 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
- The corporation wallet is debited — directly for bank and Triple-A corridors, by the funding
transfer for CPN corridors. - The payout is submitted on the rail.
- The movement appears in
GET /api/v1/activity/feed. - 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_atabandons 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 Key | Description | Resolution |
|---|---|---|
corridor | Unknown or unsupported corridor: <value> | Use a name from the corridor table exactly |
corridor | Unknown provider for corridor: <value> | Platform configuration issue. Contact Wirex |
recipient_id | Invalid recipient ID format | Send a UUID |
recipient_id | Recipient not found or does not belong to you | The recipient belongs to another corporation, or was deleted |
recipient_payment_details_id | Invalid recipient Payment Details ID | Send the payment_details[].id from the recipient |
account_id | Invalid account ID format | Bank corridors take a plain account UUID, not the composite id |
| Error Reason | Description | Resolution |
|---|---|---|
ErrorGeneral | Capability is not active | The corridor's capability is not Active. For CPN and Triple-A corridors this usually means the provider profile is missing |
Permission Errors (403)
| Error Reason | Description | Resolution |
|---|---|---|
ErrorPermissionDenied | User does not have required permissions | All three endpoints need TransactionCreate or TransactionSu |
Server Errors (500)
| Error Reason | Description | Resolution |
|---|---|---|
ErrorGeneral | Failed to read tokens | The token catalogue was unavailable. Retry |
ErrorGeneral | Estimate failure | The provider rejected the quote — unsupported destination, or an amount outside its limits |
ErrorGeneral | Initiate failure | The estimate expired or was already used. Re-estimate |
ErrorGeneral | Confirm failure | The funding transfer was not matched. Verify the hash, the amount and the destination address |
Updated 20 days ago

