← All docs

Billing

How the hosted service charges, and how to set it up. A self-hosted deployment without STRIPE_SECRET_KEY has no billing: every organization is served, whatever its plan.

Plans

Plan What it is Served while
Trial One per inbox: the first organization of an account signed up for one, fourteen days, no card, up to ANTIBOT_TRIAL_CALLS billable calls (default 20,000) Within the fourteen days and the calls
Pro A Stripe subscription: a monthly fee with calls included, the rest metered Stripe says the subscription is active, trialing or past_due (through its retries of a failed payment)
Enterprise A contract; set by hand Always

A billable call is an identification (browser or app) or an assessment (the edge's, or your server's). Pulses, challenges, outcomes and the Management API are not counted. Calls are counted as they are made, each hour: identifications deleted afterwards (by retention, a purge or an erasure) are not taken back, and the calls of an environment deleted afterwards still count against a trial — and, for a subscription, are billed if their hours had not been reported yet (on the meter on their own, at the next run) — to that subscription only: one that ends, or another that takes over, leaves them unbilled. The Management API's usage report (GET /usage) reads the environments there are: a deleted environment's days leave it, though its calls stay counted and billed as above.

One free trial per inbox, for good. An inbox is the address as it is delivered (jane+2@ and [email protected] are one), and it starts one trial: an account's first organization of its own (an invited member making theirs, or the one made at sign-up). Every organization an account makes after that starts without one — served once it subscribes — and an account deleted and signed up again with the same inbox gets none, in this region or, with a directory, in any other. An account that has made an organization before makes no trial, and neither does a throwaway inbox, however it joined. What is kept for this is a keyed digest of the inbox (trial_claims, and the directory's directory_trials), never the address: it outlives the account, and says only that some inbox had its trial, to whoever asks about that inbox. An Enterprise organization is billed by its agreement: its owners cannot open Checkout, and no Stripe event makes it Pro.

When an organization is not served, identifications and assessments answer 402 with TrialEnded, TrialCallsUsed or SubscriptionEnded (errors). The agent reports it and earns no receipt; the edge and every server SDK read it as the API out of reach and fail open, so the customer's site keeps serving — unprotected — until an owner subscribes. Data is kept. The dashboard's Billing page (owners only) says why, and has the button.

Because nothing on the customer's own pages would show it, a trial's owners are emailed twice (lib/billing/notices.ts, run by the timers and /api/cron): three days before it ends, or once 80% of its calls are used, whichever comes first — and when it has ended. Each is sent once, whatever the number of instances. Both need mail (RESEND_API_KEY) and ANTIBOT_PUBLIC_URL for the link to the Billing page.

Setting it up in Stripe

  1. A product "Pro" with two prices:
    • a recurring monthly price — STRIPE_PRICE_PRO;
    • a metered price on a meter named antibot_calls (or STRIPE_METER_EVENT), aggregated by sum, with graduated tiers whose first tier is free up to the calls a month includes (and ANTIBOT_PRO_INCLUDED_CALLS set to the same number, for the dashboard to say it) — STRIPE_PRICE_PRO_METERED. Optional: without it Pro is the flat fee.
  2. A webhook endpoint at https://<your app>/api/billing/stripe, sending checkout.session.completed, customer.subscription.created, customer.subscription.updated and customer.subscription.deleted; its signing secret is STRIPE_WEBHOOK_SECRET. The endpoint is a 404 without it, and refuses anything not signed with it in the last five minutes.
  3. The customer portal turned on (Settings → Billing → Customer portal): cards, invoices, cancelling. The Manage billing button opens it.
  4. STRIPE_SECRET_KEY, and ANTIBOT_PUBLIC_URL (Checkout and the portal send people back to it).

Each instance reports its subscribed organizations' calls to the meter every ten minutes, an hour at a time for hours that are over (and /api/cron does it on serverless). Every call is reported — the price's free first tier is what includes them — and each hour's report carries an identifier, so one reported twice counts once. An hour is reported fifteen minutes after it ends, once every instance's counts have reached the database; calls made before a subscription began — on the trial — are not billed. One organization Stripe refuses is tried again at the next run, the others reported meanwhile.

Stripe delivers events in no particular order and retries them for days, so each is applied only if it is newer than the last one applied to the organization (its created): a late updated: active cannot serve an organization whose subscription was deleted. An event about a subscription other than the organization's own is applied only when it brings a live one to an organization without one, so the late deletion of an old subscription cannot end the new one. Checkout serves the organization once it is paid; a payment still on its way waits for its subscription's own event. A second Checkout is refused while a subscription is live, and each lasts half an hour (Stripe's shortest); and should two opened before either was paid both be paid — the first live, or its payment still on its way — the second is not written over the first: the webhook ends it at once and credits its unused time to the customer's balance, which the first one's next invoice uses (answered 500, for Stripe to send it again, if Stripe cannot be reached to end it). Whichever of a new subscription's events comes first — Checkout's or the subscription's own — the meter starts from the hour it began, so a trial's calls are never billed.

What the pricing page shows

/pricing says the trial's calls and Pro's included calls from the same settings as the limits, and the prices from ANTIBOT_PRICE_PRO_MONTHLY (default $49) and ANTIBOT_PRICE_PRO_PER_1K (default $0.50): set them to the prices in Stripe, which are what is charged.

In each region

Each data region is a stack of its own (deployment architecture), with its own webhook endpoint; the subscriptions of its organizations name it. One Stripe account serves every region: set the same prices in each stack.

What is not built

Changing the plan by hand (to Enterprise) is a database update for now; invoices, tax and dunning emails are Stripe's.