Integration rules

The single list of rules for v2. Other pages refer to them by number: R1…R20. Each rule is backed by the OpenAPI document.

Access

  • R1. A key is a shop. Authorization: Bearer <shop_key>. A shop is payin or payout, with one currency. The other shop type's endpoints return 403 method_not_enabled.
  • R2. Sandbox and production keys never mix. psbox_… works only on the sandbox host, plive_… only on production.
  • R3. The endpoint is the method. One create endpoint per method; the body has no method field. A method not enabled for the shop returns 403 method_not_enabled.

Creating

  • R4. merchant_reference is the idempotency key of a create. Generate it once, save it, then call. Repeating the call with the same reference and body returns the existing transaction (Idempotent-Replayed: true); a different body returns 409 merchant_reference_conflict. It never expires and is unique per merchant across payins, refunds, payouts and pay sessions.
  • R5. capture and void are safe to repeat. A repeat while the action is in progress, or after it, returns the payin in its current status.
  • R6. A timeout tells you nothing. Resend the identical request; never create a replacement transaction.
  • R7. Amounts are strings in major units, at most two decimals: "100.50".

Outcome

  • R8. 2xx is not an outcome. Read data.status. Final: completed and failed.
  • R9. A decline is a status, not an error. The request succeeds; the transaction ends failed with the reason in status_details.code. A voided hold and a payer who rejected are failed too.
  • R10. requires_action names the step in next_action: redirect or await_confirmation. Open a redirect as a full page, never in an iframe, before next_action.expires_at when it is set — see Payer actions.
  • R11. authorized is a card hold, not a payment. Call capture or void. A rejected capture or void returns the payin to authorized.
  • R12. A refund is its own transaction. When it completes, the payin moves from completed to refunded and refunded_amount grows.
  • R13. Unknown enum values are expected. An unknown status is not final; an unknown status_details.code is a decline; an unknown next_action.type — stop and ask; an unknown event type — re-read the transaction.

Errors

  • R14. Branch on error.code, never on message. Retry only 429 and 500, with the identical request; on 429 wait Retry-After.

Webhooks

  • R15. Verify first. Pick the shop's signing secret by the URL or shop_id in the body. Webhook-Signature: t=…,v1=… — v1 is lowercase hex HMAC-SHA256 of <t>.<raw body> over the bytes received; reject a t more than 5 minutes off; accept either of two v1.
  • R16. Acknowledge fast. Persist the event, answer 2xx within 30 seconds, process after. Otherwise up to 15 redeliveries over about two days — 16 attempts in total.
  • R17. Deduplicate on the event id, and mark an event processed only after it is processed. Do not rely on delivery order.
  • R18. A signed webhook is the result. After the signature check (R15), act on data.object — it is the full transaction. Read the transaction only when the event type is unknown. A late non-final event never overrides a final status.

Reconciliation

  • R19. Sync by change time. listTransactions with order=updated_asc, updated_from = the end of the last window minus 5 minutes, updated_to = now; deduplicate on id. A final status never changes, except a payin completed → refunded.
  • R20. Webhooks are the channel; reading is the fallback. Learn about status changes from webhooks. If no webhook has come for a minute, you can start polling retrieveTransaction until the status is final. Polling instead of webhooks loads the API and gets 429 rate_limited. Missed webhooks are also caught by the sync (R19).