Skip to main content

Transactions

The transaction is the core object of the 4pay.online API. Every payment, payout, and refund is a transaction.

Base URL: https://4pay.online/api/v1/transactions


Create a transaction​

POST /api/v1/transactions

Request body​

{
"params": {
"type": "payment",
"amount": "1000",
"currency": "USD",
"txid": "order-12345",
"env": "test",
"hosted": true,
"returnUrl": "https://yoursite.com/success",
"failUrl": "https://yoursite.com/fail"
}
}

Parameters​

FieldTypeRequiredDescription
typestring✅Transaction type: payment, payout
amountstring✅Amount in smallest currency unit, as a decimal string ("1000" = 10.00 USD)
currencystring✅ISO 4217 currency code (USD, EUR, USDT, …)
txidstring—Your order ID. Free-form; unique per partner. Enables idempotency — see below
returnUrlstring—Where the payer lands after paying. Required for a server-to-server payment; optional for hosted payments and payouts
failUrlstring—Where the payer lands after a failure. Same rule as returnUrl
envstring✅prod or test. Must be inside params — see below
hostedboolean—true sends the payer to the hosted page. No default — see below. show_widget is an older alias for the same thing
descriptionstring—Payment description shown to the payer
customerobject—Payer info: { email, phone, name }
langstring—UI language: en, ru, ar, es, …
merchant_metaobject—Your opaque data, returned on reads and in webhooks unchanged

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.4pay.online?id=550e8400-...&org_id=...&token=eyJhbGciOi..."
}

Redirect your user to widget_url to complete the payment. The token in it is a JWT valid for about 15 minutes — redirect promptly, and create a new transaction rather than storing the URL.

danger
hosted has no default

A create without hosted: true (or show_widget: true) is treated as server-to-server: the platform expects card details in money_storage, and without them answers 201 Created with "status": "rejected" and "error_description": "unexpected card". Nothing in the HTTP status tells you the payment was refused.

A server-to-server payment additionally requires both returnUrl and failUrl; without them it is created and immediately rejected with return_url/fail_url params not specified.

Timestamps are not ISO 8601

started and finished come back as 2026-09-03 11:18:30.101839 Etc/UTC — a space where T belongs, six fractional digits, a named zone. new Date(...) in JavaScript returns an invalid date; datetime.fromisoformat in Python raises; PHP reads a zoneless value in the server's own timezone. The SDK parses it correctly in all three.


Idempotency​

txid is any string you choose, and it is unique per partner. Repeating a create request with a txid that already exists does not create a second transaction — you get the existing one back, again with 201 and a location header pointing at it. Two partners inside one organization may reuse the same txid without colliding.

A transaction created without txid has no idempotency at all: a retry after a timeout produces a second transaction, and therefore a second payment. Send a txid for anything that involves money.

Constraints​

FieldConstraint
txidUp to 255 characters. A longer value is refused with 422, not a database error
txidCase-sensitive and stored exactly as sent — ORDER-abc and order-abc are two different transactions
amountMust be greater than zero. "0" and a negative value are both refused with 422
amountArbitrary-precision decimal string — an 18-digit value (1 ETH in wei) is accepted as-is
caution
Idempotency ignores everything except txid

Repeating a create with the same txid but a different amount still returns the original transaction, unchanged, at its original amount. The new amount is not applied and no error is raised — read the response instead of assuming the latest amount took effect.

Environment​

Send env inside params. A ?env= query parameter selects the environment a listing or lookup searches; it does not set the environment of a transaction being created. A create request carrying only ?env=test produces a production transaction. When the payment needs something from the payer mid-flow instead, the action arrives in auth_params — see the Universal Transaction Protocol.


Get a transaction​

GET /api/v1/transactions/:id
curl https://4pay.online/api/v1/transactions/550e8400-e29b-41d4-a716-446655440000 \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: 00000000-1111-2222-3333-444444444444"

Response​

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "charged",
"txid": "order-12345",
"amount": "1000",
"amount_with_fee": "1050",
"amount_dest": "950",
"currency": "USD",
"currency_dest": "USD",
"started": "2026-03-01T10:00:00Z",
"finished": "2026-03-01T10:05:00Z"
}

