Testing
Test mode simulates payments end to end, with no card charged and no money moved. Make payments succeed, fall short or be declined on demand, send sample webhook events, and go live with confidence.
Test mode
Test mode is a complete, separate copy of your account. Test keys start with rb_test_, and everything they create is simulated: checkouts, payments, settlement and webhooks all behave as in Live mode, but no card is charged and no money moves. Switch the dashboard between Test and Live with the toggle at the top of each page.
| Test mode | Live mode | |
|---|---|---|
| Secret keys | rb_test_… | rb_live_… |
| Card payments | Simulated on Railbed's test provider page | Real, on the provider's page |
| Settlement | Simulated, with the same checks as live | USDC to your payout wallet |
| Webhook endpoints | Any https:// address (for your own computer, a tunnel) | Public https:// addresses only |
| Webhooks | Real, signed HTTP requests to your endpoints | The same |
POST /v1/payments/:id/simulate | Available | Refused |
Test and live data never mix: a test key can't read or change a live payment, and each mode has its own endpoints and keys.
Simulate an outcome as a buyer
Open a test session's url (or any test checkout or payment link), enter an email and choose Pay. The test provider page offers three outcomes:
- Simulate successful payment: about 97% of the order's value arrives, as a real provider's fee would leave it, and the payment becomes
paid. - Simulate an underpayment: half arrives, so the payment is
heldfor review, exactly as a live shortfall would be. - Simulate declined card: the payment becomes
failed.
Simulate an outcome from your server
To test without a browser, start the session and simulate the outcome through the API. This is how automated tests should drive payments.
# Start it (the buyer would normally do this by choosing a provider)
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 '{ "customer_email": "buyer@example.com" }'
# Then decide how it ends: "paid", "underpaid" or "failed"
curl -X POST \
https://pay.railbed.io/v1/payments/pay_7AAiYH0Ykt11ED4hmfiN/simulate \
-H "Authorization: Bearer $RAILBED_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "outcome": "paid" }'const api = (path, body) =>
fetch(`https://pay.railbed.io/v1${path}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RAILBED_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
}).then((r) => r.json());
await api(`/checkout_sessions/${id}/start`, {
customer_email: 'buyer@example.com',
});
const payment = await api(`/payments/${id}/simulate`, {
outcome: 'paid', // or 'underpaid', 'failed'
});def api(path, body):
return requests.post(
f"https://pay.railbed.io/v1{path}",
headers={"Authorization": f"Bearer {os.environ['RAILBED_SECRET_KEY']}"},
json=body,
timeout=15,
).json()
api(f"/checkout_sessions/{id}/start", {"customer_email": "buyer@example.com"})
payment = api(
f"/payments/{id}/simulate",
{"outcome": "paid"}, # or "underpaid", "failed"
)<?php
function railbed_post(string $path, array $body): array {
$ch = curl_init('https://pay.railbed.io/v1' . $path);
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($body),
]);
return json_decode(curl_exec($ch), true);
}
railbed_post("/checkout_sessions/$id/start", [
'customer_email' => 'buyer@example.com',
]);
$payment = railbed_post("/payments/$id/simulate", [
'outcome' => 'paid', // or 'underpaid', 'failed'
]);The simulated outcome runs the same settlement checks and sends the same webhooks as a live payment. Each session can end only once: simulating again on a finished payment returns it unchanged, so use a new session for each scenario. See Simulate a payment for the details.
Send sample webhook events
In Developers, choose Send test event on a Test endpoint and pick any event. Railbed sends a signed, realistic event with a made-up payment (its metadata.sample is "true", and it isn't in your payments or the API), then shows the exact body your server received and how it answered. Live endpoints can receive a ping only, so a live system never sees a payment that didn't happen.
Every delivery, test or real, appears in the endpoint's delivery log with its body, and can be sent again.
Receive webhooks on your own computer
Railbed sends webhooks from the internet, so it can't reach a server that only listens on your computer. While you build, expose your local server through a tunnel that gives it a public https:// address (for example Cloudflare Tunnel or ngrok), and add that address as a Test endpoint. When the tunnel's address changes, edit the endpoint: pending retries go to the new address.
A test plan worth running
Before going live, check that your integration handles each of these without a human:
| Scenario | How to cause it | Expected result |
|---|---|---|
| A normal payment | Simulate paid | Order fulfilled once |
| The same event twice | Resend a delivery from the delivery log | Nothing changes the second time |
| An underpayment | Simulate underpaid | Order not fulfilled; payment.held received |
| A decline | Simulate failed | Order not fulfilled; buyer can start again |
| A buyer who never pays | Create a session and leave it | Order not fulfilled; payment.expired after 24 hours |
| A lost create response | Send the same create request twice with one Idempotency-Key | One payment, the same id both times |
| Your server is down | Point the endpoint at a failing address, then fix it | Deliveries retry; Retry now in the log delivers it |
| A forged webhook | Send a request with a wrong signature | Your endpoint rejects it with a 4xx |
Going live
- Add your payout wallet in Settings. It must be a self-custody Polygon wallet you control, not an exchange deposit address.
- Switch the dashboard to Live and create a live key and live webhook endpoints. Live endpoints need public
https://addresses. - Put the live key and the live endpoint's signing secret on your production server. Keep the test ones for your test environment.
- Take one small real payment and follow it through:
payment.paidreceived, the order fulfilled once, USDC in your wallet.