Billing and usage
Plans in euro, what is metered, what happens at a limit, and how Stripe keeps the plan in sync.
SignalDock bills per organization, in euro, excluding VAT. Plans and limits live
in @signaldock/domain/billing; the marketing pricing page and the app read
the same definitions.
Plans
| Plan | Price | Signals processed | Research briefs | Messages sent | Mailboxes | Audit export | SSO |
|---|---|---|---|---|---|---|---|
| Starter | €0 | 250 / month | 10 / month | 50 / month | 1 | No | No |
| Growth | €349 / month | 5,000 / month | 300 / month | 2,500 / month | 5 | Yes | No |
| Scale | Quoted | Unlimited | Unlimited | Unlimited | Unlimited | Yes | Yes |
Starter and Growth are self-serve. Scale is sold directly and invoiced in EUR.
Metering
The pipeline records one row in usage_events per metered unit:
signal_processed,research_brief, andmessage_sentcount towards the plan limits above;message_drafted,enrichment_lookup, andai_tokensare recorded for cost insight only.
Usage resets on the first of each month (UTC). Settings → Billing shows the current plan, a meter per limit, and the AI tokens used this month.
At a limit
Limits are soft. Nothing is deleted and nothing fails loudly:
- over the signal limit, signals are still ingested but not scored;
- over the research limit, research briefs pause;
- over the message limit, sequences hold their next send until the next period or an upgrade.
When an invoice is past due or a subscription is canceled, the organization falls back to Starter limits instead of stopping.
Stripe
Checkout and the customer portal are Stripe-hosted; SignalDock never sees card details. The plan changes only when Stripe confirms it through the webhook, so returning from checkout shows a short "payment received" notice until the event arrives.
The API receives Stripe events at POST /api/webhooks/stripe and verifies
the Stripe-Signature header. It handles checkout.session.completed and
customer.subscription.created, .updated, and .deleted; other events are
acknowledged and ignored.
Configuration
Set these on the API service (they are listed in turbo's
globalPassThroughEnv):
| Variable | Purpose |
|---|---|
STRIPE_SECRET_KEY | Creates checkout and portal sessions |
STRIPE_WEBHOOK_SECRET | Verifies webhook signatures; without it, webhooks are rejected |
Price ids are not env: they live in the platform_settings row
stripe.prices, for example {"growth": "price_...", "scale": "price_..."}.
Set them with the secret key (SQL editor or admin client).
Without STRIPE_SECRET_KEY, billing stays read-only: every organization is on
its stored plan and the upgrade buttons return an error.
API
| Operation | Route |
|---|---|
| Billing overview and usage | GET /api/billing |
| Start Growth checkout | POST /api/billing/checkout |
| Open the customer portal | POST /api/billing/portal |
Only organization owners and admins can start checkout or open the portal.
Last updated on