Checkout sessions
A checkout session is one payment for one order. Create it on your server, send the buyer to its page or start it for your own checkout, and read its payment to fulfil.
The checkout session object
| Field | Type | Description |
|---|---|---|
id | string | The session's id, pay_…. The same id identifies its payment |
object | string | "checkout_session" |
url | string | The hosted checkout page for this session. Send the buyer here |
status | string | open, pending, paid, held, failed or expired. See statuses |
livemode | boolean | false for Test mode, true for Live mode |
amount | string | The price, as set at creation: "49.00" |
currency | string | USD, EUR, GBP, CAD or AUD |
description | string | What the buyer is paying for, shown on the checkout |
reference | string or null | Your own id for the order |
customer_email | string or null | The buyer's email, lowercased |
created | integer | When it was created, in Unix seconds |
expires_at | integer | When it stops accepting new payments: 24 hours after creation |
started_at | integer or null | When the buyer (or your server) started it. Set once |
metadata | object or null | Your key-value pairs, returned unchanged |
Create a checkout session
POST/v1/checkout_sessions
Creates a session for one order. Nothing is charged and no provider is contacted until the buyer starts paying. Send an Idempotency-Key so a retry returns the same session.
| Field | Type | Description |
|---|---|---|
amount | string Required | The price as a decimal string with at most two decimals, from "1.00" to "100000.00". "49" and "49.0" become "49.00". Numbers, exponents and negative values are refused |
currency | string Required | USD, EUR, GBP, CAD or AUD, in any letter case |
description | string Required | What the buyer is paying for, up to 120 characters |
customer_email | string or null | The buyer's email. Optional here, but needed before the session can start |
reference | string or null | Your order id, up to 120 characters. Returned on the payment and every webhook. Not unique: Railbed doesn't stop two sessions sharing one |
metadata | object or null | Up to 20 keys (1–40 characters, not starting with __) with string values up to 500 characters. For your own ids, never secrets or card details |
success_url | string or null | Where the checkout sends the buyer once the payment is confirmed. {PAYMENT_ID} and {REFERENCE} are filled in. Up to 1,000 characters |
cancel_url | string or null | Where the checkout's "Cancel and return to …" and "Back to …" links lead (they name your business). Up to 1,000 characters |
Return URLs must be full http:// or https:// addresses (Live mode: https:// only) with no username or password. In Live mode your account needs a payout wallet first, or the request gets 409 no_payout_wallet.
curl https://pay.railbed.io/v1/checkout_sessions \
-H "Authorization: Bearer $RAILBED_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_1042" \
-d '{
"amount": "49.00",
"currency": "USD",
"description": "Pro Membership",
"reference": "order_1042",
"customer_email": "buyer@example.com",
"metadata": { "user_id": "player_1042" },
"success_url":
"https://yourstore.com/thanks?order={REFERENCE}&payment={PAYMENT_ID}",
"cancel_url": "https://yourstore.com/cart"
}'const res = await fetch('https://pay.railbed.io/v1/checkout_sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RAILBED_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order_1042',
},
body: JSON.stringify({
amount: '49.00',
currency: 'USD',
description: 'Pro Membership',
reference: 'order_1042',
customer_email: 'buyer@example.com',
metadata: { user_id: 'player_1042' },
success_url:
'https://yourstore.com/thanks?order={REFERENCE}&payment={PAYMENT_ID}',
cancel_url: 'https://yourstore.com/cart',
}),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const session = await res.json();r = requests.post(
"https://pay.railbed.io/v1/checkout_sessions",
headers={
"Authorization": f"Bearer {os.environ['RAILBED_SECRET_KEY']}",
"Idempotency-Key": "order_1042",
},
json={
"amount": "49.00",
"currency": "USD",
"description": "Pro Membership",
"reference": "order_1042",
"customer_email": "buyer@example.com",
"metadata": {"user_id": "player_1042"},
"success_url": (
"https://yourstore.com/thanks?order={REFERENCE}&payment={PAYMENT_ID}"
),
"cancel_url": "https://yourstore.com/cart",
},
timeout=15,
)
r.raise_for_status()
session = r.json()<?php
$ch = curl_init('https://pay.railbed.io/v1/checkout_sessions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('RAILBED_SECRET_KEY'),
'Content-Type: application/json',
'Idempotency-Key: order_1042',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => '49.00',
'currency' => 'USD',
'description' => 'Pro Membership',
'reference' => 'order_1042',
'customer_email' => 'buyer@example.com',
'metadata' => ['user_id' => 'player_1042'],
'success_url' =>
'https://yourstore.com/thanks?order={REFERENCE}&payment={PAYMENT_ID}',
'cancel_url' => 'https://yourstore.com/cart',
]),
]);
$session = json_decode(curl_exec($ch), true);{
"id": "pay_7AAiYH0Ykt11ED4hmfiN",
"object": "checkout_session",
"url": "https://pay.railbed.io/p/pay_7AAiYH0Ykt11ED4hmfiN",
"status": "open",
"livemode": false,
"amount": "49.00",
"currency": "USD",
"description": "Pro Membership",
"reference": "order_1042",
"customer_email": "buyer@example.com",
"created": 1790380525,
"expires_at": 1790466925,
"started_at": null,
"metadata": { "user_id": "player_1042" }
}A retry with the same Idempotency-Key and body answers 200 OK with the same session.
Retrieve a checkout session
GET/v1/checkout_sessions/:id
Returns a session of yours in the key's mode. Another account's session, or one in the other mode, is 404 not_found. Reading a session past its expires_at marks it expired.
curl https://pay.railbed.io/v1/checkout_sessions/pay_7AAiYH0Ykt11ED4hmfiN \
-H "Authorization: Bearer $RAILBED_SECRET_KEY"const session = await fetch(`https://pay.railbed.io/v1/checkout_sessions/${id}`, {
headers: { Authorization: `Bearer ${process.env.RAILBED_SECRET_KEY}` },
}).then((r) => r.json());session = requests.get(
f"https://pay.railbed.io/v1/checkout_sessions/{id}",
headers={"Authorization": f"Bearer {os.environ['RAILBED_SECRET_KEY']}"},
timeout=15,
).json()<?php
$ch = curl_init(
'https://pay.railbed.io/v1/checkout_sessions/' . rawurlencode($id)
);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('RAILBED_SECRET_KEY'),
],
]);
$session = json_decode(curl_exec($ch), true);The response is the checkout session object. To fulfil, read the payment instead: it adds the payment and settlement details.
Start a checkout session
POST/v1/checkout_sessions/:id/start
For your own checkout: starts the session as the buyer would on the hosted page, and returns the card providers that can take it, each with a handoff_url to open in the buyer's browser. Starting assigns the payment's deposit address and locks the fee and the order's value in USD.
| Field | Type | Description |
|---|---|---|
customer_email | string | The buyer's email. Optional when the session already has one; a new one replaces it |
country | string | The buyer's two-letter country code (US, DE), any letter case. Optional. Use the buyer's country, never your server's |
Send {} when the saved email is enough.
curl -X POST \
https://pay.railbed.io/v1/checkout_sessions/pay_7AAiYH0Ykt11ED4hmfiN/start \
-H "Authorization: Bearer $RAILBED_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "country": "US" }'const url = `https://pay.railbed.io/v1/checkout_sessions/${id}/start`;
const started = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RAILBED_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ country: 'US' }),
}).then((r) => r.json());started = requests.post(
f"https://pay.railbed.io/v1/checkout_sessions/{id}/start",
headers={"Authorization": f"Bearer {os.environ['RAILBED_SECRET_KEY']}"},
json={"country": "US"},
timeout=15,
).json()<?php
$ch = curl_init(
'https://pay.railbed.io/v1/checkout_sessions/' . rawurlencode($id) . '/start'
);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('RAILBED_SECRET_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['country' => 'US']),
]);
$started = json_decode(curl_exec($ch), true);{
"id": "pay_7AAiYH0Ykt11ED4hmfiN",
"object": "checkout_session",
"url": "https://pay.railbed.io/p/pay_7AAiYH0Ykt11ED4hmfiN",
"status": "open",
"livemode": false,
"amount": "49.00",
"currency": "USD",
"description": "Pro Membership",
"reference": "order_1042",
"customer_email": "buyer@example.com",
"created": 1790380525,
"expires_at": 1790466925,
"started_at": 1790380611,
"metadata": { "user_id": "player_1042" },
"country": "US",
"providers": [
{
"id": "stripe",
"name": "Stripe",
"note": "Card, Apple Pay or Google Pay",
"recommended": true,
"handoff_url": "https://pay.railbed.io/go/pay_7AAiYH0Ykt11ED4hmfiN?provider=stripe"
},
{
"id": "cashapp",
"name": "Cash App",
"note": "Cash App balance or card",
"recommended": false,
"handoff_url": "https://pay.railbed.io/go/pay_7AAiYH0Ykt11ED4hmfiN?provider=cashapp"
}
]
}The response is the session with two more fields:
| Field | Type | Description |
|---|---|---|
country | string or null | The country you sent, uppercased |
providers | array | The providers that can take this payment, best match first. Each has id, name, note (a short line on how the buyer pays), recommended (true for one) and handoff_url |
Starting again is safe: started_at and the deposit address stay the same. Providers depend on the amount, currency and country, so fetch them when the buyer reaches your checkout, and open a handoff_url only from the buyer's click, in a new tab.
| Status | Code | When |
|---|---|---|
| 400 | invalid_email | The session has no email and none was sent |
| 400 | invalid_country | country isn't two letters |
| 404 | not_found | Not a session of yours in this mode |
| 409 | no_providers | No provider can take this amount and currency now. Nothing was started |
| 409 | already_paid, held, failed | The payment has finished |
| 409 | unavailable | The account can't take payments now (Live: no payout wallet) |
| 410 | expired, canceled | The session can no longer be paid |
| 429 | rate_limited | More than about 12 starts a minute |
| 502 | network_unavailable | The card network didn't answer. Retry in a minute |