> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useplinth.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> What changed and when.

## v0.11.0 — July 2026

* **Outbound webhooks** — register endpoints (`/v1/webhook-endpoints`) and Plinth pushes signed, retried events to them. Per-endpoint `whsec_` secret (rotate anytime), `Plinth-Signature` HMAC header, exponential-backoff retries (up to 8), a full delivery log, and manual resend. See [Receiving webhooks](/guides/receiving-webhooks).
* **Checkout-funded upgrades** — `POST /v1/subscriptions/:id/change-checkout` upgrades a subscription with no card on file by collecting the proration via a Nomba checkout; the plan swaps when payment settles.
* **Cancel & reactivate** — `POST /v1/subscriptions/:id/cancel` (policy-driven: end-of-period keeps access until the period end, immediate cuts now) and `POST /v1/subscriptions/:id/reactivate` to undo a pending cancel. Subscriptions now expose `cancel_at_period_end`, `canceled_at`, `scheduled_change`, and `has_card`.
* **Dunning recovery via "Update payment"** — generate a `checkout-link` for a `past_due`/`grace`/`delinquent` subscription to settle the outstanding invoice and recover to `active` (emits `subscription.recovered`).
* **One live subscription per plan-group** (default) — creating a duplicate returns `409 customer_already_subscribed` with the existing id. Opt out with `allow_multiple_subscriptions: true`.

***

## v0.10.0 — July 2026

* **Plan `lookup_key` (required)** — every plan now carries a stable handle (e.g. `pro_monthly`) you reference instead of its UUID. `GET /v1/plans` returns it; unique per tenant. Your app no longer hardcodes environment-specific plan ids.
* **Price immutability guardrail** — `PATCH /v1/plans/:id` now rejects `amount_minor` / interval changes once a plan has subscribers (`400 plan_immutable`), preventing accidental silent re-pricing. Name, lookup\_key, trial days, and archive status stay editable.
* **Archive-or-delete** — `DELETE /v1/plans/:id` archives a plan that has subscribers (`active: false` — existing subscribers keep billing, history preserved) and hard-deletes one that was never subscribed. The response tells you which happened.
* **Active-only plan list** — `GET /v1/plans` returns active plans by default; pass `include_archived=true` to include archived ones.

***

## v0.9.0 — June 2026

* **Arrears billing mode** — set `billing_mode: "arrears"` on a subscription to invoice at end of period instead of start. Useful for usage-based and enterprise contract billing.
* **Entitlements API** — `GET /v1/customers/:id/entitlements` returns a single `has_access` boolean, `tier`, and `subscription_state`. No more parsing state machines in your app.
* **Postpaid / Arrears preset** — a new billing preset combining arrears mode, 14-day grace, and lenient dunning. Apply with `preset: "postpaid"`.
* **Debt cap** (`max_debt_minor`) — a new TenantPolicy knob that caps how much outstanding balance a customer can accumulate before access is cut, regardless of grace settings.

***

## v0.8.0 — May 2026

* **Virtual account provisioning** — `POST /v1/customers/:id/virtual-account` gives each customer a dedicated NUBAN account number at Nomba MFB.
* **Inbound transfer reconciliation** — exact match, partial payment, overpayment (surplus to balance), and suspense queue for unmatched transfers. Webhooks for each case: `transfer.matched`, `transfer.partial`, `transfer.suspense`.
* **Dunning engine** — built-in retry schedule (days +1, +3, +7, +14) with payday-aware adjustment. Hard decline codes skip straight to grace. Configurable via TenantPolicy.
* **Five billing presets** — SaaS-Standard, Lenient, Strict, Transfer-First, Postpaid. Apply with `POST /v1/tenant/apply-preset`.
* **Plan change with proration** — `POST /v1/subscriptions/:id/preview-change` shows the maths before you commit. Per-second kobo proration with half-up rounding. Immediate or deferred strategies.
* **Grace period tracking** — `grace_expires_at` field on subscription status. `subscription.grace` event fires when grace begins.
