PayIns
Create incoming payments and return the customer action required by the selected payment method.
Create PayIns and PayOuts across enabled payment methods, and track every operation through a predictable lifecycle.
201Create incoming payments and return the customer action required by the selected payment method.
Send funds with operational balance reservation built into the processing flow.
Receive signed, retryable webhook events whenever an operation changes state.
Use the credentials from your Integration page to create a signed PayIn request from your backend.
You receive an API client key and secret plus a separate merchant signing secret. Never expose either secret in browser or mobile application code.
Copy your merchant code and API key from the merchant cabinet. Retrieve both 32-byte signing secrets issued during onboarding from your secret manager.
Serialize JSON once, hash those exact bytes, and use the nested HMAC construction below. Do not reformat the body after signing.
Use a new idempotency key for each logical operation. Reuse that key only when retrying the same request body.
import crypto from "node:crypto";
const apiBase = "https://api.spqr-payments.com";
const apiKey = process.env.SPQR_API_KEY;
const apiClientSecret = Buffer.from(process.env.SPQR_API_CLIENT_SECRET, "hex");
const merchantSecret = Buffer.from(process.env.SPQR_MERCHANT_SECRET, "hex");
const body = JSON.stringify({
merchant: "acme_my",
amount: 12500,
currency: "MYR",
external_id: "order-8402",
country: "MY",
payment_method: "duitnowqr",
return_url: "https://shop.example.com/orders/order-8402",
callback_url: "https://api.shop.example.com/webhooks/spqr",
payer: { customer_uid: "customer-184" }
});
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const idempotencyKey = crypto.randomUUID();
const canonical = `POST\n/payments\n${bodyHash}\n${timestamp}\n${idempotencyKey}\n`;
const inner = crypto.createHmac("sha256", merchantSecret).update(canonical).digest();
const signature = crypto.createHmac("sha256", apiClientSecret).update(inner).digest("hex");
const response = await fetch(`${apiBase}/payments`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": apiKey,
"X-Timestamp": timestamp,
"X-Signature": signature,
"Idempotency-Key": idempotencyKey
},
body
});
console.log(response.status, await response.json());
"merchant": "acme_vn",
"amount": 5000000,
"currency": "VND",
"external_id": "order-8501",
"country": "VN",
"payment_method": "momopay",
"return_url": "https://shop.example.com/orders/order-8501",
"callback_url": "https://api.shop.example.com/webhooks/spqr",
"payer": {
"customer_uid": "customer-185"
}
"merchant": "acme_vn",
"amount": 5000000,
"currency": "VND",
"external_id": "order-8502",
"country": "VN",
"payment_method": "zalopay",
"return_url": "https://shop.example.com/orders/order-8502",
"callback_url": "https://api.shop.example.com/webhooks/spqr",
"payer": {
"customer_uid": "customer-185"
}
"merchant": "acme_vn",
"amount": 5000000,
"currency": "VND",
"external_id": "order-8503",
"country": "VN",
"payment_method": "vietqr",
"return_url": "https://shop.example.com/orders/order-8503",
"callback_url": "https://api.shop.example.com/webhooks/spqr",
"payer": {
"customer_uid": "customer-185"
}
{
"payment_id": "0198f0d4-15cb-7c98-ae52-4dd112dc14cc",
"status": "PENDING",
"created_at": "2026-08-17T11:42:16.921Z",
"client_action": {
"type": "redirect",
"url": "https://spqr-payments.com/pay/0198f0d4-15cb-7c98-ae52-4dd112dc14cc#checkout_token=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": {
"payment_page_id": "0198f0d4-15cb-7c98-ae52-4dd112dc14cc",
"checkout_token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}
}In the default redirect mode, client_action.url is an SPQR hosted checkout. Pass the complete client_action.url to the browser, including #checkout_token=…; do not reconstruct or strip its fragment. Keep client_action.data opaque and never render it as payment instructions.
Every merchant API request is authenticated with a timestamped, nested HMAC-SHA256 signature.
X-Api-KeyIdentifies the API client and scopes it to a franchise. Safe to include as a request header, but not a substitute for either secret.
Signs the canonical request first. It binds the request to the merchant code in the body or query string.
Signs the raw 32-byte inner digest. It binds the request to the calling integration.
Merchant API uses one signing protocol; no version header is required. The canonical UTF-8 string has five values, each followed by LF, including the last value. Trim leading and trailing whitespace from Idempotency-Key before signing. For requests without a body, including normal GET requests, hash an empty body. Use an empty fifth line when the key is absent. Use the exact escaped path and raw query string without decoding or reordering. Old signatures are rejected.
<UPPERCASE_METHOD>
<PATH_WITH_OPTIONAL_RAW_QUERY>
<LOWERCASE_HEX_SHA256_OF_BODY>
<X_TIMESTAMP>
<TRIMMED_IDEMPOTENCY_KEY_OR_EMPTY>
signature = hex(HMAC-SHA256(api_client_secret, HMAC-SHA256(merchant_secret, canonical)))The default accepted clock skew is ±5 minutes. Synchronize servers with NTP and generate a fresh timestamp for every retry.
Sign the same raw query order you send. For example, /payments/{id}?merchant=acme_my.
Use a timing-safe comparison when verifying SPQR webhook signatures on your server.
Create a customer payment, present the returned client action when required, and use webhooks or retrieval to observe the final state.
merchantrequiredstringYour SPQR merchant code.amountrequiredint64Positive amount in minor units. MYR 125.00 is 12500; in SPQR, VND 50,000 is 5000000. These are example amounts, not limits.currencyrequiredstringThree-letter ISO 4217 code, such as MYR.external_idrequiredstringYour order ID, from 1 to 128 characters after trimming. It identifies the order in your system, not the SPQR operation ID.countryrequiredstringTwo-letter ISO 3166-1 alpha-2 code, such as MY.payment_methodrequiredstringAn enabled SPQR method code for the requested country and currency.qr_formatstringH2H only: url (default) or base64.return_urlURICustomer return destination. Defaults to the URL in merchant settings.callback_urlURIHTTPS webhook endpoint. Defaults to the URL in merchant settings.payerobjectOptional payer metadata required by the selected payment method.In redirect mode, when client_action.url is present, redirect the customer to that SPQR hosted page. The values in client_action.data are opaque checkout credentials: store or forward them only when needed, and never display them or turn them into a QR code.
The full path and raw query are part of the HMAC signature. A pending operation may be refreshed before the response is returned.
Display a payment QR in your own interface using the existing PayIn API.
H2H is available by default for supported payment methods and enabled merchant country/currency combinations, unless access has been disabled. A QR method name alone does not guarantee H2H support. Unsupported or unavailable combinations are rejected. Omitting checkout_mode keeps the standard redirect flow; there is no automatic fallback between modes.
Send a signed POST /payments with a new Idempotency-Key. Amounts are integer minor units: 12500 MYR is MYR 125.00. Payer fields depend on the method.
{
"merchant": "acme_my",
"amount": 12500,
"currency": "MYR",
"external_id": "order-8402",
"country": "MY",
"payment_method": "duitnowqr",
"checkout_mode": "h2h",
"qr_format": "url",
"payer": { "customer_uid": "customer-184" }
}
{
"merchant": "acme_vn",
"amount": 5000000,
"currency": "VND",
"external_id": "order-8504",
"country": "VN",
"payment_method": "vietqr",
"return_url": "https://shop.example.com/orders/order-8504",
"callback_url": "https://api.shop.example.com/webhooks/spqr",
"payer": {
"customer_uid": "customer-185"
},
"checkout_mode": "h2h",
"qr_format": "url"
}qr_format accepts url (default) or base64, and is valid only with checkout_mode=h2h. New payments return HTTP 201; identical retries return HTTP 200 and the original operation. Changing mode or format with the same idempotency key returns HTTP 409.
{
"payment_id": "11111111-1111-4111-8111-111111111111",
"status": "PENDING",
"created_at": "2026-10-07T10:00:00Z",
"checkout_mode": "h2h",
"instruction_status": "ready",
"client_action": {
"type": "qr",
"url": "https://api.spqr-payments.com/payment-assets/qr/<token>.png",
"data": { "mime_type": "image/png", "expires_at": "2026-10-07T10:15:00Z" }
}
}Load client_action.url as an image. It grants access to one PNG and does not require HMAC, cookies, Origin or Referer. Treat the URL as sensitive and keep it out of logs. The image endpoint returns image/png with Cache-Control: no-store and X-Content-Type-Options: nosniff. An unknown token returns HTTP 404; an expired instruction or terminal payment returns HTTP 410.
{
"type": "qr",
"data": {
"mime_type": "image/png",
"expires_at": "2026-10-07T10:15:00Z",
"image_base64": "<Base64-encoded PNG>"
}
}For Base64, use data:image/png;base64, followed by image_base64 as the image source. There is no url field. Both formats use the same saved PNG. Use the returned expires_at; deadlines vary by method and are not extended by retries or reads.
{
"payment_id": "11111111-1111-4111-8111-111111111111",
"status": "PENDING",
"created_at": "2026-10-07T10:00:00Z",
"checkout_mode": "h2h",
"instruction_status": "pending"
}The payment already exists. Poll the signed GET /payments/{id}?merchant={merchant_code} with the same merchant code used at creation and backoff to retrieve its instruction. Do not issue a second create while waiting. Stop polling when the instruction is ready, leaves pending, or the payment becomes terminal.
| instruction_status | Meaning and action |
|---|---|
pending | Preparation is in progress. Retrieve the existing payment again. |
ready | Display the PNG in client_action until expires_at or payment completion. |
unavailable | Preparation could not produce a usable image. Stop QR polling, track monetary status and contact support; do not assume another create is safe. |
expired | The instruction deadline has passed. Remove the image and track payment status. |
closed | The payment is terminal. Remove the image and use its final monetary status. |
Only ready contains an image action. QR readiness is independent of payment status: ready does not mean paid, and a QR preparation error does not change monetary status. Continue using the standard signed payment webhooks.
Create a disbursement with recipient details. SPQR reserves operational balance before processing begins.
The common fields match a PayIn, while recipient replaces payer. For bank transfers, send all values as strings:
bank_code — recipient bank identifier, such as VCB for Vietcombank in Vietnamaccount_name — account holder nameaccount_number — account number with leading zeroes preservedRead GET /capabilities?merchant=acme_my&operation_type=PAY_OUT for the required fields of your method. Customer identifiers also belong inside recipient: send "customer_uid": "customer-184" when recipient.customer_uid is listed, or "player_username": "customer184" when recipient.player_username is listed. Both are strings; neither is required for every payout. Do not send a top-level party object.
{
"merchant": "acme_my",
"amount": 25000,
"currency": "MYR",
"external_id": "withdrawal-184",
"country": "MY",
"payment_method": "bank_transfer",
"callback_url": "https://api.shop.example.com/webhooks/spqr",
"recipient": {
"bank_code": "MBBEMYKL",
"account_name": "Alex Tan",
"account_number": "5144229981"
}
}
{
"merchant": "acme_vn",
"amount": 5000000,
"currency": "VND",
"external_id": "withdrawal-185",
"country": "VN",
"payment_method": "bank_transfer",
"callback_url": "https://api.shop.example.com/webhooks/spqr",
"recipient": {
"bank_code": "VCB",
"account_name": "Nguyen Van An",
"account_number": "001234567890"
}
}
The create call can return insufficient_balance with HTTP 422 when funds cannot be reserved. Failed or cancelled processing releases its reservation.
/payouts/{id}?merchant={merchant_code}Return the latest state and reservation ID when available.
ReferenceDiscover the countries, currencies and payment methods currently configured for your merchant before you build a PayIn or PayOut request.
/capabilities?merchant={merchant_code}&operation_type=PAY_INReturn the method catalog configured for your account.
ReferenceCall capabilities from your backend and use the returned country, currency, method and amount boundaries in your checkout. Do not hardcode the catalog: your enabled configuration can change independently of an API release.
operation_enabled reports whether this direction is enabled for your merchant.configured reports whether a published configuration exists for the direction.max_amount: 0 means no configured upper boundary.required_fields lists extra request fields required by the method.features is forward-compatible; ignore keys your integration does not recognize.GET requests use the same nested HMAC construction as create and retrieve calls. Hash an empty body and sign the path with the exact query order you send.
const path = "/capabilities?merchant=acme_my&operation_type=PAY_IN";
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash("sha256").update("").digest("hex");
const canonical = `GET\n${path}\n${bodyHash}\n${timestamp}\n\n`;
const inner = crypto.createHmac("sha256", merchantSecret).update(canonical).digest();
const signature = crypto.createHmac("sha256", apiClientSecret).update(inner).digest("hex");
const response = await fetch(`${apiBase}${path}`, {
headers: {
"X-Api-Key": apiKey,
"X-Timestamp": timestamp,
"X-Signature": signature
}
});
A disabled or not-yet-configured direction returns HTTP 200 with an empty array and explanatory flags.
The response describes configured eligibility and amount boundaries. It does not guarantee instantaneous processing availability.
Respect the response ETag and cache privately for at most 30 seconds. Revalidate with If-None-Match.
{
"merchant": "acme_my",
"operation_type": "PAY_OUT",
"operation_enabled": false,
"configured": false,
"capabilities": []
}{
"merchant": "acme_my",
"operation_type": "PAY_IN",
"operation_enabled": true,
"configured": false,
"capabilities": []
}During the current rollout, a country/currency pair missing from this catalog is not rejected by a separate geography check. SPQR evaluates each submitted transaction independently; use its create response as the final answer.
Method codes are stable merchant-facing values. Use GET /capabilities as the authoritative catalog enabled for your account.
| Flow | Method code | Customer experience | Country · Currency |
|---|---|---|---|
duitnowqr | DuitNow QR | MY · MYR | |
fpx | FPX online banking | MY · MYR | |
bank_transfer | Hosted bank transfer | MY · MYR | |
tng | Touch 'n Go direct | MY · MYR | |
tngqr | Touch 'n Go QR | MY · MYR | |
grabpay | GrabPay direct | MY · MYR | |
grabpayqr | GrabPay QR | MY · MYR | |
shopee | ShopeePay direct | MY · MYR | |
gxbankqr | GXBank QR | MY · MYR | |
boostqr | Boost QR | MY · MYR | |
maybankqr | Maybank QRPay | MY · MYR | |
rhbbankqr | RHB Bank QRPay | MY · MYR | |
ebank | Online banking | MY · MYR | |
| PayOut | bank_transfer | Bank account transfer | MY · MYR |
| PayOut | tng_payout | Touch 'n Go payout | MY · MYR |
momopay | MoMo Pay QR | VN · VND | |
zalopay | ZaloPay QR | VN · VND | |
vietqr | VietQR | VN · VND | |
bank_transfer | Hosted bank transfer QR | VN · VND | |
| PayOut | bank_transfer | Bank account transfer | VN · VND |
Additional corridors can be enabled after commercial and operational approval. They are not listed as generally available until then.
PayIns and PayOuts share one merchant-facing state machine. Terminal states do not transition again.
NEW can also move directly to CANCELLED. The status_times object records the first UTC timestamp at which each state family was reached.
Status answers what happened; resolution explains why. Use both in operational dashboards and customer-support tooling.
okSuccessful terminal outcome
method_unavailableNo eligible payment method
processing_error_retryableRetryable processing fault
processing_error_terminalPermanent processing decline
limit_violationAmount or turnover limit
attempt_limit_reachedAll attempts exhausted
insufficient_balanceOperational funds unavailable
cancelledOperation was cancelled
A retryable processing error can set moderation_required: true. Keep the operation in an operator-review flow instead of treating it as a safe final decline.
SPQR posts a signed event to the request callback URL, or to the default callback URL in merchant settings, after each status transition.
Each operation keeps the X-Api-Key of the integration that created it. Verify its webhooks with that integration’s current client secret and your merchant secret. Retries keep the same key ID and event_id. Disabled, missing or foreign keys stop delivery; SPQR does not select another integration.
Rotating the secret under the same key ID affects the next delivery attempt. When replacing a key ID, keep the old client enabled until its operations and queued deliveries finish. Older operations and internal operations without an authenticated API client need an explicitly established binding before delivery.
Callback and return URLs are saved at first creation. Later merchant settings apply to new operations. Replaying an unchanged request with its original idempotency key returns the original operation; changing an explicitly supplied URL returns 409 idempotency_mismatch. An operation created without a callback keeps that absence.
{
"event_id": "0198f0d9-d219-77e8-9419-c2dbfc54889d",
"event_type": "payment.status_changed",
"merchant": "acme_my",
"operation_type": "payment",
"operation_id": "0198f0d4-15cb-7c98-ae52-4dd112dc14cc",
"external_id": "order-8402",
"status": "SUCCEEDED",
"resolution": "ok",
"moderation_required": false,
"status_times": {
"new_at": "2026-08-17T11:42:16.921Z",
"pending_at": "2026-08-17T11:42:17.244Z",
"succeeded_at": "2026-08-17T11:42:31.808Z"
},
"amount": 12500,
"paid_amount": 12500,
"currency": "MYR"
}
SUCCEEDED covers full and partial payments. In the full example above, paid_amount equals amount. A partial payment can instead contain:
{ "status": "SUCCEEDED", "amount": 12500, "paid_amount": 7500 }Credit the customer using paid_amount; check that it equals amount before fulfilling a full order. SPQR records the actually paid gross amount; net and fees are separate. A success confirmation without a separately reported paid amount is currently treated as full payment. Always use the returned paid_amount.
Persist event_id under a unique constraint with the event before returning 2xx. Repeated delivery must not repeat the business effect. Delivery order is not guaranteed: reconcile through GET instead of applying an older event over newer state. Default delivery is limited to eight attempts with exponential backoff bounded between 200 ms and five minutes; deployment settings may differ. Each attempt has a fresh timestamp and signature. There is no guaranteed elapsed-time delivery deadline; after exhaustion automatic retries stop. Recover missed state through GET by operation ID or external_id.
Webhook signatures use four LF-terminated lines: the uppercase method, exact escaped request path with its raw query, SHA-256 hex digest of the raw body, and timestamp. The last LF is required. Use the received originalUrl without decoding the path, rebuilding the query, sorting parameters, or changing repeated parameters. Read the raw body before JSON parsing and compare the signature in constant time. This is the current webhook contract; decoded-path signatures are not supported.
function verifySPQRWebhook({ method, originalUrl, rawBody, headers, nowSeconds = Math.floor(Date.now() / 1000) }) {
if (!headers || typeof method !== "string" || method.toUpperCase() !== "POST" ||
typeof originalUrl !== "string" || !originalUrl.startsWith("/") ||
!Buffer.isBuffer(rawBody) || !Number.isSafeInteger(nowSeconds)) return false;
if (typeof apiKey !== "string" || !apiKey || headers["x-api-key"] !== apiKey) return false;
const timestamp = headers["x-timestamp"];
const received = headers["x-signature"];
if (typeof timestamp !== "string" || !/^[0-9]+$/.test(timestamp) ||
typeof received !== "string" || !/^[0-9a-f]{64}$/.test(received)) return false;
const seconds = Number(timestamp);
if (!Number.isSafeInteger(seconds) || Math.abs(nowSeconds - seconds) > 300) return false;
const bodyHash = crypto.createHash("sha256").update(rawBody).digest("hex");
const canonical = `${method.toUpperCase()}\n${originalUrl}\n${bodyHash}\n${timestamp}\n`;
const inner = crypto.createHmac("sha256", merchantSecret).update(canonical).digest();
const expected = crypto.createHmac("sha256", apiClientSecret).update(inner).digest();
return crypto.timingSafeEqual(Buffer.from(received, "hex"), expected);
}
After signature verification, store each event in PostgreSQL. A duplicate insert returns no row; acknowledge it without scheduling the business work again. Keep the inbox and a durable work item in the same transaction. The worker also needs an idempotent business effect. In-memory sets lose deduplication after restart.
CREATE TABLE spqr_webhook_inbox (
event_id uuid PRIMARY KEY,
payload jsonb NOT NULL,
received_at timestamptz NOT NULL DEFAULT now(),
processed_at timestamptz
);
-- Bind event_id and the verified JSON body as parameters.
INSERT INTO spqr_webhook_inbox (event_id, payload)
VALUES ($1, $2)
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;PayOut events use payout.status_changed, operation_type: "payout", and also include the original recipient object.
The merchant assigns external_id (1–128 characters after trimming). It is unique for that merchant separately for PayIn and PayOut, regardless of currency or method, including FAILED and CANCELLED operations. Matching is case-sensitive; leading and trailing whitespace is trimmed. A new key with an existing external ID returns HTTP 409 external_id_conflict, even when the body is identical.
Use signed GET /payments?merchant=acme_my&external_id=order-8402 or GET /payouts?merchant=acme_my&external_id=withdrawal-184 if you lost the SPQR ID. URL-encode query values before signing the exact path and query. The response is the same as GET by operation ID; unknown IDs return 404. GET also restores a saved SPQR redirect action for an unfinished PayIn without creating another operation.
An error after acceptance includes error.operation with operation_type, operation_id, external_id and the current status (null when state cannot be read after the failure). The original key is already claimed. An uncertain result must be recovered with that key; after a terminal rejection, a new attempt requires both a new external ID and a new key. An identical replay can return HTTP 200 with the saved CANCELLED operation after the initial rejection.
{
"error": {
"code": "merchant_balance_insufficient",
"message": "merchant balance is insufficient",
"operation": {
"operation_type": "payout",
"operation_id": "0198f0d4-15cb-7c98-ae52-4dd112dc14cc",
"external_id": "withdrawal-184",
"status": "CANCELLED"
}
}
}Signed merchant API errors use a consistent JSON envelope. PNG assets return an image only on success; their 404, 410 and 503 responses have no image body. Log request_id and share it with the integration team when troubleshooting.
The key was already used for a different logical request.
{
"error": {
"code": "idempotency_mismatch",
"message": "idempotency key reused with different request body",
"request_id": "7c6bfb2944246c12",
"details": []
}
}validation_errorCorrect the request. Use details for field-level feedback.unauthorizedCheck the API key, merchant scope, timestamp, and credential status.invalid_signatureCompare exact body bytes, path/query, trailing newlines, and secret decoding.ip_not_allowedAdd your backend IP to the merchant API IP whitelist. An empty list blocks access. QR images remain accessible to customers.merchant_disabledContact SPQR operations; the merchant or requested flow is disabled.not_foundConfirm the merchant code and operation ID are in the authenticated scope.idempotency_mismatchDo not retry with this key. Investigate the conflicting request.conflictRetrieve the operation and reconcile its current state.external_id_conflictRead the existing operation from error.operation. Reuse the original key to recover it.merchant_balance_insufficientMerchant funds are insufficient. Check the accepted operation before starting a new attempt.method_unavailableNo compatible method meets the request conditions. Check the method, country, currency and amount.limit_exceededThe amount is outside permitted limits. Change the amount or check applicable merchant limits.rate_limitedWait at least Retry-After seconds; retain the original body and key, with a fresh timestamp and signature.processing_capacity_unavailableProcessing capacity is unavailable; adding merchant funds may not resolve it. Check error.operation.processing_configuration_unavailableProcessing configuration is unavailable. Contact SPQR support and check error.operation.internal_errorRecover using the same key and identical body with a fresh signature; inspect error.operation when present.The public contract contains seven signed merchant endpoints and one QR image endpoint. Status mutation endpoints are private to SPQR services.
X-Api-Key · X-Timestamp · X-Signature · Idempotency-Key201 on create; 200 on an identical idempotent replay.merchant, amount, currency, external_id, country, and payment_method are required.payment_id, status, created_at, with client_action for redirect checkout or a ready H2H QR. H2H also returns checkout_mode and instruction_status.merchant and case-sensitive external_id, escaped in the URL.X-Api-Key · X-Timestamp · X-Signature200 with the same operation as reading by ID; 404 when absent from your account. This endpoint returns one object.merchant — the owning merchant code.X-Api-Key · X-Timestamp · X-Signature200 with the current status, outcome metadata, amount, and lifecycle timestamps.200 with image/png and Cache-Control: no-store.404 while preparing or for an unknown token; 410 after expiry or payment completion; 503 if image storage is unavailable. These responses contain no image. 429 returns JSON with Retry-After.X-Api-Key · X-Timestamp · X-Signature · Idempotency-Key201 on create; 200 on an identical idempotent replay.recipient object.payout_id, status, and created_at.merchant and case-sensitive external_id, escaped in the URL.X-Api-Key · X-Timestamp · X-Signature200 with the same operation as reading by ID; 404 when absent from your account. This endpoint returns one object.merchant — the owning merchant code.X-Api-Key · X-Timestamp · X-Signature200 with the current status, recipient, lifecycle timestamps, and reservation ID when present.merchant and operation_type=PAY_IN|PAY_OUT; sign the exact query order sent.X-Api-Key · X-Timestamp · X-Signature200 with configured methods and amount boundaries, or an empty array with explanatory flags.ETag and If-None-Match.Download the merchant-only YAML contract for code generation, API clients, or contract tests.