Transfers
A transfer moves funds from your provider balance to a destination you describe in the request — a bank account, a digital wallet, a real-time-payment alias, or a card. Unlike a payout, a transfer is not tied to an earlier payment: you supply the full destination instrument details yourself.
A transfer is created as a new transfer transaction and comes back with status pending; the
provider is called asynchronously and the final outcome arrives by webhook.
Transfer vs payout
| Use a transfer when… | Use a payout when… |
|---|---|
| You are sending funds to a destination you specify — a bank account, wallet, RTP alias, or card. | You want to send funds back to the instrument of a payment you already took. |
| There is no original payment to reference. | You can identify an original settled payment. |
Sending to a card
Everything on this page describes a transfer to a destination you can describe in the request — a bank account, wallet, or RTP alias. A card destination is different: you cannot send a card number to the API, so the card is collected in the browser first and the transfer settles in a second call.
See Transfers to a card for that flow.
1. Create a transfer
amount, currency, payment_method, and merchant_reference are required. The
payment_method.details object describes the destination instrument — which of its fields you
supply depends on the method, country, and provider (see
Payment instruments). merchant_reference doubles as the
idempotency key.
code: "bank"All regional bank-transfer rails (SEPA, Faster Payments, ACH, SPEI, Zengin…) share the single method
code bank. The provider you route to maps it to the concrete rail based on the currency, country,
and the account fields you supply.
This POST /v1/transfers call reuses the buildSignatureHeader helper from the
Signing guide (the signed string for a request is
timestamp + path + body). It assumes you've exported ORCHESTR_SECRET_KEY and
ORCHESTR_SIGNING_SECRET as shown in Getting started.
- cURL
- Node.js
- Python
- PHP
- Java
- C#
REQ_PATH="/v1/transfers"
BODY='{"amount":"250.00","currency":"GBP","merchant_reference":"transfer-1001","reference":"ACME payout","payment_method":{"code":"bank","details":{"holder_name":"Jane Doe","iban":"GB29NWBK60161331926819","bic_swift":"NWBKGB2L","country_code":"GB"}}}'
TS=$(date +%s)
SIG=$(printf '%s' "${TS}${REQ_PATH}${BODY}" \
| openssl dgst -sha256 -hmac "$ORCHESTR_SIGNING_SECRET" | sed 's/^.*= //')
curl -sS -X POST "https://api.sandbox.upprove.com${REQ_PATH}" \
-H "Authorization: Bearer $ORCHESTR_SECRET_KEY" \
-H "X-Signature: t=${TS}, v1=${SIG}" \
-H "Content-Type: application/json" \
--data-raw "$BODY"
const path = '/v1/transfers';
const body = JSON.stringify({
amount: '250.00',
currency: 'GBP',
merchant_reference: 'transfer-1001',
reference: 'ACME payout',
payment_method: {
code: 'bank',
details: {
holder_name: 'Jane Doe',
iban: 'GB29NWBK60161331926819',
bic_swift: 'NWBKGB2L',
country_code: 'GB',
},
},
});
const res = await fetch(`https://api.sandbox.upprove.com${path}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ORCHESTR_SECRET_KEY}`,
'X-Signature': buildSignatureHeader(process.env.ORCHESTR_SIGNING_SECRET, path, body),
'Content-Type': 'application/json',
},
body,
});
const transfer = await res.json();
console.log(transfer.order_id, transfer.status);
import json
import requests
path = "/v1/transfers"
body = json.dumps(
{
"amount": "250.00",
"currency": "GBP",
"merchant_reference": "transfer-1001",
"reference": "ACME payout",
"payment_method": {
"code": "bank",
"details": {
"holder_name": "Jane Doe",
"iban": "GB29NWBK60161331926819",
"bic_swift": "NWBKGB2L",
"country_code": "GB",
},
},
},
separators=(",", ":"),
)
resp = requests.post(
f"https://api.sandbox.upprove.com{path}",
data=body,
headers={
"Authorization": f"Bearer {ORCHESTR_SECRET_KEY}",
"X-Signature": build_signature_header(ORCHESTR_SIGNING_SECRET, path, body),
"Content-Type": "application/json",
},
)
transfer = resp.json()
print(transfer["order_id"], transfer["status"])
<?php
$path = '/v1/transfers';
$body = json_encode([
'amount' => '250.00',
'currency' => 'GBP',
'merchant_reference' => 'transfer-1001',
'reference' => 'ACME payout',
'payment_method' => [
'code' => 'bank',
'details' => [
'holder_name' => 'Jane Doe',
'iban' => 'GB29NWBK60161331926819',
'bic_swift' => 'NWBKGB2L',
'country_code' => 'GB',
],
],
], JSON_UNESCAPED_SLASHES);
$ch = curl_init("https://api.sandbox.upprove.com{$path}");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('ORCHESTR_SECRET_KEY'),
'X-Signature: ' . build_signature_header(getenv('ORCHESTR_SIGNING_SECRET'), $path, $body),
'Content-Type: application/json',
],
]);
$transfer = json_decode(curl_exec($ch), true);
echo $transfer['order_id'], ' ', $transfer['status'];
String path = "/v1/transfers";
String body = """
{"amount":"250.00","currency":"GBP","merchant_reference":"transfer-1001",\
"reference":"ACME payout","payment_method":{"code":"bank","details":{\
"holder_name":"Jane Doe","iban":"GB29NWBK60161331926819","bic_swift":"NWBKGB2L",\
"country_code":"GB"}}}""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.sandbox.upprove.com" + path))
.header("Authorization", "Bearer " + System.getenv("ORCHESTR_SECRET_KEY"))
.header("X-Signature", buildSignatureHeader(System.getenv("ORCHESTR_SIGNING_SECRET"), path, body))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
var path = "/v1/transfers";
var body = "{\"amount\":\"250.00\",\"currency\":\"GBP\",\"merchant_reference\":\"transfer-1001\"," +
"\"reference\":\"ACME payout\",\"payment_method\":{\"code\":\"bank\",\"details\":{" +
"\"holder_name\":\"Jane Doe\",\"iban\":\"GB29NWBK60161331926819\",\"bic_swift\":\"NWBKGB2L\"," +
"\"country_code\":\"GB\"}}}";
using var http = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, $"https://api.sandbox.upprove.com{path}")
{
Content = new StringContent(body, Encoding.UTF8, "application/json"),
};
request.Headers.Add("Authorization", $"Bearer {Environment.GetEnvironmentVariable("ORCHESTR_SECRET_KEY")}");
request.Headers.Add("X-Signature",
BuildSignatureHeader(Environment.GetEnvironmentVariable("ORCHESTR_SIGNING_SECRET"), path, body));
var response = await http.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Omit channel_id to let the platform route the transfer through your workflows and default routing.
Supply channel_id to force a specific channel. If no channel can be routed, the request is rejected
with transfer_unroutable.
2. Read the response
A 201 Created returns a TransferView. The X-Signature response header signs the body — verify
it before trusting the response (see Signing). The
payment_instrument echoes the resolved destination.
{
"order_id": "trf_5b4a3c2d",
"merchant_reference": "transfer-1001",
"amount": 250.00,
"currency": "GBP",
"status": "pending",
"reference": "ACME payout",
"payment_instrument": {
"id": "pi_8a7b6c5d",
"type": "bank_account",
"holder_name": "Jane Doe",
"iban": "GB29NWBK60161331926819",
"bic_swift": "NWBKGB2L",
"country_code": "GB"
},
"provider_transaction_id": null,
"additional_identifiers": [],
"created_at": "2024-06-22T10:00:00Z"
}
| Field | Description |
|---|---|
order_id | The transfer order (trf_...) — the public transfer identifier. |
merchant_reference | Your reference for this transfer. |
amount / currency | The transfer amount and currency. |
status | Starts at pending; moves through processing to completed, failed, or canceled. A card transfer awaiting card collection reports requires_action instead. |
next_action | Only on the card-collection leg of a card transfer — a collect_card action carrying the client secret. Absent otherwise. |
reference | Statement descriptor shown to the recipient where supported. |
payment_instrument | The resolved destination instrument. |
provider_transaction_id | Settlement reference; null before settlement. |
additional_identifiers | Extra provider-side identifiers, each { "identifier": …, "type": "checkout" | "order" }. |
created_at | When the transfer was created. |
Retrieving a transfer
GET /v1/transfers/{id} (where {id} is the transfer transaction id, txn_...) returns the
fuller TransactionView rather than the slim TransferView above.
A transfer order can accrue more than one transaction — one per cascade attempt.
GET /v1/transfers/by-order/{orderId} (where {orderId} is the transfer order, trf_...) returns
every attempt, newest first, in an unpaged list:
{
"object": "list",
"data": [
{ "id": "txn_1c2b3a4d", "type": "transfer", "status": "failed", "amount": 250.00, "currency": "GBP" }
],
"total_count": 1,
"has_more": false
}
3. Handle the outcome
The create response tells you the initial state; the final outcome arrives asynchronously via
the transfer.* webhooks (transfer.paid, transfer.failed,
transfer.canceled, …). Alternatively, poll GET /v1/transfers/{id} until the transaction reaches a
terminal status.
Next steps
- Transfers to a card — the two-step flow for a card destination.
- Payment instruments — the destination fields to supply per instrument type.
- Signing — build and verify the
X-Signatureheader. - Payouts — send funds back to the instrument of an existing payment.
- Webhooks — the
transfer.*events that report the final outcome. - The full API Reference for the transfer endpoints.