subscription.renewed
Fired when a subscription advances into a new billing period — prepaid renewal, postpaid auto-advance, scheduled downgrade, or the tenant-called POST /renew. Unlike the other subscription events, data is NOT a Subscription object: it is a unified renewal summary carrying the just-ended period's usage/overage rollup.
When it fires
QuotaStack sends this every time an API-origin subscription rolls into a new period. Three manual-billing paths reach it. A QuotaStack job advances a postpaid plan. Your own renew call advances a prepaid one. And a downgrade that was waiting for the period to end lands here too. All three send the same shape, so one handler covers them.
When it does not fire
Payment connectors send payment.succeeded instead. This event is for the three paths named above. The data here is not a subscription object. The data sums up the period that closed. If QuotaStack cannot read usage, it drops usage_summary, not the whole event. Check for the field before you read it.
| Field | Type | Meaning |
|---|---|---|
subscription_idrequired | string (uuid) | |
customer_idrequired | string (uuid) | |
billing_moderequired | string |
|
prior_periodrequired | object | The just-ended billing period [start, end); usage/overage below are aggregated over this window. |
new_periodrequired | object | The newly-opened billing period [start, end). |
usage_summaryoptional | object | Prior-period usage/overage rollup. Omitted if the usage read failed. |
usage_summary.net_balance_at_cycle_startrequired | integer (int64) | What the customer held when the period opened. Read before the new credits landed. Unit: millicredits, where 1 credit is 1000 millicredits. |
usage_summary.net_balance_at_cycle_endrequired | integer (int64) | What the customer held when the period closed. Read after the new credits landed. Unit: millicredits, where 1 credit is 1000 millicredits. |
{
"event_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a51",
"event_type": "subscription.renewed",
"tenant_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a01",
"environment": "live",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"external_customer_id": "user_42",
"created_at": "2026-08-01T00:00:00Z",
"idempotency_key": "sub-renew:0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a21",
"data": {
"subscription_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a21",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billing_mode": "postpaid",
"prior_period": {
"start": "2026-07-01T00:00:00Z",
"end": "2026-08-01T00:00:00Z"
},
"new_period": {
"start": "2026-08-01T00:00:00Z",
"end": "2026-09-01T00:00:00Z"
},
"usage_summary": {
"total_credits_consumed": 42000,
"net_balance_at_cycle_start": 50000,
"net_balance_at_cycle_end": 58000,
"by_billable_metric": {
"chat_message": 42000
},
"total_overage": 2000,
"overage_by_billable_metric": {
"chat_message": 2000
}
}
}
} What to do
Bill the customer for their overage, if you let them overspend. Read prior_period to know what the numbers cover. Read the overage endpoint instead when you need a figure you can trust without this event.
Which calls fire this
POST /v1/subscriptions/{id}/renew- QuotaStack's scheduler, on a timer — no API call involved.
Delivery
QuotaStack guarantees at-least-once delivery. An event may be delivered more than once if your endpoint returns a non-2xx response, the connection fails, or the request exceeds the delivery timeout.
Delivery timeout: 5 seconds per attempt. If your endpoint does not return a 2xx within 5 seconds, the attempt is treated as a failure and retried. Not configurable today.
One webhook URL per tenant. Multiple URLs and per-event routing are not supported. Configure the URL via the tenant config endpoint.
Retry schedule
If delivery fails (non-2xx response, timeout, or network error), QuotaStack retries with exponential backoff:
| Attempt | Delay after previous |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
| 7 | 24 hours |
After 7 failed attempts, the event is moved to a dead letter queue. Dead-lettered events are not lost — you can requeue them yourself, from the dashboard (Activity → Webhooks → Redeliver) or the API:
curl -X POST https://api.quotastack.io/v1/webhooks/events/{event_id}/redeliver \
-H "X-API-Key: $QS_KEY" \
-H "Idempotency-Key: redeliver:{event_id}"
Redelivery resets the event to pending with a fresh retry schedule (7
new attempts). The next attempt signs with your current secret —
useful when the event dead-lettered because of a secret rotation or an endpoint
outage you have since fixed. Only dead_letter events can be
redelivered; the call returns 409 for events in any other status.
Handling duplicates
Because delivery is at-least-once, your webhook handler should be idempotent. Use
the webhook-id header for deduplication — if you have already
processed an event with that ID, return 200 and skip processing.
Loading…