plan.deactivated
Active plan flipped inactive.
When this fires
A plan is taken off sale through the REST API (POST /api/v1/projects/{project}/plans/{plan}/unpublish) or the publish_plan MCP tool; automatically when a pass series runs its last date; or when the free-plan entitlement sweep disables an unentitled zero-priced plan. The plan remains visible to existing subscribers and stays in the catalog, but new checkouts cannot select it. Deactivating from the creator dashboard does not emit this event.
Required ability
project-subscription-plan:view — token must carry this at mint time to subscribe an endpoint to this event.
Payload
{
"id": "evt_01HX...",
"type": "plan.deactivated",
"created_at": "2026-05-18T10:05:00Z",
"api_version": "2026-05-01",
"project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",
"data": {
"plan": {
"id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b",
"name": "Premium Monthly",
"price": "29.00",
"active": false
}
}
}Payload (series retired automatically)
{
"id": "evt_01HX...",
"type": "plan.deactivated",
"created_at": "2026-05-18T10:05:00Z",
"api_version": "2026-05-01",
"project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",
"data": {
"plan_id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b",
"plan_name": "Summer Season",
"reason": "series_exhausted",
"window_count": 12,
"successor_plan_id": "b1a7c3d5-2e48-4f60-9a1b-7c5d3e820f94"
}
}The two shapes are disjoint. The publish-state path sends data.plan and
nothing else; an automatically retired series sends the five flat keys and no
data.plan object. Branch on whether data.plan or data.plan_id is present
before reading any other field.
Field reference
| Field | Type | Notes |
|---|---|---|
id | string ULID | Unique event id, prefixed evt_. Use for idempotent processing. |
type | string | Always plan.deactivated for this event. |
created_at | ISO 8601 timestamp | Server-side emission time. |
api_version | string | Webhook API contract version. |
project_id | string UUID | Project this event belongs to. |
data.plan | object | Sent by the publish-state path only; absent when a series retires itself. |
data.plan.id | string UUID | Plan identifier. |
data.plan.name | string | Display name. |
data.plan.price | string | Decimal amount as a string, e.g. "29.00". |
data.plan.active | boolean | Always false for this event. |
data.plan_id | string UUID | Series-retirement payload only. The plan taken off sale. |
data.plan_name | string | Series-retirement payload only. Display name of that plan. |
data.reason | string | Series-retirement payload only. Currently always series_exhausted. |
data.window_count | integer | Series-retirement payload only. Number of passes the finished season held. |
data.successor_plan_id | string UUID, nullable | Series-retirement payload only. The next season linked on the series; null when none is set. |
Caveats
- Existing subscriptions on the plan are unaffected and continue to renew until cancelled or expired.
- For permanent removal use
plan.deleted.
Related events
plan.activated— paired transition.plan.deleted— terminal state.- Plan events overview — back to family overview.
How is this guide?