{
  "generatedAt": "2026-09-22T06:38:05.953Z",
  "entries": [
    {
      "date": "2026-09-21",
      "title": "Receipts and credit notes",
      "breaking": false,
      "changes": [
        "New event family `receipt.*`: `receipt.issued`, `receipt.supplemented`, `receipt.credit_note.issued`. The event carries the document itself, including its public `url`.",
        "New endpoints: `GET /v1/payments/:id/receipts` (the whole chain for one payment, with received, refunded and net), `GET /v1/receipts` (keyset walk, optional `kind` filter), `GET /v1/receipts/:id`.",
        "New public endpoint `GET /v1/public/receipts/:id` — no API key. It is what the customer opens, and it strips `reference`.",
        "A partial refund now issues a credit note and emits an event. Before this it emitted nothing, touched no row, and past the newest 100 refunds could not be found again.",
        "`GET /v1/public/invoices/:publicId` now carries the latest `receipt`.",
        "A merchant billing profile (the letterhead a receipt is issued under) is edited in the console under Settings → Business."
      ],
      "migrationGuide": null,
      "url": "https://docs.thru.la/changelog#2026-09-21-receipts-and-credit-notes"
    },
    {
      "date": "2026-09-21",
      "title": "The `flow.*` webhook filter is refused — use `payment.flow.*`",
      "breaking": true,
      "changes": [
        "The wildcard `flow.*` is no longer a valid `eventTypes` entry. Sending it to `POST /v1/webhooks` or an update now returns 400.",
        "The flow family is offered as `payment.flow.*`, which is the prefix its three event types actually share (`payment.flow.step`, `payment.flow.approved`, `payment.flow.rejected`).",
        "No endpoint starts or stops receiving anything: `flow.*` matched zero events, so a subscriber on it was already receiving nothing.",
        "`checkout.*` and `facilitator.*` are unchanged and still accepted, even though their types share a longer prefix."
      ],
      "migrationGuide": "https://docs.thru.la/migrations/flow-wildcard-to-payment-flow.md",
      "url": "https://docs.thru.la/changelog#2026-09-21-flow-wildcard-removed"
    },
    {
      "date": "2026-09-20",
      "title": "An event for money that moves no status, second payouts, and a reconciliation walk",
      "breaking": false,
      "changes": [
        "New event `payment.amount_increased`, in the `payment.*` family. It fires when more money lands on a payment whose status does not change — an underpaid payment topped up but still short, a further transfer onto an overpaid one, a manual recovery credit onto a confirmed one.",
        "`GET /v1/payments` and `GET /v1/sweeps` gain `updatedAfter`, `limit` and a keyset cursor over `(updatedAt, id)`, in the same envelope `GET /v1/checkout/sessions` already returned.",
        "A page of that walk is clamped to rows older than a 60-second safety lag, so the cursor cannot advance past a row that was still committing.",
        "`settlement.completed` carries the payout sequence and the lifetime total, so a second payout for the same payment can be attributed.",
        "`requiresManualPayout` narrowed to what it says: a chain with no sweep support, a held sweep, or native SUI."
      ],
      "migrationGuide": null,
      "url": "https://docs.thru.la/changelog#2026-09-20-amount-increased-and-reconciliation-walk"
    },
    {
      "date": "2026-09-19",
      "title": "Amount-based products — a locked amount per checkout session",
      "breaking": false,
      "changes": [
        "`Product.pricingMode` is `fixed` (unchanged) or `custom_amount`. A custom-amount product has no price; it has `minAmount` / `maxAmount` instead.",
        "`POST /v1/checkout/sessions` accepts `amount` on a custom-amount product; `POST /v1/public/products/:slug/checkout` accepts it too, so the hosted page can let the shopper name it.",
        "The amount is locked on the session and copied to the bound payment’s `expectedAmount`. No public redeem body accepts an amount — a test fails if one is ever added.",
        "An amount outside the bounds is a 400 whose body carries `minAmount`, `maxAmount` and `currency` as fields. The effective minimum is `max(the merchant’s, 0.01)`.",
        "The checkout-session payload gains `amount` and `currency`. Both are `null` on a fixed-price session, so one handler reads either kind.",
        "Every `payment.*` event and `GET /v1/payments/:id` now carry `checkoutSession { id, reference, metadata }` for a session-bound payment.",
        "Idempotent create compares `(source, amount)`: the same key with a different amount is a 409.",
        "Every rail on a custom-amount product must be a token thru actually detects, refused at write time — `enabledChains` had been bypassing that check.",
        "`@thru-payment/server` 0.3.1 is the published version and adds `payments.retrieve`."
      ],
      "migrationGuide": null,
      "url": "https://docs.thru.la/changelog#2026-09-19-custom-amount-products"
    },
    {
      "date": "2026-09-19",
      "title": "Arc mainnet is on",
      "breaking": false,
      "changes": [
        "A mainnet listener for `arc` is running, so an Arc mainnet payment is detected, confirmed and swept like any other chain.",
        "Arc chainId 5042 and the USDC contract were verified on chain before the listener was turned on.",
        "Before this, `arc` was already offered in the console’s payment chains while no mainnet listener existed — an Arc mainnet payment could be created and would never be detected.",
        "Chain RPC reads now rotate off endpoint rate limits (JSON-RPC -32005 / -32029) instead of treating them as the chain’s answer; a BNB detection stall on 2026-09-17 was caused by exactly that."
      ],
      "migrationGuide": null,
      "url": "https://docs.thru.la/changelog#2026-09-19-arc-mainnet"
    },
    {
      "date": "2026-09-18",
      "title": "Multiple settlement addresses, routed per product",
      "breaking": false,
      "changes": [
        "Several settlement addresses are allowed per (chain, network). Exactly one is the default for each; existing addresses became the defaults.",
        "The routing key is the product, per (chain, network). Everything unrouted — including payments with no product, created through `POST /v1/payments` — goes to the default.",
        "A new permission, `settlement.route`, governs routing. It can only choose among addresses that already went through `settlement.write` plus the cool-down, so it cannot introduce a new destination.",
        "The cool-down survives, per address. A new address and a changed address both wait it out before a sweep can land there; an address inside its cool-down is never a destination, whatever routes to it.",
        "Routing to an address that is already active takes effect immediately — that address was vetted when it activated.",
        "Promoting a due address change is now conditional on the exact pending change that was read, so a cancel can no longer be overwritten by a racing promotion. The promotion is written to the merchant audit trail.",
        "The console gets a routing board: drag a product onto an address, or use \"Move to…\"."
      ],
      "migrationGuide": null,
      "url": "https://docs.thru.la/changelog#2026-09-18-settlement-routing"
    },
    {
      "date": "2026-09-17",
      "title": "Direct Pay and subscriptions are removed",
      "breaking": true,
      "changes": [
        "All 11 merchant endpoints under `/v1/direct-pay/*` and both public ones are removed and return 404.",
        "The `subscription.*` event family (`activated`, `extended`, `expired`, `plan_change_scheduled`, `plan_changed`) is removed from the catalogue. Nothing emits it.",
        "Six fields leave the checkout-session payload: `kind`, `subscriptionId`, `planId`, `payerAddress`, `subscriptionExpiresAt`, `periodSeconds`. Use `source` (`product` | `invoice`) where you used `kind`.",
        "`POST /v1/products` with `kind: \"subscription\"` is a 400. A legacy subscription product answers 410 on its public page and 409 on a checkout session; the row itself survives.",
        "The tables are dropped. thru can no longer answer \"who had access, until when\" — an export was taken immediately before the migration ran.",
        "`@thru-payment/server`, `checkout-core` and `pay-sdk` ship the break as 0.2.0. The removed fields are deleted from the types rather than deprecated-and-nulled, on purpose: the compiler is the only place you can be told.",
        "`@thru-payment/x402` is untouched. Agent payments are a separate rail.",
        "A stored webhook filter naming `subscription.*` is now silently dropped when you save the endpoint, rather than refused — you did not make a typo, thru withdrew the events."
      ],
      "migrationGuide": "https://docs.thru.la/migrations/direct-pay-to-checkout-sessions.md",
      "url": "https://docs.thru.la/changelog#2026-09-17-direct-pay-removed"
    }
  ]
}
