---
title: "subscription.renewed"
description: "QuotaStack sends this every time an API-origin [subscription](/docs/concepts/subscriptions) 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."
---

# 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](/docs/concepts/subscriptions) 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](/docs/concepts/subscriptions) 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.

## Payload — `data`

| Field | Type | Required | Meaning |
|---|---|---|---|
| `subscription_id` | string (uuid) | required |  |
| `customer_id` | string (uuid) | required |  |
| `billing_mode` | string | required | `prepaid` `postpaid` |
| `prior_period` | object | required | The just-ended billing period [start, end); usage/overage below are aggregated over this window. |
| `new_period` | object | required | The newly-opened billing period [start, end). |
| `usage_summary` | object | optional | Prior-period usage/overage rollup. Omitted if the usage read failed. |
| `usage_summary.net_balance_at_cycle_start` | integer (int64) | required | 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_end` | integer (int64) | required | What the customer held when the period closed. Read after the new credits landed. Unit: millicredits, where 1 credit is 1000 millicredits. |

### Example payload

```json
{
  "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](/docs/concepts/credits), 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`](/docs/api/subscriptions#renewSubscription)
- QuotaStack's scheduler, on a timer — no API call involved.

## See also

- [Overage billing in arrears](/docs/cookbook/overage-billing-in-arrears)
- [Subscriptions](/docs/concepts/subscriptions)

## 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:

```bash
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.
