Introduction
Getting started
The Orbita API is organised around REST. It accepts JSON request bodies, returns JSON responses and uses standard HTTP verbs, status codes and authentication. Every account gets a sandbox and a live environment with separate keys, so you can build and test without moving real money.
- 1Create a sandbox account
Sign up in two minutes. Sandbox keys are issued instantly — no compliance review needed.
- 2Make your first test payment
Call Create a payment with any amount and an
instant_bankmethod. - 3Listen for webhooks
Register an HTTPS endpoint and confirm you receive
payment.approved. - 4Go live
Swap
sk_test_forsk_live_keys once your merchant review is approved.
Environments
| Environment | Base URL | Notes |
|---|---|---|
| Sandbox | https://sandbox.orbitapay.dev/v2 | Test methods in every market, no real money |
| Live | https://api.orbitapay.dev/v2 | Requires an approved merchant account |
Sandbox scenarios
Use the minor-unit amount to trigger specific outcomes. Everything else behaves exactly like production, including webhooks and routing decisions.
| Amount ends in | Result | Webhook |
|---|---|---|
…00 | Approved | payment.approved |
…51 | Soft decline, cascaded | payment.approved |
…52 | 3-D Secure challenge | payment.requires_action |
…99 | Hard decline | payment.declined |
Pin a version with the Orbita-Version header. The current version is 2026-09-01. We never ship breaking changes to a pinned version, and new versions are announced 90 days ahead.
Introduction
Authentication
Authenticate every request with a secret API key passed as a bearer token. Keys are scoped to one environment and can be rolled at any time from Dashboard → Developers → API keys.
| Prefix | Used for |
|---|---|
sk_test_ | Server-side requests in the sandbox |
sk_live_ | Server-side requests in production |
pk_live_ | The cashier SDK in browsers and apps |
whsec_ | Verifying webhook signatures |
Send an Idempotency-Key header with every POST. Retrying with the same key returns the original response instead of creating a duplicate payment; keys are stored for 24 hours.
curl https://api.orbitapay.dev/v2/balance \
-H "Authorization: Bearer sk_test_9Qm2LwX8rT4vK7pN" \
-H "Orbita-Version: 2026-09-01"
Never ship sk_live_ keys in a mobile app, a browser bundle or a public repository. If a key leaks, roll it immediately — the old key stops working within 60 seconds.
Core resources
Create a payment
/v2/paymentsCreates a deposit and returns the next action for the player — a redirect, a 3-D Secure challenge or an immediate approval. Orbita picks the route automatically unless you pin one.
Body parameters
amountintegerRequiredAmount in minor units, e.g.
25000for €250.00.currencystringRequiredThree-letter ISO 4217 code, e.g.
EUR,BRL.methodenumRequiredOne of
card,instant_bank,ewallet,mobile_money,voucher,crypto.customerobjectRequiredYour player reference:
id, pluscountryandemailfor better routing and risk scoring.routingobjectstrategy:smart(default),costorpinnedwith anacquirerid.return_urlstringWhere the player lands after a redirect or challenge.
metadataobjectUp to 20 key-value pairs, returned on every event.
curl https://api.orbitapay.dev/v2/payments \
-H "Authorization: Bearer sk_test_9Qm2LwX8rT4vK7pN" \
-H "Idempotency-Key: dep_01J9ZK4T8M" \
-H "Content-Type: application/json" \
-d '{
"amount": 25000,
"currency": "EUR",
"method": "instant_bank",
"customer": { "id": "plr_7Hq2xK", "country": "DE" },
"routing": { "strategy": "smart" },
"return_url": "https://play.example.com/cashier/done"
}'import Orbita from "@orbita/node";
const orbita = new Orbita(process.env.ORBITA_SECRET_KEY);
const payment = await orbita.payments.create({
amount: 25000,
currency: "EUR",
method: "instant_bank",
customer: { id: "plr_7Hq2xK", country: "DE" },
routing: { strategy: "smart" },
returnUrl: "https://play.example.com/cashier/done",
}, { idempotencyKey: "dep_01J9ZK4T8M" });import os
import orbita
client = orbita.Client(api_key=os.environ["ORBITA_SECRET_KEY"])
payment = client.payments.create(
amount=25000,
currency="EUR",
method="instant_bank",
customer={"id": "plr_7Hq2xK", "country": "DE"},
routing={"strategy": "smart"},
return_url="https://play.example.com/cashier/done",
idempotency_key="dep_01J9ZK4T8M",
)<?php
$orbita = new \Orbita\Client(getenv('ORBITA_SECRET_KEY'));
$payment = $orbita->payments->create([
'amount' => 25000,
'currency' => 'EUR',
'method' => 'instant_bank',
'customer' => ['id' => 'plr_7Hq2xK', 'country' => 'DE'],
'routing' => ['strategy' => 'smart'],
'return_url' => 'https://play.example.com/cashier/done',
], ['idempotency_key' => 'dep_01J9ZK4T8M']);{
"id": "pay_3Kd8Wq2LmZ",
"object": "payment",
"status": "requires_action",
"amount": 25000,
"currency": "EUR",
"method": "instant_bank",
"customer": { "id": "plr_7Hq2xK", "country": "DE" },
"route": {
"provider": "a2a_de_02",
"strategy": "smart",
"score": 0.97
},
"next_action": {
"type": "redirect",
"url": "https://pay.orbitapay.dev/r/3Kd8Wq2LmZ"
},
"risk": { "score": 8, "level": "low" },
"created": 1759484400,
"livemode": false
}
Payment statuses
- pending
Created, waiting for the player or the rail.
- requires_action
Redirect or 3-D Secure challenge needed.
- approved
Funds authorised — credit the player's balance.
- declined
Final decline after all cascade attempts.
Core resources
Payouts
/v2/payoutsSends a withdrawal to a player's bank account, wallet or crypto address. KYC, limits and AML screening run before the money moves; you get the result synchronously or through payout.* webhooks.
Body parameters
amountintegerRequiredAmount in minor units.
currencystringRequiredISO 4217 code of the destination account.
destinationobjectRequiredtypeisbank_account,walletorcrypto_address, plus the fields for that type.speedenuminstant(default where available) orstandard.referencestringYour withdrawal id, shown on the player's statement where supported.
/v2/payouts/{id}Retrieve a payoutcurl https://api.orbitapay.dev/v2/payouts \
-H "Authorization: Bearer sk_test_9Qm2LwX8rT4vK7pN" \
-H "Idempotency-Key: wd_01J9ZM2Q4R" \
-H "Content-Type: application/json" \
-d '{
"amount": 125000,
"currency": "EUR",
"speed": "instant",
"customer": { "id": "plr_7Hq2xK" },
"destination": {
"type": "bank_account",
"iban": "DE89370400440532013000",
"holder_name": "Lena Hoffmann"
},
"reference": "W-48213"
}'const payout = await orbita.payouts.create({
amount: 125000,
currency: "EUR",
speed: "instant",
customer: { id: "plr_7Hq2xK" },
destination: {
type: "bank_account",
iban: "DE89370400440532013000",
holderName: "Lena Hoffmann",
},
reference: "W-48213",
}, { idempotencyKey: "wd_01J9ZM2Q4R" });payout = client.payouts.create(
amount=125000,
currency="EUR",
speed="instant",
customer={"id": "plr_7Hq2xK"},
destination={
"type": "bank_account",
"iban": "DE89370400440532013000",
"holder_name": "Lena Hoffmann",
},
reference="W-48213",
idempotency_key="wd_01J9ZM2Q4R",
)$payout = $orbita->payouts->create([
'amount' => 125000,
'currency' => 'EUR',
'speed' => 'instant',
'customer' => ['id' => 'plr_7Hq2xK'],
'destination' => [
'type' => 'bank_account',
'iban' => 'DE89370400440532013000',
'holder_name' => 'Lena Hoffmann',
],
'reference' => 'W-48213',
], ['idempotency_key' => 'wd_01J9ZM2Q4R']);{
"id": "po_8Vn2Kx4TqM",
"object": "payout",
"status": "sent",
"amount": 125000,
"currency": "EUR",
"speed": "instant",
"rail": "eu_instant",
"estimated_arrival": "2026-10-03T14:02:13Z",
"reference": "W-48213",
"created": 1759500131
}
Instant payouts are available in 42 markets. If the destination bank can't receive instant transfers, Orbita falls back to the fastest available rail and tells you in payout.updated.
Events
Webhooks
Orbita sends events to your HTTPS endpoint as JSON POST requests. Respond with any 2xx status within 10 seconds; failed deliveries are retried with exponential back-off for 72 hours and can be replayed from the dashboard.
Common events
| Event | Sent when |
|---|---|
payment.approved | Funds are authorised and can be credited |
payment.declined | Final decline after all cascades |
payment.requires_action | The player must finish a redirect or challenge |
payout.sent | The payout was handed to the rail |
payout.failed | The destination rejected the payout |
chargeback.opened | An issuer raised a dispute |
settlement.paid | A settlement batch reached your account |
{
"id": "evt_5Rm9Lq2Wz8",
"type": "payment.approved",
"created": 1759484402,
"livemode": false,
"data": {
"object": {
"id": "pay_3Kd8Wq2LmZ",
"status": "approved",
"amount": 25000,
"currency": "EUR",
"route": { "provider": "a2a_de_02", "attempts": 1 }
}
}
}
Verifying signatures
Every request carries an Orbita-Signature header with a timestamp t and an HMAC-SHA256 signature v1 of t + "." + raw_body, signed with your endpoint's whsec_ secret.
Orbita-Signature: t=1759484402,v1=6f1c2b0e9a7d44c3b8e5f2a1d0c9b8a7e6f5d4c3b2a1908f7e6d5c4b3a291807import crypto from "node:crypto";
const TOLERANCE = 300; // seconds
export function verifyOrbitaSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const age = Math.floor(Date.now() / 1000) - Number(parts.t);
if (age > TOLERANCE) throw new Error("Timestamp outside tolerance");
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex"));
if (!valid) throw new Error("Invalid signature");
return JSON.parse(rawBody);
}import hashlib
import hmac
import json
import time
TOLERANCE = 300 # seconds
def verify_orbita_signature(raw_body: bytes, header: str, secret: str) -> dict:
parts = dict(item.split("=", 1) for item in header.split(","))
if time.time() - int(parts["t"]) > TOLERANCE:
raise ValueError("Timestamp outside tolerance")
signed = parts["t"].encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts["v1"]):
raise ValueError("Invalid signature")
return json.loads(raw_body)<?php
function verifyOrbitaSignature(string $rawBody, string $header, string $secret): array
{
parse_str(str_replace(',', '&', $header), $parts);
if (time() - (int) $parts['t'] > 300) {
throw new RuntimeException('Timestamp outside tolerance');
}
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
if (!hash_equals($expected, $parts['v1'])) {
throw new RuntimeException('Invalid signature');
}
return json_decode($rawBody, true);
}Parsing and re-serialising the JSON changes whitespace and key order and will break the signature. Reject events older than 5 minutes to protect against replay attacks.
Reference
Errors
Orbita uses conventional HTTP status codes. Codes in the 2xx range mean success, 4xx mean the request failed given the information provided, and 5xx are rare errors on our side that are always safe to retry with the same idempotency key.
| Status | Meaning |
|---|---|
| 200 | OK — the request succeeded. |
| 201 | Created — a new payment or payout was created. |
| 400 | Bad request — a parameter is missing or malformed. |
| 401 | Unauthorized — no valid API key was provided. |
| 402 | Request failed — valid request, but the payment was declined. |
| 404 | Not found — the resource doesn't exist. |
| 409 | Conflict — idempotency key reused with different parameters. |
| 429 | Too many requests — back off and honour Retry-After. |
| 5xx | Server error — retry with the same idempotency key. |
{
"error": {
"type": "payment_error",
"code": "insufficient_funds",
"message": "The card has insufficient funds for this deposit.",
"decline": "soft",
"retryable": true,
"payment": "pay_9Xc1Pd4ReN",
"request_id": "req_2Hk7Tn5Vb3"
}
}
Decline codes
| Code | What happened | Type |
|---|---|---|
insufficient_funds | The player's balance is too low | Soft · cascades |
do_not_honor | Generic issuer decline | Soft · cascades |
authentication_required | A 3-D Secure challenge is needed | Soft · challenge |
method_unavailable | The method is temporarily down in this country | Soft · fallback |
risk_blocked | Blocked by your Fraud shield rules | Hard |
limit_exceeded | A player or responsible-gaming limit was reached | Hard |
Reference
SDKs
Official libraries wrap authentication, retries, idempotency and webhook verification. They are versioned with the API and open to contributions.
npm install @orbita/nodepip install orbitacomposer require orbita/orbita-phpgo get orbitapay.dev/orbita-go/v2dev.orbitapay:orbita-java:1.14.2Drop-in cashier with native wallets, saved methods and 3-D Secure built in.
Was this page helpful?