Files
cfw-autumn/apps/docs/mintlify/documentation/webhooks.mdx
2026-05-21 12:16:50 +01:00

285 lines
9.2 KiB
Plaintext

---
title: "Webhooks"
description: "Receive real-time notifications when customer billing events occur"
---
With Autumn, you don't need webhooks for managing billing — subscription state, usage tracking, and access control are all synchronized automatically.
However, webhooks can still be useful for specific use cases where you want to trigger actions in your own systems.
## Use Cases
While Autumn handles billing complexity for you, webhooks are helpful for:
- **Sending activation emails** — Welcome new subscribers or notify users when their plan changes
- **Triggering workflows** — Start onboarding sequences, provision resources, or update CRM records
- **Syncing with external systems** — Keep your database, analytics, or other tools in sync with subscription changes
- **Deprovisioning access to services** — Shut off access to downstream services when a customer cancels their subscription
## Available Events
### billing.updated
Fired whenever a customer's plans change — new subscriptions, upgrades, downgrades, etc. Each event carries a `plan_changes` array describing exactly what happened to each affected plan.
| Action | Description |
|--------|-------------|
| `activated` | A plan is now active on the customer (newly attached, or a previously scheduled plan reached its start date) |
| `scheduled` | A plan has been queued to start at a future date |
| `updated` | A plan's state changed in place (cancellation set or cleared, `past_due` flipped, items added or removed) |
| `expired` | A plan ended and is no longer in effect |
Each entry also includes the `subscription` (or `purchase` for one-off products) after the change, and `previous_attributes` holding the prior values of any fields that were updated. For instance, if a plan was canceled at period end:
```json expandable
{
"action": "updated",
"subscription": {
"plan_id": "pro",
"status": "active",
"past_due": false,
"started_at": 1759248000000,
"canceled_at": 1761840000000,
"expires_at": 1764432000000,
"trial_ends_at": null,
"current_period_start": 1761840000000,
"current_period_end": 1764432000000
},
"previous_attributes": {
"canceled_at": null,
"expires_at": null
},
"item_changes": []
}
```
The top-level `tags` array surfaces optional reason tags describing why the event fired:
| Tag | When |
|-----|------|
| `trial_ended` | A trial just ended (Stripe subscription transition or the trial-expiry cron) |
| `phase_changed` | A Stripe subscription schedule phase advanced |
**Example payload (upgrade from `free` to `pro`):**
```json expandable
{
"type": "billing.updated",
"data": {
"object": "billing.updated",
"customer_id": "user_123",
"plan_changes": [
{
"action": "activated",
"subscription": {
"plan_id": "pro",
"status": "active",
"past_due": false,
"started_at": 1761840000000,
"canceled_at": null,
"expires_at": null,
"trial_ends_at": null,
"current_period_start": 1761840000000,
"current_period_end": 1764432000000
},
"previous_attributes": null,
"item_changes": []
},
{
"action": "expired",
"subscription": {
"plan_id": "free",
"status": "expired",
"past_due": false,
"started_at": 1759248000000,
"canceled_at": 1761840000000,
"expires_at": 1761840000000,
"trial_ends_at": null,
"current_period_start": null,
"current_period_end": null
},
"previous_attributes": { "status": "active" },
"item_changes": []
}
],
"tags": []
}
}
```
For entity-scoped events, the payload will also include an `entity_id`:
```json expandable
{
"type": "billing.updated",
"data": {
"object": "billing.updated",
"customer_id": "user_123",
"entity_id": "team_456",
"plan_changes": [ ... ],
"tags": []
}
}
```
**Common patterns:**
- **Sync Autumn state back to your DB** — listen for every `billing.updated` and persist each `plan_changes` entry's `subscription` snapshot keyed by `customer_id` (+ `entity_id` if set).
- **Notify on upgrades** — filter for entries with `action: "activated"`. For "upgrade from previous plan" specifically, pair it with an `action: "expired"` entry on the same event.
- **Detect cancellations** — filter for entries where `previous_attributes.canceled_at === null` (a cancellation was just set) or `previous_attributes.canceled_at` is a number (an uncancel).
- **Trial-end emails** — filter for `tags.includes("trial_ended")`.
### balances.limit_reached
Fired when a customer hits a usage limit for a feature. A limit can be the included allowance, a max purchase cap, or a spend limit.
| Limit Type | Description |
|------------|-------------|
| `included` | Customer has exhausted their included allowance |
| `max_purchase` | Customer has reached the maximum purchase cap for overage |
| `spend_limit` | Customer has hit their configured spend limit |
**Example payload:**
```json expandable
{
"type": "balances.limit_reached",
"data": {
"customer_id": "user_123",
"feature_id": "api_calls",
"limit_type": "included"
}
}
```
For entity-scoped usage, the payload will also include an `entity_id`:
```json expandable
{
"type": "balances.limit_reached",
"data": {
"customer_id": "user_123",
"feature_id": "api_calls",
"entity_id": "team_456",
"limit_type": "max_purchase"
}
}
```
### billing.auto_topup_succeeded
Fired when an [auto top-up](/documentation/modelling-pricing/auto-top-ups) successfully grants additional prepaid balance. Useful for sending receipts, updating internal ledgers, or reconciling balance after a recharge.
For auto-charged top-ups, the event fires only after the Stripe invoice is `paid`. For `invoice_mode` top-ups, the event fires once credits are granted and the invoice is finalized — `invoice.status` will typically be `"open"` until the customer pays.
Use `invoice.stripe_id` as a stable dedupe key. The top-level `id` field (e.g. `evt_auto_topup_...`) is a unique identifier for the event itself.
**Example payload (auto-charge):**
```json expandable
{
"type": "billing.auto_topup_succeeded",
"id": "evt_auto_topup_2abc123",
"occurred_at": 1761840000000,
"data": {
"customer_id": "user_123",
"feature_id": "credits",
"quantity_granted": 1000,
"threshold": 500,
"balance_after": 1450,
"invoice_mode": false,
"invoice": {
"stripe_id": "in_1A2B3C4D5E6F",
"status": "paid",
"total": 1000,
"currency": "usd",
"hosted_invoice_url": "https://invoice.stripe.com/i/..."
}
}
}
```
**Example payload (invoice mode):**
```json expandable
{
"type": "billing.auto_topup_succeeded",
"id": "evt_auto_topup_3xyz456",
"occurred_at": 1761840000000,
"data": {
"customer_id": "user_123",
"feature_id": "credits",
"quantity_granted": 1000,
"threshold": 500,
"balance_after": 1450,
"invoice_mode": true,
"invoice": {
"stripe_id": "in_2G3H4I5J6K7L",
"status": "open",
"total": 1000,
"currency": "usd",
"hosted_invoice_url": "https://invoice.stripe.com/i/..."
}
}
}
```
### balances.usage_alert_triggered
Fired when a customer crosses a configured usage alert threshold. Usage alerts let you monitor when customers approach or exceed specific usage levels for a feature.
| Alert Threshold Type | Description |
|---------------------|-------------|
| `usage` | An absolute usage count was crossed |
| `usage_percentage` | A percentage of the usage allowance was crossed |
**Example payload:**
```json expandable
{
"type": "balances.usage_alert_triggered",
"data": {
"customer_id": "user_123",
"feature_id": "api_calls",
"usage_alert": {
"name": "80% usage warning",
"threshold": 80,
"threshold_type": "usage_percentage"
}
}
}
```
## Setup
Configure your webhook endpoints in the Autumn dashboard:
<Steps>
<Step title="Navigate to Developer Settings">
Go to the **Developer** section in your Autumn dashboard and select the **Webhooks** tab.
</Step>
<Step title="Add an Endpoint">
Click **Add Endpoint** and enter the URL where you want to receive webhook events.
</Step>
<Step title="Select Events">
Choose which events you want to subscribe to. You can select all events or specific ones.
</Step>
<Step title="Save and Test">
Save your endpoint configuration. You can use the **Send Test Event** button to verify your endpoint is receiving events correctly.
</Step>
</Steps>
## Webhook Security
Autumn uses [Svix](https://www.svix.com/) for reliable webhook delivery. Each webhook request includes signature headers that you can use to verify the request is genuinely from Autumn:
- `svix-id` — Unique message identifier
- `svix-timestamp` — Timestamp of when the message was sent
- `svix-signature` — Signature for verifying authenticity
You can use the [Svix libraries](https://docs.svix.com/receiving/verifying-payloads/how) to easily verify webhook signatures in your application.
## Retry Policy
If your endpoint returns an error or is unavailable, Autumn will automatically retry the webhook with exponential backoff. You can view delivery attempts and retry failed webhooks from the dashboard.