Getting started
This guide takes you from zero to your first signed API call - creating a checkout session - and verifying the signed response. If you would rather see a payment happen before you write any code, start with the Test UI below.
1. See a payment before you write code
You don't need an API key to make your first test payment. The Test UI is a small web app where you fill in a form instead of building a request, and it creates a real hosted checkout for you. Every environment has one:
| Environment | Test UI | Dashboard | What happens when you pay |
|---|---|---|---|
| Sandbox | https://test-ui.sandbox.upprove.com | https://portal.sandbox.upprove.com | Test providers, no money moves. |
| Live | https://test-ui.upprove.com | https://portal.upprove.com | Your real providers, real money. |
Sign in with the same email, password and verification code you use for the Orchestr Dashboard in that environment (the Dashboard column above). You need the Admin role on your account; if you can't get past the sign-in page, ask whoever set up your account to grant it.
Connect a provider
A new account has no provider connected, so a checkout shows no payment methods until you add one.
There is no built-in test provider: in Sandbox you connect a real provider with its own test
credentials (for example Stripe's sk_test_… key), and nothing is charged.
In the Dashboard go to Payment Methods → Providers → Add Provider, pick a provider and enter its credentials. Then click Methods on the new provider card and enable the payment methods you want to offer. You need the Admin or Manager role for this.
If the provider only accepts API traffic from allowlisted IPs, add Orchestr's addresses in your provider account, or it will reject the requests Orchestr sends for you. Card acquirers use a separate PCI address. See IP addresses for the list for each environment.
Make a test payment
With a provider connected, in the Test UI:
- Pick a preset or type an amount, currency, customer and line items, then create the session.
- You get a checkout link. Open it and pay the way your customer would.
- Go back to the Dashboard: the session, order and transaction are there, and any webhook endpoint you registered has received the events. Nothing about it is special: it is the same session the API creates in step 4.
- Every request you send is kept in the history panel, so you can copy the exact body that worked and reuse it as your first API call.
Methods are offered per country and currency from your provider configuration. If a session shows no methods, check the provider's currencies and the method's countries and currencies under Payment Methods in the Dashboard.
On Live the Test UI runs against your real providers, and a completed payment is a real charge. Use it for a final check of your production setup, with an amount you are happy to refund.
The Test UI covers the hosted checkout and alternative payment methods. Card entry is not part of it; for cards, see Test cards.
2. Get your credentials
From the Orchestr Dashboard, in the environment you want to use (Sandbox or Live), create:
- A secret API key (
sk_test_…in Sandbox). Shown once - store it securely. - A signing secret. Also shown once.
Export them so the examples below can read them:
export UPPROVE_SECRET_KEY="sk_test_..."
export UPPROVE_SIGNING_SECRET="your-signing-secret"
3. Know the basics
- Base URL (Sandbox):
https://api.sandbox.upprove.com - All requests are JSON in
snake_case. - Every request needs two headers:
Authorization: Bearer <key>and a signedX-Signature(see Signing).
4. Create your first checkout session
This POST /v1/checkout/sessions call creates a hosted checkout and returns a url to redirect your
customer to. The examples reuse the buildSignatureHeader helper from the
Signing guide.
Card payments are offered on the hosted page automatically - no extra integration. The card result (including the card token, and saving cards for later) arrives on your webhooks; see Cards on hosted checkout.
- cURL
- Node.js
- Python
- PHP
- Java
- C#
REQ_PATH="/v1/checkout/sessions"
BODY='{
"amount": "42.00",
"currency": "GBP",
"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 "$UPPROVE_SIGNING_SECRET" | sed 's/^.*= //')
curl -sS -X POST "https://api.sandbox.upprove.com${REQ_PATH}" \
-H "Authorization: Bearer $UPPROVE_SECRET_KEY" \
-H "X-Signature: t=${TS}, v1=${SIG}" \
-H "Content-Type: application/json" \
--data-raw "$BODY"
const path = '/v1/checkout/sessions';
const body = JSON.stringify({
amount: '42.00',
currency: 'GBP',
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.UPPROVE_SECRET_KEY}`,
'X-Signature': buildSignatureHeader(process.env.UPPROVE_SIGNING_SECRET, path, body),
'Content-Type': 'application/json',
},
body,
});
const session = await res.json();
console.log(session.id, session.url);
import json
import requests
path = "/v1/checkout/sessions"
body = json.dumps(
{
"amount": "42.00",
"currency": "GBP",
"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 {UPPROVE_SECRET_KEY}",
"X-Signature": build_signature_header(UPPROVE_SIGNING_SECRET, path, body),
"Content-Type": "application/json",
},
)
session = resp.json()
print(session["id"], session["url"])
<?php
$path = '/v1/checkout/sessions';
$body = json_encode([
'amount' => '42.00',
'currency' => 'GBP',
'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('UPPROVE_SECRET_KEY'),
'X-Signature: ' . build_signature_header(getenv('UPPROVE_SIGNING_SECRET'), $path, $body),
'Content-Type: application/json',
],
]);
$session = json_decode(curl_exec($ch), true);
echo $session['id'], ' ', $session['url'];
String path = "/v1/checkout/sessions";
String body = """
{
"amount": "42.00",
"currency": "GBP",
"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("UPPROVE_SECRET_KEY"))
.header("X-Signature", buildSignatureHeader(System.getenv("UPPROVE_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/checkout/sessions";
var body = """
{
"amount": "42.00",
"currency": "GBP",
"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("UPPROVE_SECRET_KEY")}");
request.Headers.Add("X-Signature",
BuildSignatureHeader(Environment.GetEnvironmentVariable("UPPROVE_SIGNING_SECRET"), path, body));
var response = await http.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
If your account is set up with processing profiles, add
"processing_profile": "<code>" to address the session to one of them. Leave it out to operate at
account scope.
5. Read the response
A 201 Created is returned with the checkout session. Redirect the customer to url:
{
"id": "cs_7e6d5c4b",
"url": "https://checkout.sandbox.upprove.com/cs_7e6d5c4b",
"status": "open",
"merchant_reference": "order-1001",
"order_id": "ord_5b4a3c2d",
"amount": 42.00,
"currency": "GBP",
"success_url": "https://shop.example.com/checkout/success",
"cancel_url": "https://shop.example.com/checkout/cancel",
"created_at": "2024-06-22T10:00:00Z"
}
The response carries an X-Signature header. Verify it before trusting the body - use the
verification helper from Signing (the signed string
for responses is timestamp + body).
Pre-selecting the payment method
By default the hosted page lets the customer choose a country and then a payment method from
everything you can route there. If the customer already chose the method on your page, pass it as
the only entry of payment_methods - the hosted page then skips the selection and opens that
method's form directly (for card, the card entry itself):
{
"amount": "42.00",
"currency": "EUR",
"merchant_reference": "order-1002",
"country": "DE",
"payment_methods": ["card"],
"success_url": "https://shop.example.com/ok",
"cancel_url": "https://shop.example.com/cancel",
"customer": {
"email": "jane@example.com"
}
}
payment_methodstakes the same codes asPOST /v1/payments(see Payment methods). Several entries narrow the list without skipping the selection.countrypins the checkout to one country, so the customer is not asked for it either. It must be a country in which at least one of the listed methods can be routed for the session currency, otherwise the request fails withcountry_not_available. Without it, the country is resolved from the customer's location and the list of countries offered shrinks to those the listed methods route in.- The session echoes both fields back, and the hosted page refuses any other method.
Going straight to the provider
For a method that redirects to the provider's own page (paypal, skrill_wallet, ideal …), you
can skip the hosted form entirely: add straight_through: true to a session confined to that single
method, with a pinned country and a customer that already carries what the provider needs.
Orchestr then submits the payment itself the first time the customer opens url, and the hosted
page only forwards them to the provider.
{
"amount": "42.00",
"currency": "EUR",
"merchant_reference": "order-1003",
"country": "DE",
"payment_methods": ["paypal"],
"straight_through": true,
"success_url": "https://shop.example.com/ok",
"cancel_url": "https://shop.example.com/cancel",
"customer": {
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe"
}
}
- It is honoured only when all of these hold:
payment_methodshas exactly one entry and that method hasrequires_redirect: trueonGET /v1/payment-methodsand is not of typecardorcard_wallet;countryis pinned; the method can be routed there for the session currency; andcustomercarries every field the routed provider requires (email,nameorfirst_name/last_name,phone,billing_addressas applicable). - A national document id cannot be sent at creation, so a method that needs one (most LATAM local methods) falls back to the form, which collects it.
- The flag never fails the request. If a prerequisite is not met, the session is created as an
ordinary checkout confined to that method and the response says
straight_through: false- read it back rather than assume. Nothing is sent to the provider until the customer opens the checkout. - The customer still returns through the hosted page, so
success_url,cancel_url,fail_url, retries after a decline and thecheckout.session.*webhooks behave as on any other session.
6. Get notified of the outcome
When the customer finishes paying, Orchestr sends a checkout.session.completed
webhook. Register an endpoint in the Dashboard, verify the signature, and
fulfil the order. One exception: a session created with capture_method: "manual" completes when the
card is authorized, and the money moves only when you capture - fulfil on payment.succeeded
instead; see Manual capture on hosted checkout.
Outcomes explains the session and transaction statuses, which ones are final, and the polling rule to follow when you cannot rely on the webhook.
Next steps
- Outcomes - the status model, webhooks first, and the polling rule.
- Authentication and Signing in depth.
- Direct payments - create a payment in one call, without a hosted session.
- Webhooks to handle events.
- The full API Reference.
- Not sure what a field does? Recreate the case in the Test UI (step 1) and compare its request with yours.