Subscriptions
The life of a buyer's subscription, from checkout to its end, and what your extension, your dashboard and your server see at each step.
A subscription is a plan Stripe bills every month or year, on your Stripe account. The buyer signs in before they subscribe, so a subscription always belongs to an account. Toolaby follows it from Stripe's events: it unlocks the tool while Stripe's status is active or trialing, and in no other status.
This page follows one subscription from checkout to its end. At each step it says what gate(), getUser() and the Toolaby popup answer, what the tool's Customers page shows, and which webhook events are sent.
Statuses
| Status | When | Unlocks | Customers shows |
|---|---|---|---|
trialing | In a free trial taken at checkout | yes | Trial until 12 Oct 2026 |
active | Paid for the current period | yes | Renews 12 Nov 2026, or Ends 12 Nov 2026 once set to end |
past_due | A charge failed, and Stripe is retrying it | no | Past due |
unpaid | Stripe stopped retrying and kept the subscription | no | Unpaid |
canceled | It has ended | no | Ended 12 Nov 2026 |
A subscription can also be Held by the plan: a pass beyond the Hobby plan's 100, which unlocks nothing until there is room. See Plans.
What the extension sees
getUser() and gate() answer from the device. The device hears of a change when it next asks Toolaby: it asks for a fresh token every 15 minutes, and the Toolaby popup asks when it opens. On Live, the popup asks once its last answer is five minutes old.
trialing, active | past_due, unpaid, canceled | |
|---|---|---|
gate() | { allowed: true, isPremium: true, unlimited: true }, with the plan's features | What the Free plan gives. With Nothing free: { allowed: false, reason: 'paid' } |
user.paid | true | false |
user.via | 'subscription' | null |
user.plan | The plan held | null |
user.paidAt | The current period's start | null |
user.subscription | { status, periodEnd, cancelAtPeriodEnd, cancelAt } | The same, with Stripe's status |
user.subscription is there whatever the status, so your popup can say why the tool is locked. periodEnd is when the current period ends: in a trial, the trial's end. cancelAtPeriodEnd is true once the subscription is set to end, and cancelAt says when. user.trial describes a trial started without a card from your website, not one taken at checkout. See User.
1. Checkout
The buyer picks the plan on the checkout page and pays on Stripe's page. A plan with a free trial takes the card and charges nothing until the trial ends. Each inbox gets one trial per tool: see Pricing.
When Stripe has made the subscription:
- It is
trialingwith a trial,activewithout one. - Toolaby sends
purchase.completed, withkind: 'subscription', andsubscription.started. Both carrystatus,period_endanduser_id;purchase.completedhas noemailoramountfor a subscription. - The members who chose sale notifications get an email. See Notifications.
- The after-purchase page signs the extension in to the buyer's account, and the device unlocks.
onPaidfires in the background. - The buyer appears under Customers.
2. The trial
A trialing subscription unlocks the tool as a paid one does. Stripe's current period is the trial: period_end, user.subscription.periodEnd and the date under Customers are the trial's end.
The Toolaby popup opens your popup. With no popup of yours behind it, it shows the plan and Renews 12 Oct · €3 / month: the date of the first charge.
When the trial ends, Stripe charges the card. Paid, the subscription is active. Refused, it is past_due (step 4). Neither sends an event.
3. Renewal
Each period, Stripe charges the card. Paid, the subscription stays active, and user.subscription.periodEnd, user.paidAt and the Renews date under Customers move on to the new period. No event is sent.
On the Hobby plan each charge carries Toolaby's fee. See Money.
4. A payment fails
A charge Stripe cannot make, such as a declined or expired card, leaves the subscription past_due while Stripe retries it. past_due unlocks nothing:
gate()answers as the Free plan does. With Nothing free, it refuses withpaid.getUser()answerspaid: false,via: nullandsubscription.status: 'past_due'.- Where the Free plan does not let the device through, the Toolaby popup says Payment failed — update your card, with the way to the account page. Where it does, as on Everything free, it opens your popup.
- Customers shows Past due, under Needs attention.
- No event is sent.
A team subscription's seats are paused while it is past due. See Seats.
When a later attempt pays the invoice, the subscription is active again, and the device unlocks at its next check. No event is sent.
When Stripe's retries run out, Stripe cancels the subscription, marks it unpaid or leaves it past_due, as your Stripe account's settings for failed payments say. Cancelled, it has ended (step 6). unpaid unlocks nothing, shows Unpaid under Customers, and sends no event.
5. Set to end
The buyer cancels from their account page, in Stripe's portal. Or you select Cancel on the customer's page, At the end of the period. The subscription stays active, and unlocks until the period ends.
getUser()answerssubscription.cancelAtPeriodEnd: true, andcancelAt, the day it ends.- The Toolaby popup, with no popup of yours behind it, shows Ends 12 Nov · €3 / month.
- Customers shows Ends 12 Nov 2026, under Needs attention. In a trial it still shows Trial until.
- Toolaby sends
subscription.cancelled, withimmediately: falseandends_at, as soon as the subscription is set to end: from Cancel, or from the portal, which records it as acancel_atdate.
subscription.cancelled is sent once a day at most: set to end, kept, and set to end again on the same day (UTC) is one event.
Keep, on the customer's page, undoes the cancellation before the period ends: the subscription renews as before. No event is sent.
6. Ended
The subscription is canceled at the end of the period it was set to end at, after Cancel with Now, or when Stripe cancels it after its retries.
gate()answers as the Free plan does.getUser()answerspaid: false,via: nullandsubscription.status: 'canceled'.- The Toolaby popup answers as for any device on the Free plan.
- Customers shows Ended 12 Nov 2026.
- Toolaby sends
subscription.cancelled, withimmediately: true;ends_atis when it ended. - A team subscription's seats are revoked. See Seats.
7. A refund
A subscription's payment is refunded on the payment in your Stripe Dashboard: Refund on the customer's page is for licences. On Test, the Stripe account Toolaby makes has no Stripe Dashboard, so a subscription's payment is not refunded there.
A refund leaves the subscription as it is. It runs on and unlocks, and no event is sent. To end it as well, cancel it (step 5 or 6).
A refund of the payment for the current period makes that period a pass: its charges no longer pay for it.
8. A dispute
A buyer who disputes a subscription's payment with their bank leaves the subscription as it is: it runs on and unlocks.
- Toolaby sends
payment.disputedwhen the dispute opens, withlicenceempty and the invoice's address inemail. - It sends
dispute.closedwhen the dispute is won or lost. - The members who chose dispute notifications get an email, with the deadline to respond.
If the disputed payment paid for the current period, that period is a pass while the dispute is open, and stays one if the dispute is lost.
Events
| Moment | Event |
|---|---|
| Subscribed, with or without a trial | purchase.completed and subscription.started |
| A trial ends, a renewal, a payment fails, a failed payment is paid | none |
| Set to end at the period's end | subscription.cancelled, immediately: false |
| Set to end, then kept | none |
| Ended | subscription.cancelled, immediately: true |
| A plan switched in Stripe's portal | none |
| A payment refunded | none |
| A payment disputed | payment.disputed, then dispute.closed when won or lost |
A server that needs to know who is paid, past due included, asks the API: Retrieve a customer answers entitled and the subscription's status, whatever it is. See Sync customers.