Atune Docs
Concepts

Trial, paying and subscription value

How Atune knows whether an account is a trial or a paying customer, what it pays, and how downgrades become churn signals.

Atune separates trials from paying customers because they need different help: a trial needs to reach value before it ends, a paying customer needs to keep getting value. Several signals, such as an account going quiet or missing a feature that keeps customers, only apply to paying accounts.

Atune doesn't guess who pays

Only your app knows who pays. Atune does not infer it from behavior.

  • An account is shown as a paying customer only when your app sends accountStage: "paying" for it.
  • Until then, every account is shown as a trial. This is a default, not a conclusion. The account page says Assumed next to the stage, and the Accounts list shows a notice when your app has not sent a stage for any account.

Send the stage with your events

Add the optional lifecycle fields to the events you already send to POST /api/v1/events. Send them on every event, or at least whenever they change. Invalid values are ignored and never reject the event.

{
  "accountId": "acme",
  "userId": "u_42",
  "event": "report.generated",
  "accountStage": "paying",
  "accountPlan": "Pro",
  "accountSeats": 10,
  "accountMrr": 490,
  "accountCurrency": "NOK"
}
FieldValuesMeaning
accountStage"trial" or "paying"Where the account is in its lifecycle.
trialEndsAtISO 8601 dateThe real end of the trial. Enables the trial-expiring signal.
accountPlanText, up to 100 charactersThe plan name in your product, e.g. "Pro".
accountSeatsPositive integerSeats the account pays for. Enables the underseated (upsell) signal.
accountMrrNumber ≥ 0What the account pays per month, in major units (49.5 = 49.50). 0 is kept, e.g. a free plan.
accountCurrencyISO 4217 codeCurrency of accountMrr, e.g. "NOK" or "USD".

When the account page says Reported by your app, the stage came from your app on that date.

With the SDK middleware

Return a subscription from getAccount and the middleware sends it with every captured request:

createAtuneMiddleware({
  getAccount: async (request) => {
    const session = await getSession(request);
    if (!session) return null;
    const billing = await getBilling(session.orgId);
    return {
      accountId: session.orgId,
      userId: session.userId,
      subscription: {
        stage: billing.active ? "paying" : "trial",
        plan: billing.planName,
        seats: billing.seats,
        mrr: billing.monthlyAmount,
        currency: billing.currency,
      },
    };
  },
});

Downgrades are churn signals

Atune keeps a history of these fields. Each time your app sends a stage, plan, seat count or MRR that differs from the last one, Atune records the change. The first value it receives is the starting point, not a change.

A paying account that lowers its MRR, drops seats, moves to a cheaper plan or goes from paying back to trial gets a Downgraded action in the Inbox for 14 days. Lower spend often comes before churn, so this is the moment to ask what changed. While it is open, Atune does not suggest selling that account more seats.

A plan name has no order, so a plan change counts as a downgrade only when MRR fell in the same update. Amounts in different currencies are not compared.

MRR also raises the priority of an account's actions, so bigger customers come first. The account page shows MRR and the subscription history, and the Accounts list can be sorted by MRR.

On this page