storage.tier_changed
Fires when the workspace recording storage size actually changes — an upgrade, a scheduled downgrade, or a drop to free after an unpaid renewal.
storage.tier_changed fires when the workspace recording storage size actually changes. A monthly renewal that keeps the same size does not fire it. The reason field says why the size changed:
customer: you changed the size from the console or PUT /v1/storage/tier. An upgrade applies immediately, and the wallet is debited for the rest of the month.renewal: the renewal on the 1st applied a downgrade you scheduled to a smaller paid size. charge_minor is that size's monthly price.downgrade: the renewal on the 1st applied a downgrade you scheduled to the free 100 MB. Nothing is charged, and nothing is refunded.past_due: storage renews on the 1st from the wallet. If the wallet is still negative 72 hours after the renewal, the workspace drops to the free 100 MB.When a workspace ends up over its new size, the oldest recordings are deleted first. Use this event to update your own billing records or notify workspace members.
This event is subscribable via workspace webhooks (POST /v1/webhooks).
{
"kind": "storage.tier_changed",
"workspace_id": "01900000-0000-7000-8000-000000000002",
"from_tier_mb": 100,
"to_tier_mb": 1024,
"charge_minor": 29950,
"currency": "KES",
"reason": "customer",
"changed_at": "2026-09-16T00:00:00Z"
}| Field | Type | Description |
|---|---|---|
kind | string | Always storage.tier_changed. |
workspace_id | string (UUID) | The workspace whose storage changed. |
from_tier_mb | integer | Size before the change, in MB. |
to_tier_mb | integer | Size after the change, in MB. One of 100 (free), 1024, 4096, 51200. |
charge_minor | integer (int64) | What this change debited from the wallet, in minor units (KES cents). 0 when nothing was charged. |
currency | string | Wallet currency, e.g. KES. |
reason | string | One of customer, renewal, downgrade, past_due. |
changed_at | string (RFC 3339) | When the change was applied. |
The example above is an upgrade from the free 100 MB to 1 GB halfway through a 30-day month: the monthly KES 599 is prorated to the half month that is left, so charge_minor is 29950 (KES 299.50).
Every webhook delivery includes the following request headers:
| Header | Description |
|---|---|
X-Sautikit-Signature | HMAC-SHA256 of the raw body, hex-encoded. Verify with your subscription secret. |
X-Sautikit-Idempotency-Key | Unique delivery ID for deduplication. |
X-Sautikit-Event | Literal event kind: storage.tier_changed. |
X-Sautikit-Idempotency-Key header.dead_letter.import { createHmac } from "node:crypto";
export async function POST(req) {
const sig = req.headers["x-sautikit-signature"];
const body = await req.text();
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(body)
.digest("hex");
if (sig !== expected) return new Response("Forbidden", { status: 403 });
const event = JSON.parse(body);
if (event.kind === "storage.tier_changed") {
const { from_tier_mb, to_tier_mb, charge_minor, currency, reason } = event;
console.log(`Storage ${reason}: ${from_tier_mb} MB -> ${to_tier_mb} MB, charged ${charge_minor / 100} ${currency}`);
// update subscription records, notify admins...
}
return new Response("OK", { status: 200 });
}events: ["storage.tier_changed"].