Amount fields​

FieldDescription
amountOriginal transaction amount (what the payer pays)
amount_with_feeAmount including platform fee
amount_destAmount credited to you (after fee deduction)

List transactions​

GET /api/v1/transactions
curl "https://4pay.online/api/v1/transactions?status=charged&limit=50" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: 00000000-1111-2222-3333-444444444444"

Query parameters​

ParameterTypeDescription
statusstringFilter by status
typestringFilter by type: payment, payout
envstringprod or test — which environment to search
limitintegerResults per page (default: 20, max: 100)
cursorstringcursor_value from the previous response — see paging below
started_from, started_toISO 8601Bounds on creation time
updated_from, updated_toISO 8601Bounds on last update
finished_from, finished_toISO 8601Bounds on completion
amount_from, amount_tostringBounds on amount, in minor units
order_bystringstarted (default) or updated
rrnstringRetrieval reference number
link_iduuidPayments made through one payment link

Anything not in that table is refused with 422 Unexpected field — including page, offset, from, to and currency, all of which look plausible and none of which exist.

Paging​

Responses carry a cursor block; pass its cursor_value back as cursor to get the next page.

{
"data": [ … ],
"cursor": { "cursor_field": "started", "cursor_value": "2026-09-03T11:20:45.377937Z" }
}
curl "https://4pay.online/api/v1/transactions?env=test&limit=50&cursor=2026-09-03T11:20:45.377937Z" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: YOUR_ORGANIZATION_ID"

A page whose cursor_value is null is the last one.

Two silent traps in the listing

start_cursor is accepted and then ignored unless end_cursor comes with it (that pair is a windowed read). A loop built on start_cursor alone re-reads page one for ever, and looks exactly like "there is nothing more".

txid is likewise accepted from a partner key and ignored, returning your whole list rather than one order. Keep the platform id you got from the create call and read it back with GET /api/v1/transactions/:id.


Aggregate transactions​

Get summarized stats across multiple transactions:

GET /api/v1/transactions/aggregate
curl "https://4pay.online/api/v1/transactions/aggregate?currency=USD&status=charged" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: 00000000-1111-2222-3333-444444444444"

Update a transaction​

Use PUT /api/v1/transactions/:id to issue a refund or cancel a transaction.

Refund​

curl -X PUT https://4pay.online/api/v1/transactions/550e8400-... \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: 00000000-1111-2222-3333-444444444444" \
-d '{
"params": {
"type": "refund",
"amount": "500"
}
}'

Omit amount for a full refund.

Cancel​

curl -X PUT https://4pay.online/api/v1/transactions/550e8400-... \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: 00000000-1111-2222-3333-444444444444" \
-d '{
"params": {
"type": "cancel"
}
}'

Two-phase payments (Authorize + Capture)​

For providers that support two-phase, first create with type: "payment" — the funds are held but not captured. Then commit:

POST /api/v1/transactions/commit/:id
curl -X POST https://4pay.online/api/v1/transactions/commit/550e8400-... \
-H "x-api-key: YOUR_API_KEY" \
-H "x-organization-id: 00000000-1111-2222-3333-444444444444"

3DS / Additional authentication​

When a transaction needs additional authentication (3-D Secure, a one-time password, a wallet signature), its status becomes action_required and the transaction carries the action itself in auth_params — there is no action_required boolean. Render the action as described in the protocol specification. Material the payer must sign or complete arrives in additional_params_for_auth, with an expires_at after which it must be requested again. Submit the result with:

POST /api/v1/transactions/additional_params_for_auth/:id
{
"params": {
"otp": "123456"
}
}

Transaction lifecycle​

created
└─► charging processing
├─► action_required payer action needed, then back to charging
├─► charged ✅ settled
├─► rejected ❌ no routing, limits or anti-fraud
└─► failed ❌ provider error, or cancelled before settlement
└─► cancelled cancelled before processing started

charged
├─► reversed a settled transaction was returned
└─► refunded refund completed

charged, rejected, failed and reversed are final. Everything else means the transaction is still in flight.

reversed can only follow charged — it is the return of a settled transaction, not a cancellation. A cancellation before settlement arrives as failed.