plan.created
A plan is first created on a project.
When this fires
A plan is created through the REST API (POST /api/v1/projects/{project}/plans) or the create_plan MCP tool, or a finished pass season is duplicated into its next season. Plans created from the creator dashboard or the Telegram bot do not emit this event. The plan is immediately eligible for purchase if it was created in the active state.
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.created",
"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",
"currency_id": "8f27a0d4-63be-4915-8c07-1a5d9e34b628",
"billing_cycle": "month",
"active": true
}
}
}Payload (season duplication)
{
"id": "evt_01HX...",
"type": "plan.created",
"created_at": "2026-05-18T10:05:00Z",
"api_version": "2026-05-01",
"project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",
"data": {
"plan_id": "b1a7c3d5-2e48-4f60-9a1b-7c5d3e820f94",
"plan_name": "Season 4",
"kind": "pass_series",
"duplicated_from_plan_id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b"
}
}The two shapes are disjoint. The API and MCP path sends data.plan and
nothing else; the season-duplication path sends the four 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.created 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 API and MCP path only; absent when a season is duplicated. |
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.currency_id | string UUID | Currency row id — not an ISO 4217 code. |
data.plan.billing_cycle | enum: day | week | month | year | lifetime | Billing cadence unit. |
data.plan.active | boolean | true if the plan is purchasable. |
data.plan_id | string UUID | Season-duplication payload only. Plan id of the new season. |
data.plan_name | string | Season-duplication payload only. Display name of the new season's plan. |
data.kind | enum: subscription | pass | pass_series | Season-duplication payload only. Always pass_series on this path, since it clones a series. |
data.duplicated_from_plan_id | string UUID | Season-duplication payload only. The plan the season was cloned from. |
billing_cycle is only the unit. The multiplier lives in billing_cycle_count on the plan record and is not included in this payload — a "3 month" plan sends billing_cycle: "month" with no indication of the 3. Nor is trial_days. Fetch the plan with GET /v1/projects/{project}/plans/{plan} when you need the full cadence or trial configuration.
Caveats
- A
plan.createdevent does not imply the plan has been synced to a payment-provider catalog. Listen forplan.sync_completedfor that. - Trial is configured at create time. A later trial-only edit is only applied — and only then fires
plan.updated— when the PATCH also carries akindkey; without it the trial fields are dropped and no event is emitted.
Related events
plan.updated— subsequent edits.plan.sync_completed— provider catalog sync.- Plan events overview — back to family overview.
How is this guide?