Billing (Stripe, per-partner seats)¶
Billing is charged to the Partner (the MSP account), metered on active seats — machines seen in the last 30 days across all of that partner's clients. Everything is optional: with no Stripe keys set, the billing endpoints report "not configured" and nothing charges.
How it works¶
- A partner admin opens the portal; the Billing panel shows plan, status, active seats, and the
billed quantity (
max(activeSeats, 1)). - Subscribe calls
POST /v1/partner/billing/checkout, which creates a Stripe Checkout session (subscription, one per-seat price × quantity) and redirects to Stripe. - Stripe calls back to
POST /v1/stripe/webhook. The Worker verifies the signature (HMAC-SHA256 overt.rawBodywith the webhook secret, ±5 min tolerance — unit-tested) and updates the partner'sstripe_customer_id,stripe_subscription_id, andstatus(active/past_due/suspended).
Portal → /checkout → Stripe Checkout → subscription → webhook (verified) → partner.status updated
Configure¶
# In backend/wrangler.toml [vars]:
# STRIPE_PRICE_ID = "price_..." # a per-seat recurring price
# As secrets:
wrangler secret put STRIPE_SECRET_KEY --config backend/wrangler.toml
wrangler secret put STRIPE_WEBHOOK_SECRET --config backend/wrangler.toml
wrangler deploy --config backend/wrangler.toml
Then in the Stripe dashboard add a webhook endpoint pointing at
https://totlprovision-api.totlcom.com/v1/stripe/webhook, subscribed to checkout.session.completed
and customer.subscription.*. Copy its signing secret into STRIPE_WEBHOOK_SECRET.
Invoices¶
The portal Billing panel has a View invoices button that lists the partner's recent Stripe invoices
(number, status, amount, date, and a link to the hosted invoice). Endpoint:
GET /v1/partner/billing/invoices.
Seat sync¶
Checkout sets the subscription quantity once; an hourly Worker cron then reconciles each subscribed partner's Stripe quantity to its live active-seat count (retired machines excluded), so billing tracks usage between checkouts.
Notes¶
- Seat enforcement (over-limit warnings) already exists per client via the seat limit; Stripe adds the actual charge at the partner level.
- The webhook is the only unauthenticated endpoint; it is protected by Stripe signature verification, not a session.