Direct payments
A direct payment creates a one-time payment in a single, merchant-initiated call. Unlike a
hosted checkout session, you supply the amount, currency, country, and
payment_method yourself rather than handing the whole flow to Orchestr.
The payment is completed on a provider-hosted page, so payment details never touch your
servers. (Paying by card without a redirect — collecting the card on your own page — is its own
flow: see Card payments.) A successful POST /v1/payments returns one of:
status: requires_action— redirect the customer tonext_action.redirect_urlto finish paying.status: completed— the payment settled immediately.status: failed— the payment was declined or errored.
When to use a direct payment
| Use a direct payment when… | Use a checkout session when… |
|---|---|
| You already know the amount, currency, country, and payment method and want a single server-to-server call. | You want Orchestr to host the entire payment page and collect customer/payment details for you. |
| You manage your own return-URL redirect. | You want line items, address/phone collection, and locale handling out of the box. |
Both flows share the same providers and settlement — the resulting order surfaces with
subtype: direct_payment on the Orders and Transactions
endpoints.
1. Create a payment
This POST /v1/payments 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/payments"
BODY='{"amount":"42.00","currency":"GBP","country":"GB","payment_method":{"code":"paypal"},"merchant_reference":"order-1001","success_url":"https://shop.example.com/ok","cancel_url":"https://shop.example.com/cancel","customer":{"email":"jane@example.com"}}'
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/payments';
const body = JSON.stringify({
amount: '42.00',
currency: 'GBP',
country: 'GB',
payment_method: {code: 'paypal'},
merchant_reference: 'order-1001',
success_url: 'https://shop.example.com/ok',
cancel_url: 'https://shop.example.com/cancel',
customer: {email: 'jane@example.com'},
});
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 payment = await res.json();
if (payment.status === 'requires_action') {
console.log('redirect to', payment.next_action.redirect_url);
}
import json
import requests
path = "/v1/payments"
body = json.dumps(
{
"amount": "42.00",
"currency": "GBP",
"country": "GB",
"payment_method": {"code": "paypal"},
"merchant_reference": "order-1001",
"success_url": "https://shop.example.com/ok",
"cancel_url": "https://shop.example.com/cancel",
"customer": {"email": "jane@example.com"},
},
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",
},
)
payment = resp.json()
if payment["status"] == "requires_action":
print("redirect to", payment["next_action"]["redirect_url"])
<?php
$path = '/v1/payments';
$body = json_encode([
'amount' => '42.00',
'currency' => 'GBP',
'country' => 'GB',
'payment_method' => ['code' => 'paypal'],
'merchant_reference' => 'order-1001',
'success_url' => 'https://shop.example.com/ok',
'cancel_url' => 'https://shop.example.com/cancel',
'customer' => ['email' => 'jane@example.com'],
], 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',
],
]);
$payment = json_decode(curl_exec($ch), true);
echo $payment['status'], ' ', $payment['next_action']['redirect_url'] ?? '';
String path = "/v1/payments";
String body = """
{"amount":"42.00","currency":"GBP","country":"GB","payment_method":{"code":"paypal"},\
"merchant_reference":"order-1001","success_url":"https://shop.example.com/ok",\
"cancel_url":"https://shop.example.com/cancel","customer":{"email":"jane@example.com"}}""";
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/payments";
var body = "{\"amount\":\"42.00\",\"currency\":\"GBP\",\"country\":\"GB\"," +
"\"payment_method\":{\"code\":\"paypal\"},\"merchant_reference\":\"order-1001\"," +
"\"success_url\":\"https://shop.example.com/ok\",\"cancel_url\":\"https://shop.example.com/cancel\"," +
"\"customer\":{\"email\":\"jane@example.com\"}}";
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());
The required fields are amount, currency, country, payment_method, merchant_reference,
success_url, and cancel_url. merchant_reference must be unique per account and doubles as your
idempotency key — retrying with the same reference will
not create a duplicate.
:::note LATAM local methods require the customer's identity document
For PIX, BOLETO, SPEI, OXXO and PSE, pass the paying customer's national identity document in
payment_method.details.document_id — the CPF in Brazil (11 digits, punctuation optional), CURP or
RFC in Mexico, cedula in Colombia (6–10 digits). The payment is rejected with a validation error
when it is missing or malformed for the method's country.
"payment_method": {"code": "pix", "details": {"document_id": "123.456.789-01"}}
:::
2. Read the response
A 200 OK returns the payment. The X-Signature response header signs the body — verify it
before trusting the response (see Signing).
Most payments come back requires_action — redirect the customer to next_action.redirect_url:
{
"order_id": "ord_5b4a3c2d",
"transaction_id": "txn_9f8e7d6c",
"status": "requires_action",
"amount": 42.00,
"currency": "GBP",
"next_action": {
"type": "redirect",
"redirect_url": "https://pay.provider.example/session/abc123"
}
}
For a payment that settles immediately, status is completed, next_action is null, and
provider_transaction_id carries the settlement reference:
{
"order_id": "ord_5b4a3c2d",
"transaction_id": "txn_9f8e7d6c",
"status": "completed",
"amount": 42.00,
"currency": "GBP",
"provider_transaction_id": "pi_3PqRsT2eZvKYlo2C1aBcD3eF",
"next_action": null
}
| Field | Description |
|---|---|
order_id | The order created for this payment. |
transaction_id | The payment transaction attempt. |
status | requires_action, completed, or failed. A card charge can also return authorized (a manual-capture hold), settling, or canceled. |
amount / currency | The payment amount and currency. |
provider_transaction_id | Settlement/charge reference; null before settlement. |
additional_identifiers | Extra provider-side identifiers, each { "identifier": …, "type": "checkout" | "order" }. |
next_action | Present when status is requires_action; null on terminal outcomes. |
3. Handle the outcome
The create response tells you the initial state; the final outcome arrives asynchronously via
the payment.* webhooks (payment.succeeded, payment.failed,
payment.requires_action, …). Fulfil the order on payment.succeeded.
Unlike a checkout session, a declined direct payment has no retry surface — the payment does not
reopen for another attempt. To try again, create a new payment with a new merchant_reference.
Inspect the decline code on the failed transaction to decide whether retrying
is worthwhile.
Next steps
- Signing — build and verify the
X-Signatureheader. - Webhooks — the
payment.*events that report the final outcome. - Decline codes — interpret failed payments.
- The full API Reference for
POST /v1/payments.