Skip to main content

Quick Start — Your First Payment in 15 Minutes

This guide takes a payment from nothing to settled. No prior knowledge of the platform required.

Prerequisites​

Your operator issues two values together, and you need both:

  • an API key (Partner Area → Settings → API Keys);
  • the organization id the key lives in.

Plus curl, or any HTTP client. Faster still: the SDK does the same four calls in a dozen lines.

Sandbox first

Integrate against https://sandbox.4pay.online and switch the host when you go live. The sandbox is the same platform with simulated providers — see Sandbox & Testing.


Step 1 — Check your connection​

curl https://sandbox.4pay.online/api/v1/health
{
"core_status": "ok",
"db_status": "ok",
"appversion": "f0175d14",
"env": "sandbox",
"domain": "sandbox.4pay.online"
}

Readiness is core_status. There is no field called status.


Step 2 — Create a payment​

curl -X POST https://sandbox.4pay.online/api/v1/transactions \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: YOUR_ORGANIZATION_ID" \
-d '{
"params": {
"type": "payment",
"amount": "1000",
"currency": "USD",
"txid": "order-12345",
"env": "test",
"hosted": true,
"returnUrl": "https://yoursite.com/success",
"failUrl": "https://yoursite.com/fail"
}
}'
danger
hosted has no default — set it

Leave hosted out and the request is read as server-to-server: the platform looks for card details, finds none, and answers 201 Created with "status": "rejected" and "error_description": "unexpected card". This is the single most common reason a first integration produces transactions that never charge anyone.

Amount format

amount is a string holding the smallest currency unit (cents, pence, kopecks). "1000" = $10.00 USD. It travels as a string because assets with eighteen decimals do not fit a JSON number safely — send "1000", not 1000.

Response:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "created",
"type": "payment",
"env": "test",
"txid": "order-12345",
"amount": "1000",
"currency": "USD",
"started": "2026-09-03 11:18:30.101839 Etc/UTC",
"widget_url": "https://pay.sandbox.4pay.online?id=550e8400-...&org_id=...&token=eyJhbGciOi..."
}

Redirect the payer to widget_url — the hosted payment page, where they enter their card. Your server never touches card data, which is what keeps you out of PCI DSS scope.

Three things about that response

widget_url expires in about 15 minutes. Its token is a short-lived JWT. Redirect immediately; never store the URL or email it for later. If the payer comes back tomorrow, create a new transaction.

201 Created does not mean accepted. Routing, limits and anti-fraud all refuse a payment by creating it as rejected. Read status on every response, not just the HTTP code.

Timestamps are not ISO 8601. started and finished arrive as 2026-09-03 11:18:30.101839 Etc/UTC — a space instead of T, a named zone. Most date parsers return an invalid date; some silently read it as local time. The SDK handles it.


Step 3 — Check the transaction status​

curl https://sandbox.4pay.online/api/v1/transactions/550e8400-e29b-41d4-a716-446655440000 \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: YOUR_ORGANIZATION_ID"

Both headers, every time. The key is resolved inside the organization the second header names, and a request carrying only the key is refused.

Transaction statuses:

StatusFinalMeaning
created, newnoCreated, not yet routed
authorizingnoBeing authorised
action_requirednoThe payer has to do something
chargingnoBeing processed by the provider
chargedyesSettled ✅
rejectedyesRefused before or by the provider — funds released
failedyesProcessing error, or cancelled before settlement — funds released
reversedyesA settled payment was returned
refundednoRefund completed

Act on final statuses only, and treat any status you do not recognise as non-final — the list is not closed, rails carry their own intermediate states. The full status model is in the protocol specification.


Step 4 — Watch it settle​

In the sandbox you can charge a card directly and watch the whole lifecycle, without a browser:

curl -X POST https://sandbox.4pay.online/api/v1/transactions \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: YOUR_ORGANIZATION_ID" \
-d '{
"params": {
"type": "payment",
"amount": "1000",
"currency": "USD",
"txid": "order-12346",
"env": "test",
"show_widget": false,
"money_storage": {
"cardnumber": "2201382000000013",
"cvv": "283",
"cardholder": "IVAN IVANOV",
"exp_year": "20",
"exp_month": "12"
},
"returnUrl": "https://yoursite.com/success",
"failUrl": "https://yoursite.com/fail"
}
}'

It comes back charging, and reaches charged on its own within about a minute. Poll Step 3, or take the webhook below.

The test card is matched whole

Number, CVV, holder and expiry are compared as one tuple. IVAN IVANOV in capitals, exp_year of "20" — any difference at all gives rejected / unexpected card. The full set of cards is in Sandbox & Testing.

A server-to-server payment also needs both returnUrl and failUrl. Without them the platform creates the transaction and immediately rejects it with return_url/fail_url params not specified. Hosted payments and payouts do not need them.


Step 5 — Receive webhook notifications​

Instead of polling, have your operator set a notify_url on your terminal in the Admin Console, together with a webhook secret. It applies to every transaction on that terminal — there is no per-request notification field, and a notifyUrl in the create body is ignored.

When the status changes, a signed POST arrives at that URL:

{
"event_id": "4d9d0e6c-1d0a-4a2e-9a2f-0c9a1b0e8f21",
"timestamp": 1772668800,
"type": "transaction.charged",
"data": {
"txid": "order-12345",
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "charged",
"amount": "1000",
"amount_dest": "950"
}
}

Verify the signature before trusting it, and deduplicate on event_id — full details in Webhooks.


Where to go next​

I want to…Link
Do all of this in a dozen lines of codeSDK
Let an AI agent take paymentsAgentic Payments
Understand every field of a transactionTransactions
Handle failures properlyError Reference
Read the formal contractUniversal Transaction Protocol