{
  "info": {
    "name": "Aurumsys API — client integration",
    "description": "Generated from the OpenAPI description at https://docs.aurumsys.com/openapi.yaml.\n\nSet `apiKey` before sending anything under `/v1`; set `callbackSecret`\nand `callbackUrl` before sending anything in **Callbacks**. Both ship\nempty — nothing secret is stored in this file.\n\nGuide: https://docs.aurumsys.com/guide/index.md",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Collection-level pre-request script.",
          "//",
          "// Embedded verbatim into the generated Postman collection; it is the one part",
          "// of the collection that is written by hand rather than derived from the",
          "// description, so it lives in its own file where it can be read and reviewed.",
          "//",
          "// It does three things:",
          "//",
          "//   1. Fails fast when the collection is not configured, rather than letting a",
          "//      missing key surface as a confusing 401.",
          "//   2. Mints a fresh Idempotency-Key for every POST — one per business intent,",
          "//      which is exactly what a manual send is. Set `idempotencyKeyPin` to",
          "//      replay a previous intent deliberately.",
          "//   3. Signs a request that declares an `X-PS-Signature` header, using the",
          "//      same recipe the platform uses on the callbacks it sends you. Only the",
          "//      callback-simulation requests declare that header; nothing under /v1",
          "//      does, because requests are authenticated by API key alone.",
          "//",
          "// Before anything else it reproduces the worked example from the guide. If the",
          "// HMAC helper in this sandbox ever stops producing that value, every send",
          "// fails loudly here instead of producing a signature nobody can verify.",
          "",
          "var VECTOR = {",
          "  \"secret\": \"whsec_EXAMPLEsecretDOnotUSEthisVALUEanywhereEVER1\",",
          "  \"t\": \"1788528751\",",
          "  \"body\": \"{\\\"event\\\":\\\"transaction.status_changed\\\",\\\"transactionId\\\":\\\"tx_4bQ9xR2mKpLvN7sTfWgHdYcZ\\\",\\\"parentTransactionId\\\":null,\\\"type\\\":\\\"PAYIN\\\",\\\"reversalKind\\\":null,\\\"status\\\":\\\"COMPLETED\\\",\\\"method\\\":\\\"CARD\\\",\\\"amount\\\":25.00,\\\"currency\\\":\\\"EUR\\\",\\\"settledAmount\\\":25.00,\\\"settledCurrency\\\":\\\"EUR\\\",\\\"provider\\\":\\\"EXAMPLEPSP\\\",\\\"userRef\\\":\\\"user-10427\\\",\\\"clientTxRef\\\":\\\"order-8891\\\",\\\"errorCode\\\":null,\\\"errorMessage\\\":null,\\\"occurredAt\\\":\\\"2026-09-04T09:12:31.482913Z\\\",\\\"fee\\\":{\\\"amount\\\":0.75,\\\"currency\\\":\\\"EUR\\\"}}\",",
          "  \"expected\": \"7975acdb19bbe9db3afca8a40de7d40aa3bd5fb4ebaf99e1d15a9b1c5688baa7\"",
          "};",
          "",
          "function hmacHex (secret, message) {",
          "  return CryptoJS.HmacSHA256(message, secret).toString(CryptoJS.enc.Hex);",
          "}",
          "",
          "(function selfTest () {",
          "  var got = hmacHex(VECTOR.secret, VECTOR.t + '.' + VECTOR.body);",
          "  if (got !== VECTOR.expected) {",
          "    throw new Error(",
          "      'The HMAC helper does not reproduce the documented worked example. ' +",
          "      'Expected ' + VECTOR.expected + ', got ' + got + '. Do not trust any ' +",
          "      'signature this collection produces until that is resolved.'",
          "    );",
          "  }",
          "})();",
          "",
          "(function checkConfiguration () {",
          "  var baseUrl = pm.collectionVariables.get('baseUrl');",
          "  if (!baseUrl) {",
          "    throw new Error('Set the collection variable \"baseUrl\" before sending.');",
          "  }",
          "  var signing = pm.request.headers.has('X-PS-Signature');",
          "  if (signing) return; // a callback simulation carries no API key",
          "",
          "  var apiKey = pm.collectionVariables.get('apiKey');",
          "  if (!apiKey) {",
          "    throw new Error(",
          "      'Set the collection variable \"apiKey\" to the brand API key you were ' +",
          "      'issued. It is shown once and cannot be retrieved afterwards.'",
          "    );",
          "  }",
          "  if (apiKey.indexOf('psk_') !== 0) {",
          "    throw new Error('The API key must carry the \"psk_\" prefix.');",
          "  }",
          "})();",
          "",
          "(function idempotency () {",
          "  if (pm.request.method !== 'POST') return;",
          "  if (pm.request.headers.has('X-PS-Signature')) return;",
          "",
          "  var pinned = pm.collectionVariables.get('idempotencyKeyPin');",
          "  if (pinned) {",
          "    pm.collectionVariables.set('idempotencyKey', pinned);",
          "    return;",
          "  }",
          "  // One key per business intent. A manual send is a new intent, so a new key",
          "  // is right here — in your own integration the key is minted BEFORE the",
          "  // call, stored durably, and reused on every retry of that same intent.",
          "  var key = (pm.info && pm.info.requestName ? pm.info.requestName : 'request')",
          "    .toLowerCase()",
          "    .replace(/[^a-z0-9]+/g, '-')",
          "    .replace(/^-+|-+$/g, '')",
          "    .slice(0, 31);",
          "  pm.collectionVariables.set(",
          "    'idempotencyKey',",
          "    (key || 'request') + '-' + Date.now().toString(36) + '-' +",
          "      Math.floor(Math.random() * 0x100000000).toString(36)",
          "  );",
          "})();",
          "",
          "(function signCallbackBody () {",
          "  if (!pm.request.headers.has('X-PS-Signature')) return;",
          "",
          "  var secret = pm.collectionVariables.get('callbackSecret');",
          "  if (!secret) {",
          "    throw new Error(",
          "      'Set the collection variable \"callbackSecret\" to your brand callback ' +",
          "      'secret (the whsec_ value shown once when the endpoint or its secret ' +",
          "      'was created).'",
          "    );",
          "  }",
          "",
          "  // Sign the bytes that will actually be sent: the raw body with every",
          "  // {{variable}} already resolved. Never re-serialize a body you intend to",
          "  // sign or verify — a reformatted body is a different message.",
          "  var raw = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
          "  var body = pm.variables.replaceIn(raw);",
          "  var t = Math.floor(Date.now() / 1000);",
          "",
          "  pm.collectionVariables.set('callbackBodySent', body);",
          "  pm.collectionVariables.set('callbackTimestamp', String(t));",
          "  pm.request.headers.upsert({",
          "    key: 'X-PS-Signature',",
          "    value: 't=' + t + ',v1=' + hmacHex(secret, t + '.' + body)",
          "  });",
          "})();",
          ""
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.aurumsys.com",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "secret"
    },
    {
      "key": "callbackSecret",
      "value": "",
      "type": "secret"
    },
    {
      "key": "callbackUrl",
      "value": "",
      "type": "string"
    },
    {
      "key": "authorizeUrl",
      "value": "",
      "type": "string"
    },
    {
      "key": "transactionId",
      "value": "",
      "type": "string"
    },
    {
      "key": "idempotencyKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "idempotencyKeyPin",
      "value": "",
      "type": "string"
    },
    {
      "key": "callbackTimestamp",
      "value": "",
      "type": "string"
    },
    {
      "key": "callbackBodySent",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Operations",
      "description": "Moving money — payins, payouts and refunds.",
      "item": [
        {
          "name": "Start a payin",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "description": "**Required on every POST.** Generate it once per business intent —\nbefore the call, and durably — and reuse it on every retry of that\nintent. A key minted after a successful response can never help you.\n\nSame key + same payload = replay: `200` with the stored transaction.\nSame key + different payload = `409 IDEMPOTENCY_CONFLICT`.\n\nUnique per brand; keys are compared exactly after trimming surrounding\nwhitespace."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payins",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payins"
              ]
            },
            "description": "Creates a payin and calls the routed PSP. What comes back depends on\nthe provider and method:\n\n* a hosted-page provider answers `PENDING` with a `redirectUrl` — send\n  the end user there. For a page that opens only by form POST the URL\n  is on the platform's own host (`/handoff/…`) and forwards the\n  browser; follow it unchanged like any other;\n* a pay-to-address provider answers `PENDING` with\n  `paymentInstructions` and no `redirectUrl`;\n* a synchronous refusal answers `FAILED` with `error`;\n* an ambiguous provider failure (timeout, crash mid-call) answers\n  `IN_DOUBT` — the platform reconciles it and sends a callback. It is\n  never terminal-failed, because the PSP may be executing it.\n\nA few rails also need the PAYER's own account on them before the\nrequest can be raised at all — a UPI VPA. Those options are marked\n`requiresPayerAccount` on `GET /v1/payment-methods`, and the value goes\nin `payerAccount`.\n\nValidation order matters, because everything below happens **before a\ntransaction row exists**, so a refusal costs nothing: bean validation →\nidempotency → routing → required customer fields → required request\nfields → payer account. Only then is a row persisted and the PSP\ncalled.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 25.00,\n  \"currency\": \"EUR\",\n  \"method\": \"CARD\",\n  \"userRef\": \"user-10427\",\n  \"clientTxRef\": \"order-8891\",\n  \"country\": \"DE\",\n  \"returnUrl\": \"https://merchant.example.com/checkout/return\",\n  \"customer\": {\n    \"email\": \"alex@example.com\",\n    \"firstName\": \"Alex\",\n    \"lastName\": \"Example\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Send a payout",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "description": "**Required on every POST.** Generate it once per business intent —\nbefore the call, and durably — and reuse it on every retry of that\nintent. A key minted after a successful response can never help you.\n\nSame key + same payload = replay: `200` with the stored transaction.\nSame key + different payload = `409 IDEMPOTENCY_CONFLICT`.\n\nUnique per brand; keys are compared exactly after trimming surrounding\nwhitespace."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payouts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payouts"
              ]
            },
            "description": "Executes a payout immediately — the platform performs no approval step\nof its own, because approval already happened on the client's side\nbefore the call.\n\n**The destination rule is the whole endpoint.** For a normal method,\n`destination` is required; it is stored encrypted under the brand key\nand only a head+tail mask (`destinationMasked`) is ever readable\nafterwards. For a method flagged `hostedPayout` on\n`GET /v1/payment-methods`, the PSP collects the destination on its own\npage: send **no** `destination` and **no** `bankCode`, and send the\nend user to the `redirectUrl` on the answer. Sending one anyway is\nrefused (`DESTINATION_NOT_ACCEPTED`) rather than quietly stored and\nignored — this is what makes a card payout possible without a PAN ever\nreaching the platform.\n\nWhere the account number does not route itself — a UK sort code on\nFaster Payments, an IFSC on India's bank transfer — send `bankCode`\nbeside `destination`. Whether that applies depends on **method and\ncurrency**: read `requiresDestinationBankCode` per (method, currency)\nfrom `GET /v1/payment-methods`.\n\nSome bank rails also demand a block DESCRIBING the receiving account —\nits type, its bank, its branch — before they will accept the payout at\nall. Send those in `destinationDetails`, and send exactly the fields\n`requiredDestinationDetails` names for this (method, currency): a\nmissing one is `DESTINATION_DETAILS_REQUIRED` (with the field names in\n`details`), and sending the object at all to a rail whose list is empty\nis `DESTINATION_DETAILS_NOT_ACCEPTED` rather than data we accept and\nthen drop.\n\nFor a hosted payout method the platform may be able to pay a card the end user has\nalready paid in with; the answer then carries no `redirectUrl`, starts at `PROCESSING`,\nand names the card in `destinationMasked`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 120.00,\n  \"currency\": \"EUR\",\n  \"method\": \"BANK_TRANSFER\",\n  \"userRef\": \"user-10427\",\n  \"clientTxRef\": \"withdrawal-4412\",\n  \"country\": \"DE\",\n  \"destination\": \"DE36000000000000000000\",\n  \"customer\": {\n    \"email\": \"alex@example.com\",\n    \"firstName\": \"Alex\",\n    \"lastName\": \"Example\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Check a payout destination before sending it",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "description": "Optional here, and never consumed — see above. Same format as on\nthe real call: 1-64 characters of letters, digits, dash and\nunderscore."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payouts/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payouts",
                "validate"
              ]
            },
            "description": "A dry run of `POST /v1/payouts`. Same body, same authentication, and\n**nothing is created**: no transaction, no callback, and no claim on an\nidempotency key. Call it as often as you like.\n\nIt answers what the real call would have said about the destination:\n\n- the provider your request would actually be routed to;\n- one `check` per thing that was looked at, each carrying the same\n  machine `code` the real call would have answered with;\n- whether the beneficiary's name could be compared against the one the\n  rail holds (`nameMatch`).\n\nShapes are checked against the same patterns\n`GET /v1/payment-methods` publishes in `destinationFields`, so a form\nbuilt from that endpoint and a payout refused by this one can never\ndisagree. Where the routed provider publishes a destination check of\nits own, it is asked — but only once the shape passes, so a malformed\nvalue never costs a round trip.\n\n**It answers `200` even when the payout would be refused.** \"This\ndestination is wrong\" is the answer, not an error; read `valid`. A\nmalformed *request* is still a `400`, because there is nothing to\nvalidate. A `200` with `valid: true` is a statement about the request\nas it stands — it is not a promise that a payout will succeed.\n\nA **read-only** API key may call this: it moves no money and creates\nnothing.\n\nSending the `Idempotency-Key` you intend to use for the real payout is\noptional. It is read only as the seed that decides which provider the\npayment would be offered to first, so the answer names the provider the\nreal call will actually reach. **It is not consumed and reserves\nnothing** — the same key is still free for the payout itself.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 80.00,\n  \"currency\": \"GBP\",\n  \"method\": \"BANK_TRANSFER\",\n  \"userRef\": \"user-10427\",\n  \"country\": \"GB\",\n  \"destination\": \"00000000\",\n  \"bankCode\": \"12-34-56\",\n  \"customer\": {\n    \"email\": \"alex@example.com\",\n    \"firstName\": \"Alex\",\n    \"lastName\": \"Example\",\n    \"ipAddress\": \"203.0.113.10\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Refund a completed payin",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "description": "**Required on every POST.** Generate it once per business intent —\nbefore the call, and durably — and reuse it on every retry of that\nintent. A key minted after a successful response can never help you.\n\nSame key + same payload = replay: `200` with the stored transaction.\nSame key + different payload = `409 IDEMPOTENCY_CONFLICT`.\n\nUnique per brand; keys are compared exactly after trimming surrounding\nwhitespace."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/refunds",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "refunds"
              ]
            },
            "description": "Refunds a `COMPLETED` payin, fully or partially. Omit `amount` to\nrefund the full remaining refundable balance.\n\nThe refund is **not routed** — it follows its parent payin to the same\nPSP product that took the money, using the route settings snapshotted\non the parent row.\n\nThe refundable remainder is enforced under a lock on the parent, so\nconcurrent refunds cannot overdraw it (`REFUND_EXCEEDS_REMAINING`). A\npayin under an active dispute is refused outright (`DISPUTED`) — this\ncheck runs **first**, because refunding a disputed payin is the classic\ndouble-credit loss and every other refusal message would be a lie\nabout the real reason.\n\nWhether a refund settles inside this response or later over a callback\nis a per-provider property. Do not assume either; read `status` and\nwait for a callback if it is not terminal.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"transactionId\": \"{{transactionId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Transactions",
      "description": "Reading back what happened.",
      "item": [
        {
          "name": "Read a transaction",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions/:publicId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions",
                ":publicId"
              ],
              "variable": [
                {
                  "key": "publicId",
                  "value": "{{transactionId}}",
                  "description": "Path parameter `publicId`."
                }
              ]
            },
            "description": "The reconciliation route. Use it when a callback never arrived, when a\nPOST failed in transport and you never learned its outcome, or after a\nrestart mid-flight — ask the platform rather than guessing.\n\nScoped to the brand of the presented API key: a transaction belonging\nto another brand answers `404 NOT_FOUND`, exactly as an unknown id does.\n\nWorks for every transaction the brand owns, including `REVERSAL` rows\nthe platform created on its own from a PSP notification."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Configuration",
      "description": "What this brand can currently accept.",
      "item": [
        {
          "name": "What this brand currently accepts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payment-methods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payment-methods"
              ],
              "query": [
                {
                  "key": "currency",
                  "value": "EUR",
                  "description": "Exact ISO-4217 match. Omitted = every currency.",
                  "disabled": true
                },
                {
                  "key": "country",
                  "value": "DE",
                  "description": "ISO-3166-1 alpha-2. A row whose own `country` is null is\ncountry-agnostic and matches every filter value. Omitted = no\ncountry filtering at all.",
                  "disabled": true
                }
              ]
            },
            "description": "One row per enabled (provider config, method, currency, country) route\nthe brand has. **Build your payment form entirely from this list** —\nlimits, required customer fields, which directions each option\nsupports, and what a payout destination for it actually is. Nothing\nabout any specific PSP belongs in client code.\n\nRows come from your brand's current routing configuration, so the list\nchanges without a release. Re-read it rather than caching it\nindefinitely.\n\nAn empty list means the brand currently has nothing matching: no\nenabled provider configuration, no enabled routes on one, or no route\nmatching the filters you passed. That is a `200` with `[]`, not an\nerror."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Withdrawals",
      "description": "Withdrawal requests — a request that becomes one or more payouts, with an optional approval step.",
      "item": [
        {
          "name": "Request a withdrawal",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "description": "**Required on every POST.** Generate it once per business intent —\nbefore the call, and durably — and reuse it on every retry of that\nintent. A key minted after a successful response can never help you.\n\nSame key + same payload = replay: `200` with the stored transaction.\nSame key + different payload = `409 IDEMPOTENCY_CONFLICT`.\n\nUnique per brand; keys are compared exactly after trimming surrounding\nwhitespace."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/withdrawals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "withdrawals"
              ]
            },
            "description": "A request to pay an end user, which the platform turns into one or more\nordinary payouts. Use it instead of `POST /v1/payouts` when the payment\nneeds an approval step, may be too large for a single route, or is paid\nin parts of your choosing. The one-shot payout is unchanged and remains\nthe simple default.\n\n**Approval** is the brand's setting, frozen onto the request as\n`approval`: `NONE` (approved at once), `CLIENT` (you decide it with\n`/approve` or `/reject`), `MANUAL` (a person decides it in the manual review queue).\nA brand set to a threshold resolves to `MANUAL` at or above the figure\nand `NONE` below it; the figure is in one currency and is never\nconverted, so a request in any other currency goes to `MANUAL`.\n\n**Execution** is yours per request. `AUTO` (the default): once approved\nthe platform makes the payouts itself — one for the whole amount, or,\nwhen every route refuses the amount as too large, the fewest equal parts\nunder the largest route maximum, one after another, stopping at the\nfirst part that does not complete. `CLIENT`: you create each payout\nwith `POST /v1/withdrawals/{id}/payouts`.\n\nThe routing and destination rules are those of a payout and are checked\nhere, before any request exists, with the same refusal codes. A request\ncarries **no customer data, bank block or return URL** — it may wait for\napproval and the platform keeps no customer data at rest — so an `AUTO`\nrequest to a route that needs any of them is refused; use `CLIENT`\nexecution, whose payouts carry them. A hosted payout method cannot be\nused: a request needs its `destination`.\n\nEvery payout goes through the ordinary payout path — your\npre-authorisation hook is asked for each one, and each one appears in\n`GET /v1/transactions/{id}` and in its own callbacks.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 300.00,\n  \"currency\": \"EUR\",\n  \"method\": \"BANK_TRANSFER\",\n  \"userRef\": \"user-10427\",\n  \"clientRef\": \"withdrawal-4412\",\n  \"country\": \"DE\",\n  \"destination\": \"DE36000000000000000000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Read a withdrawal request",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/withdrawals/:publicId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "withdrawals",
                ":publicId"
              ],
              "variable": [
                {
                  "key": "publicId",
                  "value": "{{transactionId}}",
                  "description": "Path parameter `publicId`."
                }
              ]
            },
            "description": "The request, its money (`paidAmount`, `inFlightAmount`,\n`remainingAmount`) and every payout made for it. Scoped to the brand of\nthe presented key: another brand's request answers `404 NOT_FOUND`,\nexactly as an unknown id does."
          },
          "response": []
        },
        {
          "name": "Approve a request whose approval step is yours",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/withdrawals/:publicId/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "withdrawals",
                ":publicId",
                "approve"
              ],
              "variable": [
                {
                  "key": "publicId",
                  "value": "{{transactionId}}",
                  "description": "Path parameter `publicId`."
                }
              ]
            },
            "description": "Only on a request whose `approval` is `CLIENT` and whose status is\n`AWAITING_APPROVAL` (`409` otherwise). An `AUTO` request starts paying\nat once, and the answer carries its first payout."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Reject a request whose approval step is yours",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/withdrawals/:publicId/reject",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "withdrawals",
                ":publicId",
                "reject"
              ],
              "variable": [
                {
                  "key": "publicId",
                  "value": "{{transactionId}}",
                  "description": "Path parameter `publicId`."
                }
              ]
            },
            "description": "Only on a request whose `approval` is `CLIENT` and whose status is\n`AWAITING_APPROVAL` (`409` otherwise). Ends it as `REJECTED`; nothing is\npaid. The optional `reason` is kept with the request for your own staff; it is never sent back."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Cancel a request, or the unpaid remainder of one you pay yourself",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/withdrawals/:publicId/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "withdrawals",
                ":publicId",
                "cancel"
              ],
              "variable": [
                {
                  "key": "publicId",
                  "value": "{{transactionId}}",
                  "description": "Path parameter `publicId`."
                }
              ]
            },
            "description": "Before any payout exists (`AWAITING_APPROVAL` or `APPROVED`) the request\nends as `CANCELLED`. On a `CLIENT`-executed request that is under way it\ndrops the unpaid remainder: payouts already in flight finish, and the\nrequest closes when the last of them ends — `PARTIALLY_COMPLETED` if\nanything was paid, `CANCELLED` if nothing was. A request the platform is\npaying (`AUTO`, `IN_PROGRESS`) cannot be stopped between its payouts\n(`409 WITHDRAWAL_NOT_CANCELLABLE`); a closed one answers\n`409 WITHDRAWAL_CLOSED`."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Pay part of a request you pay yourself",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "description": "**Required on every POST.** Generate it once per business intent —\nbefore the call, and durably — and reuse it on every retry of that\nintent. A key minted after a successful response can never help you.\n\nSame key + same payload = replay: `200` with the stored transaction.\nSame key + different payload = `409 IDEMPOTENCY_CONFLICT`.\n\nUnique per brand; keys are compared exactly after trimming surrounding\nwhitespace."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/withdrawals/:publicId/payouts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "withdrawals",
                ":publicId",
                "payouts"
              ],
              "variable": [
                {
                  "key": "publicId",
                  "value": "{{transactionId}}",
                  "description": "Path parameter `publicId`."
                }
              ]
            },
            "description": "One payout of up to what is left (`remainingAmount` — neither paid nor\nunder way) of a `CLIENT`-executed request, to the request's own\ndestination. More than that is `400 AMOUNT_EXCEEDS_REMAINING` with the\nremainder in `details`; an `AUTO` request answers\n`409 WITHDRAWAL_NOT_CLIENT_EXECUTED`; a closed or cancelled one\n`409 WITHDRAWAL_NOT_PAYABLE` / `WITHDRAWAL_CANCELLING`.\n\nIt is an ordinary payout in every other respect: its own\n`Idempotency-Key`, the same preflight and refusal codes, your\npre-authorisation hook, and the answer in the ordinary transaction view\n(`201`, or `200` for a replay). Customer data, the bank block and a\nreturn URL the route needs travel HERE, with the payout, and are never\nstored. A payout that fails leaves the request open for another try."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "var ok = pm.response.code === 200 || pm.response.code === 201;",
                  "pm.test(\"created or replayed\", function () { pm.expect(ok).to.be.true; });",
                  "if (ok) {",
                  "  var body = pm.response.json();",
                  "  if (body && body.id) pm.collectionVariables.set(\"transactionId\", body.id);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Callbacks (test your own receiver)",
      "description": "These requests do not call the API. They post a signed sample delivery\nto **your** endpoint, signed exactly as the platform signs the real\nthing, so you can prove your verification before any money is involved.\n\nSet `callbackUrl` (and `authorizeUrl`, if the pre-authorisation hook is\nswitched on for your brand) and `callbackSecret` first. Edit the body\nfreely — the signature is computed over whatever bytes are actually sent,\nso a tampered body is exactly the negative test you want.",
      "item": [
        {
          "name": "transaction.status_changed — A transaction changed status",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-PS-Signature",
                "value": "computed by the collection pre-request script",
                "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">"
              },
              {
                "key": "X-PS-Event",
                "value": "transaction.status_changed",
                "description": "The event name, identical to `event` in the body."
              },
              {
                "key": "X-PS-Delivery",
                "value": "84213",
                "description": "Delivery id — the **same value on every retry** of this delivery.\nDeduplicate on it."
              },
              {
                "key": "X-PS-Sequence",
                "value": "2",
                "description": "This notice's place among every notice about its transaction —\n1, 2, 3… per transaction, no gaps — repeating `sequence` from the\nbody. **Not covered by the signature**: order by the body field,\nwhich is. Present on every delivery, including notices enqueued\nbefore the body field existed."
              }
            ],
            "url": {
              "raw": "{{callbackUrl}}",
              "host": [
                "{{callbackUrl}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"event\": \"transaction.status_changed\",\n  \"transactionId\": \"tx_4bQ9xR2mKpLvN7sTfWgHdYcZ\",\n  \"parentTransactionId\": null,\n  \"type\": \"PAYIN\",\n  \"reversalKind\": null,\n  \"status\": \"COMPLETED\",\n  \"method\": \"CARD\",\n  \"amount\": 25.00,\n  \"currency\": \"EUR\",\n  \"settledAmount\": 25,\n  \"settledCurrency\": \"EUR\",\n  \"provider\": \"EXAMPLEPSP\",\n  \"userRef\": \"user-10427\",\n  \"clientTxRef\": \"order-8891\",\n  \"errorCode\": null,\n  \"errorMessage\": null,\n  \"errorReason\": null,\n  \"errorAction\": null,\n  \"errorRetryable\": null,\n  \"errorCascadable\": null,\n  \"errorUserMessage\": null,\n  \"occurredAt\": \"2026-09-04T09:12:31.482913Z\",\n  \"fee\": {\n    \"amount\": 0.75,\n    \"currency\": \"EUR\"\n  },\n  \"attemptCount\": 1,\n  \"cascaded\": false,\n  \"attempts\": [\n    {\n      \"attemptNo\": 1,\n      \"provider\": \"EXAMPLEPSP\",\n      \"outcome\": \"ACCEPTED\",\n      \"error\": null,\n      \"startedAt\": \"2026-09-04T09:12:04.481700Z\",\n      \"endedAt\": \"2026-09-04T09:12:04.769800Z\"\n    }\n  ],\n  \"correction\": null,\n  \"abandonedReason\": null,\n  \"sequence\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Delivered to the brand's configured callback URL for every status\ntransition the client did not itself cause synchronously — a\nnotification from the provider, the platform's own reconciliation of an\nunresolved transaction, or a manual intervention. A status the client\nalready learned from its own API response produces no callback.\n\nAnswer **2xx to anything whose signature verified**, including\nduplicates, stale deliveries and transactions you do not recognise.\nAnything else buys retries (and eventually a `DEAD` delivery) for\nsomething a retry cannot fix. Refuse only what fails verification, with\n`401` and no explanation.\n\nSee the integration guide, [§ 5 \"Verifying a callback signature\"](https://docs.aurumsys.com/guide/#5-verifying-a-callback-signature)."
          },
          "response": []
        },
        {
          "name": "transaction.reversed — Money moved backwards",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-PS-Signature",
                "value": "computed by the collection pre-request script",
                "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">"
              },
              {
                "key": "X-PS-Event",
                "value": "transaction.reversed",
                "description": ""
              },
              {
                "key": "X-PS-Delivery",
                "value": "1",
                "description": ""
              },
              {
                "key": "X-PS-Sequence",
                "value": "2",
                "description": "This notice's place among every notice about its transaction —\n1, 2, 3… per transaction, no gaps — repeating `sequence` from the\nbody. **Not covered by the signature**: order by the body field,\nwhich is. Present on every delivery, including notices enqueued\nbefore the body field existed."
              }
            ],
            "url": {
              "raw": "{{callbackUrl}}",
              "host": [
                "{{callbackUrl}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"event\": \"transaction.reversed\",\n  \"transactionId\": \"tx_7hK2mNpQrStUvWxYzAbCdEf\",\n  \"parentTransactionId\": \"tx_4bQ9xR2mKpLvN7sTfWgHdYcZ\",\n  \"type\": \"REVERSAL\",\n  \"reversalKind\": \"CHARGEBACK\",\n  \"status\": \"COMPLETED\",\n  \"method\": \"CARD\",\n  \"amount\": 25.00,\n  \"currency\": \"EUR\",\n  \"settledAmount\": 25,\n  \"settledCurrency\": \"EUR\",\n  \"provider\": \"EXAMPLEPSP\",\n  \"userRef\": \"user-10427\",\n  \"clientTxRef\": \"order-8891\",\n  \"errorCode\": null,\n  \"errorMessage\": null,\n  \"errorReason\": null,\n  \"errorAction\": null,\n  \"errorRetryable\": null,\n  \"errorCascadable\": null,\n  \"errorUserMessage\": null,\n  \"occurredAt\": \"2026-09-18T14:03:02.117400Z\",\n  \"fee\": null,\n  \"attempts\": [],\n  \"attemptCount\": 0,\n  \"cascaded\": false,\n  \"correction\": null,\n  \"abandonedReason\": null,\n  \"sequence\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "A deliberately distinct event name for a `REVERSAL` transaction: money\nthat moved and then moved back — a payout the bank returned, or a\nchargeback / recall / fraud claim on a settled payin.\n\nIt is **not** a `REFUND`: a refund is client-initiated and draws\nagainst a refundable budget; a reversal is inbound and nobody asked us\nfor it. The event name differs precisely so that an unprepared client\nfails loudly at integration time instead of dropping the most\nfinancially significant message the platform sends into an `else`\nbranch.\n\nEvery transition of the reversal row — its creation and its later\nresolution alike — arrives under this same name. `parentTransactionId`\npoints at the movement being compensated, and **`reversalKind` says\nwhich compensation it is** — including the one case where the money\ncomes back to you rather than leaving: `CHARGEBACK_WON`. Branch on\n`reversalKind`, not on the event name and not on arrival order."
          },
          "response": []
        },
        {
          "name": "transaction.corrected — An outcome was corrected by hand",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-PS-Signature",
                "value": "computed by the collection pre-request script",
                "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">"
              },
              {
                "key": "X-PS-Event",
                "value": "transaction.corrected",
                "description": ""
              },
              {
                "key": "X-PS-Delivery",
                "value": "1",
                "description": ""
              },
              {
                "key": "X-PS-Sequence",
                "value": "2",
                "description": "This notice's place among every notice about its transaction —\n1, 2, 3… per transaction, no gaps — repeating `sequence` from the\nbody. **Not covered by the signature**: order by the body field,\nwhich is. Present on every delivery, including notices enqueued\nbefore the body field existed."
              }
            ],
            "url": {
              "raw": "{{callbackUrl}}",
              "host": [
                "{{callbackUrl}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"event\": \"transaction.corrected\",\n  \"transactionId\": \"tx_4bQ9xR2mKpLvN7sTfWgHdYcZ\",\n  \"parentTransactionId\": null,\n  \"type\": \"PAYIN\",\n  \"reversalKind\": null,\n  \"status\": \"COMPLETED\",\n  \"method\": \"CARD\",\n  \"amount\": 25.00,\n  \"currency\": \"EUR\",\n  \"settledAmount\": 25,\n  \"settledCurrency\": \"EUR\",\n  \"provider\": \"EXAMPLEPSP\",\n  \"userRef\": \"user-10427\",\n  \"clientTxRef\": \"order-8891\",\n  \"errorCode\": null,\n  \"errorMessage\": null,\n  \"errorReason\": null,\n  \"errorAction\": null,\n  \"errorRetryable\": null,\n  \"errorCascadable\": null,\n  \"errorUserMessage\": null,\n  \"occurredAt\": \"2026-09-18T14:03:02.117400Z\",\n  \"fee\": {\n    \"amount\": 0.75,\n    \"currency\": \"EUR\"\n  },\n  \"attemptCount\": 1,\n  \"cascaded\": false,\n  \"attempts\": [\n    {\n      \"attemptNo\": 1,\n      \"provider\": \"EXAMPLEPSP\",\n      \"outcome\": \"ACCEPTED\",\n      \"error\": null,\n      \"startedAt\": \"2026-09-04T09:12:04.481700Z\",\n      \"endedAt\": \"2026-09-04T09:12:04.769800Z\"\n    }\n  ],\n  \"correction\": {\n    \"reason\": \"DISPUTE_WON\",\n    \"previousStatus\": \"COMPLETED\",\n    \"newStatus\": \"COMPLETED\",\n    \"disputeCaseId\": \"dsp_9tMv2QkR7bXpLs4nFhCdWyZa\",\n    \"correctedAt\": \"2026-09-18T14:03:02.117400Z\"\n  },\n  \"abandonedReason\": null,\n  \"sequence\": 2\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "An outcome that could not be established from a provider's answer was\nestablished by a person instead: a transaction stuck at `IN_DOUBT` and\ndecided by hand, or a dispute settled somewhere the payment rail cannot\nsee.\n\nIt is a notice about a DECISION, not a second copy of a status. A\nmanual resolution also sends `transaction.status_changed` for the new\nstatus; a dispute outcome is recorded against the disputed payin, whose\nown `status` does not move, and a dispute WON additionally sends\n`transaction.reversed` for the compensating row that returns the money.\n\n**Nothing is credited or debited by this event.** Money is always in\nthe transaction rows — the corrected transaction, or the reversal a won\ndispute writes. A ledger that moves an amount on a correction\ndouble-counts every win.\n\n`correction.previousStatus` and `correction.newStatus` MAY be equal;\nbranch on `correction.reason`, not on whether they differ. The reason\nvocabulary can grow: treat an unrecognised value as \"this transaction\nwas corrected\" and re-read the transaction."
          },
          "response": []
        },
        {
          "name": "payin.abandoned — A payer walked away",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-PS-Signature",
                "value": "computed by the collection pre-request script",
                "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">"
              },
              {
                "key": "X-PS-Event",
                "value": "payin.abandoned",
                "description": ""
              },
              {
                "key": "X-PS-Delivery",
                "value": "1",
                "description": "Delivery id — the **same value on every retry** of this delivery.\nDeduplicate on it."
              },
              {
                "key": "X-PS-Sequence",
                "value": "2",
                "description": "This notice's place among every notice about its transaction —\n1, 2, 3… per transaction, no gaps — repeating `sequence` from the\nbody. **Not covered by the signature**: order by the body field,\nwhich is. Present on every delivery, including notices enqueued\nbefore the body field existed."
              }
            ],
            "url": {
              "raw": "{{callbackUrl}}",
              "host": [
                "{{callbackUrl}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"event\": \"payin.abandoned\",\n  \"transactionId\": \"tx_4bQ9xR2mKpLvN7sTfWgHdYcZ\",\n  \"parentTransactionId\": null,\n  \"type\": \"PAYIN\",\n  \"reversalKind\": null,\n  \"status\": \"EXPIRED\",\n  \"method\": \"CARD\",\n  \"amount\": 25.00,\n  \"currency\": \"EUR\",\n  \"settledAmount\": null,\n  \"settledCurrency\": null,\n  \"provider\": \"EXAMPLEPSP\",\n  \"userRef\": \"user-10427\",\n  \"clientTxRef\": \"order-8891\",\n  \"errorCode\": null,\n  \"errorMessage\": null,\n  \"errorReason\": null,\n  \"errorAction\": null,\n  \"errorRetryable\": null,\n  \"errorCascadable\": null,\n  \"errorUserMessage\": null,\n  \"occurredAt\": \"2026-09-22T11:28:01.903100Z\",\n  \"fee\": null,\n  \"attemptCount\": 1,\n  \"cascaded\": false,\n  \"attempts\": [\n    {\n      \"attemptNo\": 1,\n      \"provider\": \"EXAMPLEPSP\",\n      \"outcome\": \"ACCEPTED\",\n      \"error\": null,\n      \"startedAt\": \"2026-09-22T11:12:04.481700Z\",\n      \"endedAt\": \"2026-09-22T11:12:04.769800Z\"\n    }\n  ],\n  \"correction\": null,\n  \"abandonedReason\": \"LINK_EXPIRED\",\n  \"sequence\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Sent once when a payin reaches its deadline with nothing paid — the end\nuser never finished on the provider's page, or never opened it.\n\nIt arrives **beside** the ordinary `transaction.status_changed` that\ncarries `status: EXPIRED`, not instead of it: the status change is the\nledger fact, this is the marketing fact, and they are separate events so\nthat retargeting work need not subscribe to every status transition and\nfilter. Settle books on the status change; drive reminders off this.\n\n**At most one per transaction, ever**, and never for a payin that\ncompleted, failed, was cancelled or is still being decided.\n\nIt is **not final**. `EXPIRED` is one of the two statuses late truth can\noverrule: a pay-to-address rail may still receive funds afterwards, and\nthe same transaction id can later arrive as\n`transaction.status_changed` with `status: COMPLETED`. Anything\nautomated off this must be safe to undo.\n\nThe browser returning from the provider's page is **not** this signal\nand never triggers it — that return is unauthenticated, it happens on\ncancel as well as success, and it does not happen at all when a tab is\nclosed.\n\nAnswer **2xx to anything whose signature verified**, exactly as for\n`transaction.status_changed`.\n\nSee the integration guide, [\"Knowing when a payer walked away\"](https://docs.aurumsys.com/guide/#knowing-when-a-payer-walked-away)."
          },
          "response": []
        },
        {
          "name": "withdrawal.status_changed — A withdrawal request changed",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-PS-Signature",
                "value": "computed by the collection pre-request script",
                "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">"
              },
              {
                "key": "X-PS-Event",
                "value": "withdrawal.status_changed",
                "description": ""
              },
              {
                "key": "X-PS-Delivery",
                "value": "1",
                "description": "Delivery id — the **same value on every retry**. Deduplicate on it."
              }
            ],
            "url": {
              "raw": "{{callbackUrl}}",
              "host": [
                "{{callbackUrl}}"
              ]
            },
            "description": "Sent when a withdrawal request changes status, or when the amount paid\nagainst it rises, for every change you did not cause in your own call:\nan approval or rejection in the manual review queue, a payout completing on the\nprovider's notice, the platform starting the next part of a split\nrequest, the request closing when its last payout ends. What a\n`/v1/withdrawals` call answered you with produces no notice.\n\nThe body is the request view (the same one `GET /v1/withdrawals/{id}`\nanswers with) plus `event`, `occurredAt` and `sequence` — the notice's\nplace among every notice about THIS request, 1-based, also sent as\n`X-PS-Sequence`. Order by `sequence` and discard anything not newer,\nexactly as for transaction notices; deduplicate on `X-PS-Delivery`.\n\nEach payout also produces its own `transaction.status_changed` where it\nmoved on a provider's notice. Settle your ledger on either, not both —\nthe request notice is the one that says whether anything more will be\npaid.\n\nA payout reversed after the request completed does not reopen it and\nsends no request notice: the money coming back is its own transaction,\nannounced as `transaction.reversed`.\n\nAnswer **2xx to anything whose signature verified**."
          },
          "response": []
        },
        {
          "name": "transaction.authorize — Pre-authorisation — may this operation be raised?",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-PS-Signature",
                "value": "computed by the collection pre-request script",
                "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">"
              },
              {
                "key": "X-PS-Event",
                "value": "transaction.authorize",
                "description": "The event name, identical to `event` in the body."
              }
            ],
            "url": {
              "raw": "{{authorizeUrl}}",
              "host": [
                "{{authorizeUrl}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"event\": \"transaction.authorize\",\n  \"transactionId\": \"tx_9f2c1aQ4rXyZtN8cVbGhJkLd\",\n  \"type\": \"PAYIN\",\n  \"method\": \"CARD\",\n  \"provider\": \"EXAMPLEPSP\",\n  \"amount\": 25.00,\n  \"currency\": \"EUR\",\n  \"userRef\": \"user-10427\",\n  \"clientTxRef\": \"order-8891\",\n  \"country\": \"DE\",\n  \"destinationMasked\": null,\n  \"requestedAt\": \"2026-09-10T10:00:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Sent before any provider is contacted, when the pre-authorisation hook\nis switched on for the brand. Answer 200 with a decision. A timeout, a\nnon-2xx, an unreadable body or an unknown decision are all read as \"no\nanswer\", and the brand's `onUnreachable` setting decides what that means\n(default: the operation is declined).\n\nVerified exactly like a callback: `X-PS-Signature` over\n`\"<t>.<raw body>\"` under the same callback secret, which is why the hook\ncannot be enabled without a configured callback endpoint. Refuse a call\nyou cannot verify with `401` and no body.\n\nAsked once per operation, and never retried. A retry of the same\n`Idempotency-Key` returns the decided transaction rather than asking\nagain. Refunds and reversals are not asked about.\n\nSee the integration guide, [§ 7 \"Pre-authorisation hook\"](https://docs.aurumsys.com/guide/#7-pre-authorisation-hook)."
          },
          "response": []
        }
      ]
    }
  ]
}
