Download OpenAPI specification:
Payin, payout, refund and transaction lookup over a brand API key.
The client API is how a merchant backend moves money through the Payment
Solution platform. Five endpoints, all under /v1, all authenticated by a
brand API key.
Money coming in is a payin (POST /v1/payins); money going out
is a payout (POST /v1/payouts). A refund is client-initiated and
draws against a completed payin's refundable remainder. A reversal is
provider-initiated compensation — a returned payout, a chargeback — which
can only ever be observed, never requested. Refunds and reversals draw
against the same remainder, so do not model them as one thing.
Authorization: Bearer psk_… (or X-Api-Key: psk_…) on every request.Idempotency-Key is required on all three POSTs.{status, code, message, details, timestamp} with a
stable machine-readable code.CREATED → PENDING | PROCESSING → COMPLETED | FAILED | CANCELLED | EXPIRED,
with IN_DOUBT as the ambiguity state. COMPLETED, FAILED, CANCELLED
and EXPIRED are terminal — see the integration guide for the two
deliberate exceptions and what each status means for a client.
Status changes a client could not have learned from its own API call are
delivered as signed HTTP POSTs to the brand's callback URL. They are
described under webhooks at the bottom of this document; the signature
recipe and a worked example are in the integration guide.
Where this document says contact support, use the support channel you were given during onboarding.
Creates a payin and calls the routed PSP. What comes back depends on the provider and method:
PENDING with a redirectUrl — send
the end user there. For a page that opens only by form POST the URL
is on the platform's own host (/handoff/…) and forwards the
browser; follow it unchanged like any other;PENDING with
paymentInstructions and no redirectUrl;FAILED with error;IN_DOUBT — the platform reconciles it and sends a callback. It is
never terminal-failed, because the PSP may be executing it.A few rails also need the PAYER's own account on them before the
request can be raised at all — a UPI VPA. Those options are marked
requiresPayerAccount on GET /v1/payment-methods, and the value goes
in payerAccount.
Validation order matters, because everything below happens before a transaction row exists, so a refusal costs nothing: bean validation → idempotency → routing → required customer fields → required request fields → payer account. Only then is a row persisted and the PSP called.
| Idempotency-Key required | string [ 1 .. 64 ] characters ^[A-Za-z0-9_-]{1,64}$ Examples: order-8891-payin-01H9 Required on every POST. Generate it once per business intent — before the call, and durably — and reuse it on every retry of that intent. A key minted after a successful response can never help you. Same key + same payload = replay: Unique per brand; keys are compared exactly after trimming surrounding whitespace. |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string^[A-Z]{3}$ ISO-4217, uppercase. |
| method required | string [ 1 .. 32 ] characters ^[A-Z0-9_]+$ A payment method token from |
| provider | string or null <= 64 characters Pin the operation to one provider code. Omitted = the platform routes by the brand's method rules, which is the normal case. |
| userRef required | string [ 1 .. 64 ] characters Your end-user reference. The platform holds no user accounts, so this is the only identity a transaction carries. Echoed on every view and callback. |
| clientTxRef | string or null <= 64 characters Your own reference for this operation, echoed back everywhere.
Note that this is not the idempotency mechanism — it is not
checked for uniqueness. Use |
| country | string or null^[A-Z]{2}$ ISO-3166-1 alpha-2. Required when |
| returnUrl | string or null <= 1024 characters Where the PSP sends the end user back after the hosted page.
Required when |
| payerAccount | string or null <= 128 characters ^\s*[^\s\u0000-\u001F\u007F]*\s*$ The payer's own account on the rail being paid from — a UPI VPA. One token: no spaces and no control characters, surrounding whitespace trimmed away, a blank value read as absent. Send it exactly when Forwarded to the provider and nothing else: it is never stored and never echoed back. |
| expiresInSeconds | integer or null [ 60 .. 86400 ] How long this payment link stays payable, in seconds, counted from
the moment the payin is created. Omitted (the default) = the
platform's own deadline for the rail applies and It can only bring the deadline forward: the platform's own
deadline still applies and whichever comes first wins. The
provider's page has a lifetime of its own besides, so a link may
stop working before Outside the accepted range the request is refused with
|
object (CustomerDetails) End-user data forwarded to the PSP, in-flight only — the platform keeps no end-user PII at rest and holds no end-user accounts. Which of these are required depends on the routed provider, the
method and the currency. Read Blank strings count as absent. The list is the minimum, not a whitelist: you may send any other
field you hold ( |
{- "amount": 25,
- "currency": "EUR",
- "method": "CARD",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "customer": {
- "email": "alex@example.com",
- "firstName": "Alex",
- "lastName": "Example"
}
}{- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}Executes a payout immediately — the platform performs no approval step of its own, because approval already happened on the client's side before the call.
The destination rule is the whole endpoint. For a normal method,
destination is required; it is stored encrypted under the brand key
and only a head+tail mask (destinationMasked) is ever readable
afterwards. For a method flagged hostedPayout on
GET /v1/payment-methods, the PSP collects the destination on its own
page: send no destination and no bankCode, and send the
end user to the redirectUrl on the answer. Sending one anyway is
refused (DESTINATION_NOT_ACCEPTED) rather than quietly stored and
ignored — this is what makes a card payout possible without a PAN ever
reaching the platform.
Where the account number does not route itself — a UK sort code on
Faster Payments, an IFSC on India's bank transfer — send bankCode
beside destination. Whether that applies depends on method and
currency: read requiresDestinationBankCode per (method, currency)
from GET /v1/payment-methods.
Some bank rails also demand a block DESCRIBING the receiving account —
its type, its bank, its branch — before they will accept the payout at
all. Send those in destinationDetails, and send exactly the fields
requiredDestinationDetails names for this (method, currency): a
missing one is DESTINATION_DETAILS_REQUIRED (with the field names in
details), and sending the object at all to a rail whose list is empty
is DESTINATION_DETAILS_NOT_ACCEPTED rather than data we accept and
then drop.
For a hosted payout method the platform may be able to pay a card the end user has
already paid in with; the answer then carries no redirectUrl, starts at PROCESSING,
and names the card in destinationMasked.
| Idempotency-Key required | string [ 1 .. 64 ] characters ^[A-Za-z0-9_-]{1,64}$ Examples: order-8891-payin-01H9 Required on every POST. Generate it once per business intent — before the call, and durably — and reuse it on every retry of that intent. A key minted after a successful response can never help you. Same key + same payload = replay: Unique per brand; keys are compared exactly after trimming surrounding whitespace. |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string^[A-Z]{3}$ |
| method required | string [ 1 .. 32 ] characters ^[A-Z0-9_]+$ A method token from |
| provider | string or null <= 64 characters |
| userRef required | string [ 1 .. 64 ] characters |
| clientTxRef | string or null <= 64 characters |
| country | string or null^[A-Z]{2}$ |
| destination | string or null <= 256 characters The account being paid — an IBAN, a domestic account number, a
wallet account, a crypto address. What it is for a given
(method, currency) is published as Stored encrypted under the brand key; only Omit it only for a method flagged |
| bankCode | string or null <= 16 characters ^[A-Z0-9][A-Z0-9 -]*$ The routing code that completes Uppercase letters and digits, optionally separated by spaces or
dashes as a human would type them (a lowercase one is
Send it exactly when Stored encrypted and never echoed back — a routing code names a bank or a branch, not an account, so there is nothing in it worth displaying. |
object (PayoutDestinationDetails) Structured facts about the receiving account, beside the account
identifier itself. Send exactly the fields These are not stored: they are forwarded to the provider in flight and never echoed back on the transaction. | |
| returnUrl | string or null <= 1024 characters Where the end user returns from a hosted payout page. |
object (CustomerDetails) End-user data forwarded to the PSP, in-flight only — the platform keeps no end-user PII at rest and holds no end-user accounts. Which of these are required depends on the routed provider, the
method and the currency. Read Blank strings count as absent. The list is the minimum, not a whitelist: you may send any other
field you hold ( |
{- "amount": 120,
- "currency": "EUR",
- "method": "BANK_TRANSFER",
- "userRef": "user-10427",
- "clientTxRef": "withdrawal-4412",
- "country": "DE",
- "destination": "DE36000000000000000000",
- "customer": {
- "email": "alex@example.com",
- "firstName": "Alex",
- "lastName": "Example"
}
}{- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}A dry run of POST /v1/payouts. Same body, same authentication, and
nothing is created: no transaction, no callback, and no claim on an
idempotency key. Call it as often as you like.
It answers what the real call would have said about the destination:
check per thing that was looked at, each carrying the same
machine code the real call would have answered with;nameMatch).Shapes are checked against the same patterns
GET /v1/payment-methods publishes in destinationFields, so a form
built from that endpoint and a payout refused by this one can never
disagree. Where the routed provider publishes a destination check of
its own, it is asked — but only once the shape passes, so a malformed
value never costs a round trip.
It answers 200 even when the payout would be refused. "This
destination is wrong" is the answer, not an error; read valid. A
malformed request is still a 400, because there is nothing to
validate. A 200 with valid: true is a statement about the request
as it stands — it is not a promise that a payout will succeed.
A read-only API key may call this: it moves no money and creates nothing.
Sending the Idempotency-Key you intend to use for the real payout is
optional. It is read only as the seed that decides which provider the
payment would be offered to first, so the answer names the provider the
real call will actually reach. It is not consumed and reserves
nothing — the same key is still free for the payout itself.
| Idempotency-Key | string^[A-Za-z0-9_-]{1,64}$ Example: 550e8400-e29b-41d4-a716-446655440000 Optional here, and never consumed — see above. Same format as on the real call: 1-64 characters of letters, digits, dash and underscore. |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string^[A-Z]{3}$ |
| method required | string [ 1 .. 32 ] characters ^[A-Z0-9_]+$ A method token from |
| provider | string or null <= 64 characters |
| userRef required | string [ 1 .. 64 ] characters |
| clientTxRef | string or null <= 64 characters |
| country | string or null^[A-Z]{2}$ |
| destination | string or null <= 256 characters The account being paid — an IBAN, a domestic account number, a
wallet account, a crypto address. What it is for a given
(method, currency) is published as Stored encrypted under the brand key; only Omit it only for a method flagged |
| bankCode | string or null <= 16 characters ^[A-Z0-9][A-Z0-9 -]*$ The routing code that completes Uppercase letters and digits, optionally separated by spaces or
dashes as a human would type them (a lowercase one is
Send it exactly when Stored encrypted and never echoed back — a routing code names a bank or a branch, not an account, so there is nothing in it worth displaying. |
object (PayoutDestinationDetails) Structured facts about the receiving account, beside the account
identifier itself. Send exactly the fields These are not stored: they are forwarded to the provider in flight and never echoed back on the transaction. | |
| returnUrl | string or null <= 1024 characters Where the end user returns from a hosted payout page. |
object (CustomerDetails) End-user data forwarded to the PSP, in-flight only — the platform keeps no end-user PII at rest and holds no end-user accounts. Which of these are required depends on the routed provider, the
method and the currency. Read Blank strings count as absent. The list is the minimum, not a whitelist: you may send any other
field you hold ( |
{- "amount": 80,
- "currency": "GBP",
- "method": "BANK_TRANSFER",
- "userRef": "user-10427",
- "country": "GB",
- "destination": "00000000",
- "bankCode": "12-34-56",
- "customer": {
- "email": "alex@example.com",
- "firstName": "Alex",
- "lastName": "Example",
- "ipAddress": "203.0.113.10"
}
}{- "valid": true,
- "provider": "EXAMPLEPSP",
- "destinationKind": "BANK_ACCOUNT",
- "checks": [
- {
- "field": "destination",
- "ok": true,
- "code": null,
- "message": null
}, - {
- "field": "bankCode",
- "ok": true,
- "code": null,
- "message": null
}
], - "nameMatch": "NOT_SUPPORTED"
}Refunds a COMPLETED payin, fully or partially. Omit amount to
refund the full remaining refundable balance.
The refund is not routed — it follows its parent payin to the same PSP product that took the money, using the route settings snapshotted on the parent row.
The refundable remainder is enforced under a lock on the parent, so
concurrent refunds cannot overdraw it (REFUND_EXCEEDS_REMAINING). A
payin under an active dispute is refused outright (DISPUTED) — this
check runs first, because refunding a disputed payin is the classic
double-credit loss and every other refusal message would be a lie
about the real reason.
Whether a refund settles inside this response or later over a callback
is a per-provider property. Do not assume either; read status and
wait for a callback if it is not terminal.
| Idempotency-Key required | string [ 1 .. 64 ] characters ^[A-Za-z0-9_-]{1,64}$ Examples: order-8891-payin-01H9 Required on every POST. Generate it once per business intent — before the call, and durably — and reuse it on every retry of that intent. A key minted after a successful response can never help you. Same key + same payload = replay: Unique per brand; keys are compared exactly after trimming surrounding whitespace. |
| transactionId required | string [ 1 .. 40 ] characters Public id of the COMPLETED payin to refund. Anything else — another type, another status, another brand — is refused. |
Money (number) or null Omitted = the full remaining refundable amount (the payin's settled amount, less every refund and reversal already claimed against it). |
{- "transactionId": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ"
}{- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}The reconciliation route. Use it when a callback never arrived, when a POST failed in transport and you never learned its outcome, or after a restart mid-flight — ask the platform rather than guessing.
Scoped to the brand of the presented API key: a transaction belonging
to another brand answers 404 NOT_FOUND, exactly as an unknown id does.
Works for every transaction the brand owns, including REVERSAL rows
the platform created on its own from a PSP notification.
| publicId required | string Examples: tx_4bQ9xR2mKpLvN7sTfWgHdYcZ The transaction's public id, as returned in |
{- "id": "tx_7hK2mNpQrStUvWxYzAbCdEf",
- "type": "REVERSAL",
- "reversalKind": "CHARGEBACK",
- "status": "COMPLETED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 25,
- "settledCurrency": "EUR",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": null,
- "paymentInstructions": null,
- "destinationMasked": null,
- "parentTransactionId": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "error": null,
- "createdAt": "2026-09-18T14:03:02.117400Z",
- "completedAt": "2026-09-18T14:03:02.117400Z",
- "expiresAt": null,
- "fee": null,
- "attempts": [ ],
- "attemptCount": 0,
- "cascaded": false
}One row per enabled (provider config, method, currency, country) route the brand has. Build your payment form entirely from this list — limits, required customer fields, which directions each option supports, and what a payout destination for it actually is. Nothing about any specific PSP belongs in client code.
Rows come from your brand's current routing configuration, so the list changes without a release. Re-read it rather than caching it indefinitely.
An empty list means the brand currently has nothing matching: no
enabled provider configuration, no enabled routes on one, or no route
matching the filters you passed. That is a 200 with [], not an
error.
| currency | string Examples: currency=EUR Exact ISO-4217 match. Omitted = every currency. |
| country | string Examples: country=DE ISO-3166-1 alpha-2. A row whose own |
Illustrative values — provider codes, labels and limits are
whatever your own routing is configured with. Note the
shape rather than the contents: CARD here takes payins
only and needs no customer data, while CRYPTO works in
both directions, demands an e-mail, and is addressed to a
chain address rather than a bank account.
[- {
- "provider": "EXAMPLEPSP",
- "providerName": "Example Payments",
- "method": "CARD",
- "currency": "EUR",
- "country": null,
- "minAmount": 10,
- "maxAmount": 5000,
- "requiredCustomerFields": {
- "payin": [ ],
- "payout": [ ]
}, - "requiredRequestFields": {
- "payin": [ ],
- "payout": [ ]
}, - "supportsPayins": true,
- "supportsPayouts": false,
- "hostedPayout": false,
- "requiresDestinationBankCode": false,
- "requiresPayerAccount": false,
- "requiredDestinationDetails": [ ],
- "destinationKind": null,
- "destinationFields": [ ],
- "supportsRefunds": true,
- "supportsPartialRefunds": true
}, - {
- "provider": "EXAMPLEPSP",
- "providerName": "Example Payments",
- "method": "CRYPTO",
- "currency": "EUR",
- "country": null,
- "minAmount": 20,
- "maxAmount": 10000,
- "requiredCustomerFields": {
- "payin": [
- "EMAIL"
], - "payout": [
- "EMAIL"
]
}, - "requiredRequestFields": {
- "payin": [ ],
- "payout": [ ]
}, - "supportsPayins": true,
- "supportsPayouts": true,
- "hostedPayout": false,
- "requiresDestinationBankCode": false,
- "requiresPayerAccount": false,
- "requiredDestinationDetails": [ ],
- "destinationKind": "CRYPTO_ADDRESS",
- "destinationFields": [
- {
- "field": "destination",
- "shape": "CRYPTO_ADDRESS",
- "label": "Wallet address",
- "example": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
- "pattern": "^\\S+$",
- "normalisation": "NONE"
}
], - "supportsRefunds": true,
- "supportsPartialRefunds": true
}
]Withdrawal requests — a request that becomes one or more payouts, with an optional approval step.
A request to pay an end user, which the platform turns into one or more
ordinary payouts. Use it instead of POST /v1/payouts when the payment
needs an approval step, may be too large for a single route, or is paid
in parts of your choosing. The one-shot payout is unchanged and remains
the simple default.
Approval is the brand's setting, frozen onto the request as
approval: NONE (approved at once), CLIENT (you decide it with
/approve or /reject), MANUAL (a person decides it in the manual review queue).
A brand set to a threshold resolves to MANUAL at or above the figure
and NONE below it; the figure is in one currency and is never
converted, so a request in any other currency goes to MANUAL.
Execution is yours per request. AUTO (the default): once approved
the platform makes the payouts itself — one for the whole amount, or,
when every route refuses the amount as too large, the fewest equal parts
under the largest route maximum, one after another, stopping at the
first part that does not complete. CLIENT: you create each payout
with POST /v1/withdrawals/{id}/payouts.
The routing and destination rules are those of a payout and are checked
here, before any request exists, with the same refusal codes. A request
carries no customer data, bank block or return URL — it may wait for
approval and the platform keeps no customer data at rest — so an AUTO
request to a route that needs any of them is refused; use CLIENT
execution, whose payouts carry them. A hosted payout method cannot be
used: a request needs its destination.
Every payout goes through the ordinary payout path — your
pre-authorisation hook is asked for each one, and each one appears in
GET /v1/transactions/{id} and in its own callbacks.
| Idempotency-Key required | string [ 1 .. 64 ] characters ^[A-Za-z0-9_-]{1,64}$ Examples: order-8891-payin-01H9 Required on every POST. Generate it once per business intent — before the call, and durably — and reuse it on every retry of that intent. A key minted after a successful response can never help you. Same key + same payload = replay: Unique per brand; keys are compared exactly after trimming surrounding whitespace. |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string^[A-Z]{3}$ |
| method required | string <= 32 characters ^[A-Z0-9_]+$ |
| provider | string <= 64 characters Pins every payout of the request to one provider, as on a payout. |
| userRef required | string <= 64 characters |
| clientRef | string <= 64 characters Your own reference; copied onto every payout as its |
| country | string^[A-Z]{2}$ |
| destination required | string <= 256 characters Required. Stored encrypted, echoed only as |
| bankCode | string <= 16 characters The routing half of a two-part destination, exactly as on a payout. |
| execution | string Default: "AUTO" Enum: "AUTO" "CLIENT" Who creates the payouts once the request is approved. |
{- "amount": 300,
- "currency": "EUR",
- "method": "BANK_TRANSFER",
- "userRef": "user-10427",
- "clientRef": "withdrawal-4412",
- "country": "DE",
- "destination": "DE36000000000000000000"
}{- "id": "wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x",
- "status": "AWAITING_APPROVAL",
- "amount": 25,
- "currency": "string",
- "paidAmount": 0,
- "inFlightAmount": 0,
- "remainingAmount": 0,
- "method": "string",
- "provider": "string",
- "userRef": "string",
- "clientRef": "string",
- "country": "string",
- "destinationMasked": "string",
- "approval": "NONE",
- "execution": "AUTO",
- "payouts": [
- {
- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z",
- "decidedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z"
}The request, its money (paidAmount, inFlightAmount,
remainingAmount) and every payout made for it. Scoped to the brand of
the presented key: another brand's request answers 404 NOT_FOUND,
exactly as an unknown id does.
| publicId required | string Examples: wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x The withdrawal request's public id, as returned in |
{- "id": "wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x",
- "status": "AWAITING_APPROVAL",
- "amount": 25,
- "currency": "string",
- "paidAmount": 0,
- "inFlightAmount": 0,
- "remainingAmount": 0,
- "method": "string",
- "provider": "string",
- "userRef": "string",
- "clientRef": "string",
- "country": "string",
- "destinationMasked": "string",
- "approval": "NONE",
- "execution": "AUTO",
- "payouts": [
- {
- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z",
- "decidedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z"
}Only on a request whose approval is CLIENT and whose status is
AWAITING_APPROVAL (409 otherwise). An AUTO request starts paying
at once, and the answer carries its first payout.
| publicId required | string Examples: wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x The withdrawal request's public id, as returned in |
{- "id": "wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x",
- "status": "AWAITING_APPROVAL",
- "amount": 25,
- "currency": "string",
- "paidAmount": 0,
- "inFlightAmount": 0,
- "remainingAmount": 0,
- "method": "string",
- "provider": "string",
- "userRef": "string",
- "clientRef": "string",
- "country": "string",
- "destinationMasked": "string",
- "approval": "NONE",
- "execution": "AUTO",
- "payouts": [
- {
- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z",
- "decidedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z"
}Only on a request whose approval is CLIENT and whose status is
AWAITING_APPROVAL (409 otherwise). Ends it as REJECTED; nothing is
paid. The optional reason is kept with the request for your own staff; it is never sent back.
| publicId required | string Examples: wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x The withdrawal request's public id, as returned in |
| reason | string <= 500 characters |
{- "reason": "string"
}{- "id": "wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x",
- "status": "AWAITING_APPROVAL",
- "amount": 25,
- "currency": "string",
- "paidAmount": 0,
- "inFlightAmount": 0,
- "remainingAmount": 0,
- "method": "string",
- "provider": "string",
- "userRef": "string",
- "clientRef": "string",
- "country": "string",
- "destinationMasked": "string",
- "approval": "NONE",
- "execution": "AUTO",
- "payouts": [
- {
- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z",
- "decidedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z"
}Before any payout exists (AWAITING_APPROVAL or APPROVED) the request
ends as CANCELLED. On a CLIENT-executed request that is under way it
drops the unpaid remainder: payouts already in flight finish, and the
request closes when the last of them ends — PARTIALLY_COMPLETED if
anything was paid, CANCELLED if nothing was. A request the platform is
paying (AUTO, IN_PROGRESS) cannot be stopped between its payouts
(409 WITHDRAWAL_NOT_CANCELLABLE); a closed one answers
409 WITHDRAWAL_CLOSED.
| publicId required | string Examples: wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x The withdrawal request's public id, as returned in |
{- "id": "wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x",
- "status": "AWAITING_APPROVAL",
- "amount": 25,
- "currency": "string",
- "paidAmount": 0,
- "inFlightAmount": 0,
- "remainingAmount": 0,
- "method": "string",
- "provider": "string",
- "userRef": "string",
- "clientRef": "string",
- "country": "string",
- "destinationMasked": "string",
- "approval": "NONE",
- "execution": "AUTO",
- "payouts": [
- {
- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z",
- "decidedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z"
}One payout of up to what is left (remainingAmount — neither paid nor
under way) of a CLIENT-executed request, to the request's own
destination. More than that is 400 AMOUNT_EXCEEDS_REMAINING with the
remainder in details; an AUTO request answers
409 WITHDRAWAL_NOT_CLIENT_EXECUTED; a closed or cancelled one
409 WITHDRAWAL_NOT_PAYABLE / WITHDRAWAL_CANCELLING.
It is an ordinary payout in every other respect: its own
Idempotency-Key, the same preflight and refusal codes, your
pre-authorisation hook, and the answer in the ordinary transaction view
(201, or 200 for a replay). Customer data, the bank block and a
return URL the route needs travel HERE, with the payout, and are never
stored. A payout that fails leaves the request open for another try.
| publicId required | string Examples: wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x The withdrawal request's public id, as returned in |
| Idempotency-Key required | string [ 1 .. 64 ] characters ^[A-Za-z0-9_-]{1,64}$ Examples: order-8891-payin-01H9 Required on every POST. Generate it once per business intent — before the call, and durably — and reuse it on every retry of that intent. A key minted after a successful response can never help you. Same key + same payload = replay: Unique per brand; keys are compared exactly after trimming surrounding whitespace. |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
object (PayoutDestinationDetails) Structured facts about the receiving account, beside the account
identifier itself. Send exactly the fields These are not stored: they are forwarded to the provider in flight and never echoed back on the transaction. | |
| returnUrl | string <= 1024 characters |
object (CustomerDetails) End-user data forwarded to the PSP, in-flight only — the platform keeps no end-user PII at rest and holds no end-user accounts. Which of these are required depends on the routed provider, the
method and the currency. Read Blank strings count as absent. The list is the minimum, not a whitelist: you may send any other
field you hold ( |
{- "amount": 100
}{- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}Delivered to the brand's configured callback URL for every status transition the client did not itself cause synchronously — a notification from the provider, the platform's own reconciliation of an unresolved transaction, or a manual intervention. A status the client already learned from its own API response produces no callback.
Answer 2xx to anything whose signature verified, including
duplicates, stale deliveries and transactions you do not recognise.
Anything else buys retries (and eventually a DEAD delivery) for
something a retry cannot fix. Refuse only what fails verification, with
401 and no explanation.
See the integration guide, § 5 "Verifying a callback signature".
| X-PS-Signature required | string Examples: t=1788528751,v1=2629194c7ff61de00be9dfcf45b480a44b758ff2ae3efa89466382f943b5f20f
|
| X-PS-Event required | string The event name, identical to Value: "transaction.status_changed" |
| X-PS-Delivery required | string Examples: 84213 Delivery id — the same value on every retry of this delivery. Deduplicate on it. |
| X-PS-Sequence required | string Examples: 2 This notice's place among every notice about its transaction —
1, 2, 3… per transaction, no gaps — repeating |
| event required | string Enum: "transaction.status_changed" "transaction.reversed" "transaction.corrected" "payin.abandoned" Identical to the |
| transactionId required | string |
| parentTransactionId required | string or null The transaction this one derives from — the refunded payin, or the movement a reversal compensates. Present on every payload that has a parent, not only on reversals. |
| type required | string (TransactionType) Enum: "PAYIN" "PAYOUT" "REFUND" "REVERSAL"
|
required | ReversalKind (string) or null Which compensation this is, when |
| status required | string (TransactionStatus) Enum: "CREATED" "PENDING" "PROCESSING" "IN_DOUBT" "COMPLETED" "FAILED" "CANCELLED" "EXPIRED"
The other exception to terminality: for some methods a |
| method required | string |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string |
| settledAmount required | number or null |
| settledCurrency required | string or null |
| provider required | string or null |
| userRef required | string |
| clientTxRef required | string or null |
| errorCode required | string or null |
| errorMessage required | string or null |
| errorReason required | string or null The failure in the platform's one vocabulary — the same value the
transaction view calls |
| errorAction required | string or null What to do about it — the same value as |
| errorRetryable required | boolean or null Whether the SAME request may be sent again later, unchanged. |
| errorCascadable required | boolean or null Whether a different provider could have taken this same request —
the same value as |
| errorUserMessage required | string or null One neutral English sentence, safe to show the end user. Null when there is no error. |
| occurredAt required | string <date-time> When the transition happened. The ordering key — arrival order is not. |
required | object or null (Fee) What the transaction cost you in fees, under your own contract with the provider, on the terms configured for your brand. Present once the fee has settled, which happens when the transaction
reaches Informational, and there so you can reconcile against the provider's
invoice without waiting for it. It is neither added to nor taken out of
|
required | Array of objects (Attempt) Every provider this payment was offered to, oldest first — exactly
as The |
| attemptCount required | integer >= 0
|
| cascaded required | boolean True when more than one provider was asked. |
required | CallbackCorrection (object) or null What a person changed, and why — present on |
| abandonedReason required | string or null Which deadline ran out on a payin reported abandoned. Present on
The vocabulary can grow: treat a value you do not recognise as an abandonment you cannot classify, never as an error. |
| sequence required | integer >= 1 This notice's place among every notice ever sent about its transaction: 1 for the first, then 2, 3… with no gaps and no repeats. It counts per TRANSACTION — a refund or a reversal is its own transaction and starts at 1 — and it is assigned when the notice is created, in the same database transaction as the change it reports. The platform does not deliver in order: a notice waiting out
its retry backoff can arrive after a later one, and a replay
re-sends an old one. This number is how you put them back in
order — apply a notice only when its Also sent as the |
{- "event": "transaction.status_changed",
- "transactionId": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "parentTransactionId": null,
- "type": "PAYIN",
- "reversalKind": null,
- "status": "COMPLETED",
- "method": "CARD",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 25,
- "settledCurrency": "EUR",
- "provider": "EXAMPLEPSP",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "errorCode": null,
- "errorMessage": null,
- "errorReason": null,
- "errorAction": null,
- "errorRetryable": null,
- "errorCascadable": null,
- "errorUserMessage": null,
- "occurredAt": "2026-09-04T09:12:31.482913Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attemptCount": 1,
- "cascaded": false,
- "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "ACCEPTED",
- "error": null,
- "startedAt": "2026-09-04T09:12:04.481700Z",
- "endedAt": "2026-09-04T09:12:04.769800Z"
}
], - "correction": null,
- "abandonedReason": null,
- "sequence": 1
}A deliberately distinct event name for a REVERSAL transaction: money
that moved and then moved back — a payout the bank returned, or a
chargeback / recall / fraud claim on a settled payin.
It is not a REFUND: a refund is client-initiated and draws
against a refundable budget; a reversal is inbound and nobody asked us
for it. The event name differs precisely so that an unprepared client
fails loudly at integration time instead of dropping the most
financially significant message the platform sends into an else
branch.
Every transition of the reversal row — its creation and its later
resolution alike — arrives under this same name. parentTransactionId
points at the movement being compensated, and reversalKind says
which compensation it is — including the one case where the money
comes back to you rather than leaving: CHARGEBACK_WON. Branch on
reversalKind, not on the event name and not on arrival order.
| X-PS-Signature required | string Examples: t=1788528751,v1=2629194c7ff61de00be9dfcf45b480a44b758ff2ae3efa89466382f943b5f20f
|
| X-PS-Event required | string Value: "transaction.reversed" |
| X-PS-Delivery required | string |
| X-PS-Sequence required | string Examples: 2 This notice's place among every notice about its transaction —
1, 2, 3… per transaction, no gaps — repeating |
| event required | string Enum: "transaction.status_changed" "transaction.reversed" "transaction.corrected" "payin.abandoned" Identical to the |
| transactionId required | string |
| parentTransactionId required | string or null The transaction this one derives from — the refunded payin, or the movement a reversal compensates. Present on every payload that has a parent, not only on reversals. |
| type required | string (TransactionType) Enum: "PAYIN" "PAYOUT" "REFUND" "REVERSAL"
|
required | ReversalKind (string) or null Which compensation this is, when |
| status required | string (TransactionStatus) Enum: "CREATED" "PENDING" "PROCESSING" "IN_DOUBT" "COMPLETED" "FAILED" "CANCELLED" "EXPIRED"
The other exception to terminality: for some methods a |
| method required | string |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string |
| settledAmount required | number or null |
| settledCurrency required | string or null |
| provider required | string or null |
| userRef required | string |
| clientTxRef required | string or null |
| errorCode required | string or null |
| errorMessage required | string or null |
| errorReason required | string or null The failure in the platform's one vocabulary — the same value the
transaction view calls |
| errorAction required | string or null What to do about it — the same value as |
| errorRetryable required | boolean or null Whether the SAME request may be sent again later, unchanged. |
| errorCascadable required | boolean or null Whether a different provider could have taken this same request —
the same value as |
| errorUserMessage required | string or null One neutral English sentence, safe to show the end user. Null when there is no error. |
| occurredAt required | string <date-time> When the transition happened. The ordering key — arrival order is not. |
required | object or null (Fee) What the transaction cost you in fees, under your own contract with the provider, on the terms configured for your brand. Present once the fee has settled, which happens when the transaction
reaches Informational, and there so you can reconcile against the provider's
invoice without waiting for it. It is neither added to nor taken out of
|
required | Array of objects (Attempt) Every provider this payment was offered to, oldest first — exactly
as The |
| attemptCount required | integer >= 0
|
| cascaded required | boolean True when more than one provider was asked. |
required | CallbackCorrection (object) or null What a person changed, and why — present on |
| abandonedReason required | string or null Which deadline ran out on a payin reported abandoned. Present on
The vocabulary can grow: treat a value you do not recognise as an abandonment you cannot classify, never as an error. |
| sequence required | integer >= 1 This notice's place among every notice ever sent about its transaction: 1 for the first, then 2, 3… with no gaps and no repeats. It counts per TRANSACTION — a refund or a reversal is its own transaction and starts at 1 — and it is assigned when the notice is created, in the same database transaction as the change it reports. The platform does not deliver in order: a notice waiting out
its retry backoff can arrive after a later one, and a replay
re-sends an old one. This number is how you put them back in
order — apply a notice only when its Also sent as the |
{- "event": "transaction.reversed",
- "transactionId": "tx_7hK2mNpQrStUvWxYzAbCdEf",
- "parentTransactionId": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "REVERSAL",
- "reversalKind": "CHARGEBACK",
- "status": "COMPLETED",
- "method": "CARD",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 25,
- "settledCurrency": "EUR",
- "provider": "EXAMPLEPSP",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "errorCode": null,
- "errorMessage": null,
- "errorReason": null,
- "errorAction": null,
- "errorRetryable": null,
- "errorCascadable": null,
- "errorUserMessage": null,
- "occurredAt": "2026-09-18T14:03:02.117400Z",
- "fee": null,
- "attempts": [ ],
- "attemptCount": 0,
- "cascaded": false,
- "correction": null,
- "abandonedReason": null,
- "sequence": 1
}An outcome that could not be established from a provider's answer was
established by a person instead: a transaction stuck at IN_DOUBT and
decided by hand, or a dispute settled somewhere the payment rail cannot
see.
It is a notice about a DECISION, not a second copy of a status. A
manual resolution also sends transaction.status_changed for the new
status; a dispute outcome is recorded against the disputed payin, whose
own status does not move, and a dispute WON additionally sends
transaction.reversed for the compensating row that returns the money.
Nothing is credited or debited by this event. Money is always in the transaction rows — the corrected transaction, or the reversal a won dispute writes. A ledger that moves an amount on a correction double-counts every win.
correction.previousStatus and correction.newStatus MAY be equal;
branch on correction.reason, not on whether they differ. The reason
vocabulary can grow: treat an unrecognised value as "this transaction
was corrected" and re-read the transaction.
| X-PS-Signature required | string Examples: t=1788528751,v1=2629194c7ff61de00be9dfcf45b480a44b758ff2ae3efa89466382f943b5f20f
|
| X-PS-Event required | string Value: "transaction.corrected" |
| X-PS-Delivery required | string |
| X-PS-Sequence required | string Examples: 2 This notice's place among every notice about its transaction —
1, 2, 3… per transaction, no gaps — repeating |
| event required | string Enum: "transaction.status_changed" "transaction.reversed" "transaction.corrected" "payin.abandoned" Identical to the |
| transactionId required | string |
| parentTransactionId required | string or null The transaction this one derives from — the refunded payin, or the movement a reversal compensates. Present on every payload that has a parent, not only on reversals. |
| type required | string (TransactionType) Enum: "PAYIN" "PAYOUT" "REFUND" "REVERSAL"
|
required | ReversalKind (string) or null Which compensation this is, when |
| status required | string (TransactionStatus) Enum: "CREATED" "PENDING" "PROCESSING" "IN_DOUBT" "COMPLETED" "FAILED" "CANCELLED" "EXPIRED"
The other exception to terminality: for some methods a |
| method required | string |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string |
| settledAmount required | number or null |
| settledCurrency required | string or null |
| provider required | string or null |
| userRef required | string |
| clientTxRef required | string or null |
| errorCode required | string or null |
| errorMessage required | string or null |
| errorReason required | string or null The failure in the platform's one vocabulary — the same value the
transaction view calls |
| errorAction required | string or null What to do about it — the same value as |
| errorRetryable required | boolean or null Whether the SAME request may be sent again later, unchanged. |
| errorCascadable required | boolean or null Whether a different provider could have taken this same request —
the same value as |
| errorUserMessage required | string or null One neutral English sentence, safe to show the end user. Null when there is no error. |
| occurredAt required | string <date-time> When the transition happened. The ordering key — arrival order is not. |
required | object or null (Fee) What the transaction cost you in fees, under your own contract with the provider, on the terms configured for your brand. Present once the fee has settled, which happens when the transaction
reaches Informational, and there so you can reconcile against the provider's
invoice without waiting for it. It is neither added to nor taken out of
|
required | Array of objects (Attempt) Every provider this payment was offered to, oldest first — exactly
as The |
| attemptCount required | integer >= 0
|
| cascaded required | boolean True when more than one provider was asked. |
required | CallbackCorrection (object) or null What a person changed, and why — present on |
| abandonedReason required | string or null Which deadline ran out on a payin reported abandoned. Present on
The vocabulary can grow: treat a value you do not recognise as an abandonment you cannot classify, never as an error. |
| sequence required | integer >= 1 This notice's place among every notice ever sent about its transaction: 1 for the first, then 2, 3… with no gaps and no repeats. It counts per TRANSACTION — a refund or a reversal is its own transaction and starts at 1 — and it is assigned when the notice is created, in the same database transaction as the change it reports. The platform does not deliver in order: a notice waiting out
its retry backoff can arrive after a later one, and a replay
re-sends an old one. This number is how you put them back in
order — apply a notice only when its Also sent as the |
{- "event": "transaction.corrected",
- "transactionId": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "parentTransactionId": null,
- "type": "PAYIN",
- "reversalKind": null,
- "status": "COMPLETED",
- "method": "CARD",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 25,
- "settledCurrency": "EUR",
- "provider": "EXAMPLEPSP",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "errorCode": null,
- "errorMessage": null,
- "errorReason": null,
- "errorAction": null,
- "errorRetryable": null,
- "errorCascadable": null,
- "errorUserMessage": null,
- "occurredAt": "2026-09-18T14:03:02.117400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attemptCount": 1,
- "cascaded": false,
- "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "ACCEPTED",
- "error": null,
- "startedAt": "2026-09-04T09:12:04.481700Z",
- "endedAt": "2026-09-04T09:12:04.769800Z"
}
], - "correction": {
- "reason": "DISPUTE_WON",
- "previousStatus": "COMPLETED",
- "newStatus": "COMPLETED",
- "disputeCaseId": "dsp_9tMv2QkR7bXpLs4nFhCdWyZa",
- "correctedAt": "2026-09-18T14:03:02.117400Z"
}, - "abandonedReason": null,
- "sequence": 2
}Sent once when a payin reaches its deadline with nothing paid — the end user never finished on the provider's page, or never opened it.
It arrives beside the ordinary transaction.status_changed that
carries status: EXPIRED, not instead of it: the status change is the
ledger fact, this is the marketing fact, and they are separate events so
that retargeting work need not subscribe to every status transition and
filter. Settle books on the status change; drive reminders off this.
At most one per transaction, ever, and never for a payin that completed, failed, was cancelled or is still being decided.
It is not final. EXPIRED is one of the two statuses late truth can
overrule: a pay-to-address rail may still receive funds afterwards, and
the same transaction id can later arrive as
transaction.status_changed with status: COMPLETED. Anything
automated off this must be safe to undo.
The browser returning from the provider's page is not this signal and never triggers it — that return is unauthenticated, it happens on cancel as well as success, and it does not happen at all when a tab is closed.
Answer 2xx to anything whose signature verified, exactly as for
transaction.status_changed.
See the integration guide, "Knowing when a payer walked away".
| X-PS-Signature required | string Examples: t=1788528751,v1=2629194c7ff61de00be9dfcf45b480a44b758ff2ae3efa89466382f943b5f20f
|
| X-PS-Event required | string Value: "payin.abandoned" |
| X-PS-Delivery required | string Delivery id — the same value on every retry of this delivery. Deduplicate on it. |
| X-PS-Sequence required | string Examples: 2 This notice's place among every notice about its transaction —
1, 2, 3… per transaction, no gaps — repeating |
| event required | string Enum: "transaction.status_changed" "transaction.reversed" "transaction.corrected" "payin.abandoned" Identical to the |
| transactionId required | string |
| parentTransactionId required | string or null The transaction this one derives from — the refunded payin, or the movement a reversal compensates. Present on every payload that has a parent, not only on reversals. |
| type required | string (TransactionType) Enum: "PAYIN" "PAYOUT" "REFUND" "REVERSAL"
|
required | ReversalKind (string) or null Which compensation this is, when |
| status required | string (TransactionStatus) Enum: "CREATED" "PENDING" "PROCESSING" "IN_DOUBT" "COMPLETED" "FAILED" "CANCELLED" "EXPIRED"
The other exception to terminality: for some methods a |
| method required | string |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string |
| settledAmount required | number or null |
| settledCurrency required | string or null |
| provider required | string or null |
| userRef required | string |
| clientTxRef required | string or null |
| errorCode required | string or null |
| errorMessage required | string or null |
| errorReason required | string or null The failure in the platform's one vocabulary — the same value the
transaction view calls |
| errorAction required | string or null What to do about it — the same value as |
| errorRetryable required | boolean or null Whether the SAME request may be sent again later, unchanged. |
| errorCascadable required | boolean or null Whether a different provider could have taken this same request —
the same value as |
| errorUserMessage required | string or null One neutral English sentence, safe to show the end user. Null when there is no error. |
| occurredAt required | string <date-time> When the transition happened. The ordering key — arrival order is not. |
required | object or null (Fee) What the transaction cost you in fees, under your own contract with the provider, on the terms configured for your brand. Present once the fee has settled, which happens when the transaction
reaches Informational, and there so you can reconcile against the provider's
invoice without waiting for it. It is neither added to nor taken out of
|
required | Array of objects (Attempt) Every provider this payment was offered to, oldest first — exactly
as The |
| attemptCount required | integer >= 0
|
| cascaded required | boolean True when more than one provider was asked. |
required | CallbackCorrection (object) or null What a person changed, and why — present on |
| abandonedReason required | string or null Which deadline ran out on a payin reported abandoned. Present on
The vocabulary can grow: treat a value you do not recognise as an abandonment you cannot classify, never as an error. |
| sequence required | integer >= 1 This notice's place among every notice ever sent about its transaction: 1 for the first, then 2, 3… with no gaps and no repeats. It counts per TRANSACTION — a refund or a reversal is its own transaction and starts at 1 — and it is assigned when the notice is created, in the same database transaction as the change it reports. The platform does not deliver in order: a notice waiting out
its retry backoff can arrive after a later one, and a replay
re-sends an old one. This number is how you put them back in
order — apply a notice only when its Also sent as the |
abandonedReason names the rule that ended it. Here it is
LINK_EXPIRED — the client's own expiresInSeconds ran out.
A payin with no client deadline is ended by the rail's own
window instead, and arrives identically but with
abandonedReason: PAYMENT_WINDOW_EXPIRED.
{- "event": "payin.abandoned",
- "transactionId": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "parentTransactionId": null,
- "type": "PAYIN",
- "reversalKind": null,
- "status": "EXPIRED",
- "method": "CARD",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": null,
- "settledCurrency": null,
- "provider": "EXAMPLEPSP",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "errorCode": null,
- "errorMessage": null,
- "errorReason": null,
- "errorAction": null,
- "errorRetryable": null,
- "errorCascadable": null,
- "errorUserMessage": null,
- "occurredAt": "2026-09-22T11:28:01.903100Z",
- "fee": null,
- "attemptCount": 1,
- "cascaded": false,
- "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "ACCEPTED",
- "error": null,
- "startedAt": "2026-09-22T11:12:04.481700Z",
- "endedAt": "2026-09-22T11:12:04.769800Z"
}
], - "correction": null,
- "abandonedReason": "LINK_EXPIRED",
- "sequence": 1
}Sent when a withdrawal request changes status, or when the amount paid
against it rises, for every change you did not cause in your own call:
an approval or rejection in the manual review queue, a payout completing on the
provider's notice, the platform starting the next part of a split
request, the request closing when its last payout ends. What a
/v1/withdrawals call answered you with produces no notice.
The body is the request view (the same one GET /v1/withdrawals/{id}
answers with) plus event, occurredAt and sequence — the notice's
place among every notice about THIS request, 1-based, also sent as
X-PS-Sequence. Order by sequence and discard anything not newer,
exactly as for transaction notices; deduplicate on X-PS-Delivery.
Each payout also produces its own transaction.status_changed where it
moved on a provider's notice. Settle your ledger on either, not both —
the request notice is the one that says whether anything more will be
paid.
A payout reversed after the request completed does not reopen it and
sends no request notice: the money coming back is its own transaction,
announced as transaction.reversed.
Answer 2xx to anything whose signature verified.
| X-PS-Signature required | string
|
| X-PS-Event required | string Value: "withdrawal.status_changed" |
| X-PS-Delivery required | string Delivery id — the same value on every retry. Deduplicate on it. |
| id required | string |
| status required | string (WithdrawalStatus) Enum: "AWAITING_APPROVAL" "APPROVED" "REJECTED" "IN_PROGRESS" "COMPLETED" "PARTIALLY_COMPLETED" "FAILED" "CANCELLED"
|
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string |
| paidAmount required | number The sum of the payouts that completed. It only rises — a payout reversed later is not subtracted. |
| inFlightAmount required | number The sum of the payouts still under way. |
| remainingAmount required | number
|
| method required | string |
| provider | string or null |
| userRef required | string |
| clientRef | string or null |
| country | string or null |
| destinationMasked required | string |
| approval required | string Enum: "NONE" "CLIENT" "MANUAL" The approval step that applied, frozen when the request was made. |
| execution required | string Enum: "AUTO" "CLIENT" |
required | Array of objects (TransactionView) Every payout made for the request, oldest first. |
| createdAt required | string <date-time> |
| updatedAt required | string <date-time> |
| decidedAt | string or null <date-time> |
| completedAt | string or null <date-time> |
| event required | string Value: "withdrawal.status_changed" |
| occurredAt required | string <date-time> |
{- "id": "wr_Q2mKpLvN7sTfWgHdYcZ4bQ9x",
- "status": "AWAITING_APPROVAL",
- "amount": 25,
- "currency": "string",
- "paidAmount": 0,
- "inFlightAmount": 0,
- "remainingAmount": 0,
- "method": "string",
- "provider": "string",
- "userRef": "string",
- "clientRef": "string",
- "country": "string",
- "destinationMasked": "string",
- "approval": "NONE",
- "execution": "AUTO",
- "payouts": [
- {
- "id": "tx_4bQ9xR2mKpLvN7sTfWgHdYcZ",
- "type": "PAYIN",
- "reversalKind": "PAYOUT_REVERSAL",
- "status": "CREATED",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "settledAmount": 0,
- "settledCurrency": "string",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "redirectUrl": "string",
- "paymentInstructions": {
- "payAddress": "string",
- "payAmount": 0,
- "payCurrency": "USDC",
- "network": "TRON",
- "qrData": "string",
- "memo": "string",
- "expiresAt": "2019-08-24T14:15:22Z"
}, - "destinationMasked": "DE00…0000",
- "parentTransactionId": "string",
- "error": {
- "code": "CARD_DECLINED",
- "message": "The issuer declined this card",
- "reason": "BANK_DECLINED",
- "action": "TRY_ANOTHER_METHOD",
- "retryable": false,
- "cascadable": false,
- "userMessage": "Your bank declined this payment. Please try another card or payment method.",
- "providerCode": "CARD_DECLINED"
}, - "createdAt": "2026-09-04T09:12:04.771200Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2026-09-22T11:27:03.118400Z",
- "fee": {
- "amount": 0.75,
- "currency": "EUR"
}, - "attempts": [
- {
- "attemptNo": 1,
- "provider": "EXAMPLEPSP",
- "outcome": "REFUSED",
- "error": {
- "code": "PSP_REJECTED",
- "reason": "PROVIDER_REJECTED",
- "action": "TRY_ANOTHER_METHOD"
}, - "startedAt": "2026-09-04T09:12:04.771200Z",
- "endedAt": "2026-09-04T09:12:05.113900Z"
}
], - "attemptCount": 1,
- "cascaded": false
}
], - "createdAt": "2019-08-24T14:15:22Z",
- "updatedAt": "2019-08-24T14:15:22Z",
- "decidedAt": "2019-08-24T14:15:22Z",
- "completedAt": "2019-08-24T14:15:22Z",
- "event": "withdrawal.status_changed",
- "occurredAt": "2019-08-24T14:15:22Z"
}Sent before any provider is contacted, when the pre-authorisation hook
is switched on for the brand. Answer 200 with a decision. A timeout, a
non-2xx, an unreadable body or an unknown decision are all read as "no
answer", and the brand's onUnreachable setting decides what that means
(default: the operation is declined).
Verified exactly like a callback: X-PS-Signature over
"<t>.<raw body>" under the same callback secret, which is why the hook
cannot be enabled without a configured callback endpoint. Refuse a call
you cannot verify with 401 and no body.
Asked once per operation, and never retried. A retry of the same
Idempotency-Key returns the decided transaction rather than asking
again. Refunds and reversals are not asked about.
See the integration guide, § 7 "Pre-authorisation hook".
| X-PS-Signature required | string Examples: t=1757498400,v1=2629194c7ff61de00be9dfcf45b480a44b758ff2ae3efa89466382f943b5f20f
|
| X-PS-Event required | string The event name, identical to Value: "transaction.authorize" |
| event required | string Value: "transaction.authorize" |
| transactionId required | string The platform's public id for the operation; the same value every later message uses. |
| type required | string Enum: "PAYIN" "PAYOUT" |
| method required | string |
| provider required | string The provider the operation has been routed to. |
| amount required | number (Money) decimal places <= 2 > 0 A decimal money amount. Sent as a JSON number (a JSON string parses
too); returned as a JSON number that preserves the stored scale
( A client that does arithmetic on these should read them as decimal
strings from the raw body rather than through a |
| currency required | string |
| userRef required | string The client's own reference for the end user, exactly as it was sent. |
| clientTxRef | string or null |
| country | string or null |
| destinationMasked | string or null Head+tail mask of a payout destination. Null on a payin and on a payout whose destination the provider collects itself. Never the destination in full. |
| requestedAt required | string <date-time> |
{- "event": "transaction.authorize",
- "transactionId": "tx_9f2c1aQ4rXyZtN8cVbGhJkLd",
- "type": "PAYIN",
- "method": "CARD",
- "provider": "EXAMPLEPSP",
- "amount": 25,
- "currency": "EUR",
- "userRef": "user-10427",
- "clientTxRef": "order-8891",
- "country": "DE",
- "destinationMasked": null,
- "requestedAt": "2026-09-10T10:00:00Z"
}{- "decision": "APPROVE"
}