billing.trial_ending
The creator's platform trial is about to convert and take its first payment.
project_id is always null for billing events. Fires once per milestone,
not once per trial — read hours_remaining to tell the two apart.
When this fires
The creator's own Subscriby trial crosses a reminder milestone on its way to converting. The trial collects a card at checkout and converts by itself, so this is the only advance notice a consumer gets before the first charge appears.
Milestones are configured per deployment (PLATFORM_TRIAL_REMINDER_HOURS) and fire largest-first. The default ladder is 72 hours then 24 hours before trial_ends_at, so a standard 7-day trial produces exactly two events. A milestone wider than the trial itself is skipped rather than fired at signup, so a trial shorter than 72 hours emits only the 24-hour event.
Subscriby emails the creator alongside every emission, and mirrors it to Telegram when their account is linked.
Required ability
billing:read — token must carry this at mint time to subscribe an endpoint to this event.
Payload
{
"id": "evt_01HX...",
"type": "billing.trial_ending",
"created_at": "2026-08-23T09:00:00Z",
"api_version": "2026-05-01",
"project_id": null,
"data": {
"creator_id": "cre_01HX...",
"plan_id": "pln_01HX...",
"subscription_id": "sub_01HX...",
"trial_ends_at": "2026-08-26T09:00:00Z",
"hours_remaining": 72,
"amount": "19.00",
"currency": "USD",
"amount_estimated": false
}
}Field reference
| Field | Type | Notes |
|---|---|---|
id | string ULID | Unique event id, prefixed evt_. Use for idempotent processing. |
type | string | Always billing.trial_ending for this event. |
created_at | ISO 8601 timestamp | Server-side emission time. |
api_version | string | Webhook API contract version. |
project_id | null | Always null — account-level event. |
data.creator_id | string ULID | Creator whose trial is converting. |
data.plan_id | string ULID / null | Platform plan the trial converts onto. null if the tier could not be resolved. |
data.subscription_id | string ULID | The creator's platform subscription row. |
data.trial_ends_at | ISO 8601 timestamp | When the trial converts and the charge is raised. |
data.hours_remaining | integer | Which milestone fired — 72 or 24 on the default ladder. Not the live countdown. |
data.amount | decimal string | Total that will be charged, e.g. "19.00". null only when no currency could be resolved. |
data.currency | string / null | Uppercase ISO 4217 code for amount. |
data.amount_estimated | boolean | true when amount is the plan's list price because the upcoming invoice was unreachable. |
Reading amount correctly
When amount_estimated is false, the figure comes from the upcoming Stripe invoice and already includes tax, any discount, and the transaction fees the creator's own sales metered during the trial. It is what will actually be taken.
When amount_estimated is true, Stripe could not be reached and the figure is the plan's list price. Treat it as indicative: do not reconcile against it, and do not surface it as a confirmed charge.
A creator carrying a credit grant or account balance will legitimately see "0.00" — the trial still converts, the invoice is simply covered.
Caveats
- Not once per trial. The default ladder emits twice. De-duplicate on
subscription_id+hours_remainingif your consumer must act only once. - A trial the creator cancels before it converts stops emitting — no further milestones fire and no charge is raised.
- The milestone ladder is deployment configuration, so do not hard-code
72and24. Readhours_remaining. hours_remainingis the milestone that fired, not the exact time left. The sweep runs hourly, so the true remaining time is somewhere within an hour below the value.
Related events
billing.invoice_created— the invoice this warning was about, once the trial actually converts.billing.payment_failed— fires instead if the card on file is declined at conversion.subscription.trial_converting— the subscriber-tier equivalent, for a creator's own members.- Billing events overview — back to family overview.
How is this guide?