Billing
Stripe subscriptions, plan tiers, usage limits, payment methods, and invoices.
Settings → Billing (/dashboard/settings/billing) is restricted to Organization Admins. It connects your tenant to Stripe for subscription management, usage tracking, and invoice history.
Subscription plans
Public self-serve plans (via trpc.billing.listPublicPlans):
| Plan | Typical price | Candidates / month | Highlights |
|---|---|---|---|
| TRIAL | Free | 50 (static cap) | Time-limited evaluation |
| STARTER | $499/mo | 50 (DB) / 100 (static fallback) | Core video screening, basic analytics |
| PROFESSIONAL | $1,499/mo | 200 | ATS integrations, advanced analytics, priority support |
| ENTERPRISE | $3,999/mo | Unlimited (-1) | White-label, custom workflows, dedicated CSM |
| ENTERPRISE_PLUS | Custom | Unlimited | Sales-assigned; same limits as Enterprise |
Prices and candidate limits live in subscription_plans (seeded from plan-config.ts defaults). Platform admins can override limits per plan from InsightHire Admin.
Feature highlights by plan
Starter includes: video pre-screening, automated scoring, custom questions, basic analytics, email support.
Starter excludes: ATS integrations, white-label branding.
Professional adds: ATS integrations (Greenhouse, etc.), team collaboration, advanced analytics, priority support, dedicated onboarding.
Enterprise adds: white-label branding, unlimited screenings, custom integrations, dedicated success manager, 24/7 support, SLA, SOC 2, custom MSAs.
Usage limits
getMergedPlanLimits combines DB candidatesLimit with static caps:
| Dimension | TRIAL | STARTER | PROFESSIONAL | ENTERPRISE |
|---|---|---|---|---|
| Candidates / month | 50 | 100* | 500* | Unlimited |
| Team members | 3 | 5 | Unlimited | Unlimited |
| Video minutes / month | 100 | 300 | 1,500 | Unlimited |
| Storage (GB) | 5 | 10 | 50 | Unlimited |
*Candidate cap follows subscription_plans.candidatesLimit when DB row exists (Starter 50, Professional 200 in marketing defaults).
The Usage dashboard component on the billing page shows current-period consumption vs limits. Approaching limits triggers in-app banners (trial-banner component links back to billing).
Enforcement
When limits are exceeded:
- New candidate screenings may block or warn depending on plan enforcement hooks
- Trial expiration redirects to
/dashboard/settings/billing?expired=true - ATS integration access checks plan tier (
enforceATSAccess)
Stripe integration flows
Billing uses Stripe Checkout, Customer Portal, SetupIntents, and in-app PaymentIntents.
Upgrade paths
flowchart TD
A[Admin clicks Upgrade] --> B{Existing Stripe sub?}
B -->|Yes| C[updateSubscriptionPlan — proration]
B -->|No| D{Saved payment method?}
D -->|Yes| E[subscribeWithSavedPaymentMethod]
E --> F{Requires 3DS?}
F -->|Yes| G[confirmCardPayment + finalizeInAppSubscription]
F -->|No| H[Subscription active]
D -->|No| I[createCheckoutSession → redirect]Payment methods
- Add card —
createSetupIntentopens Stripe Elements modal (AddCardModal) - Default card —
setDefaultPaymentMethod - Remove card —
removePaymentMethod(blocked if last method on active sub)
Billing portal
Manage subscription opens Stripe Customer Portal (createBillingPortalSession) for:
- Cancel at period end
- Update payment method (alternative to in-app card UI)
- View Stripe-hosted invoices
Invoices
listInvoices returns recent Stripe invoices with expandable line items on the billing page. PDF links open Stripe-hosted documents.
Query parameters
| Param | Meaning |
|---|---|
?success=true | Checkout returned successfully — refetch subscription |
?canceled=true | Checkout abandoned |
?expired=true | Trial expired banner |
Environment requirements
| Variable | Purpose |
|---|---|
STRIPE_PUBLISHABLE_KEY | Web Stripe.js |
STRIPE_SECRET_KEY | API Stripe SDK |
STRIPE_PRICE_*_MONTHLY | Price IDs for Checkout |
| Stripe webhooks | Sync subscription status → org record |
Missing publishable key shows an alert when adding cards.
Org record fields
Subscription state on organizations:
subscriptionPlan—TRIAL,STARTER,PROFESSIONAL,ENTERPRISE,ENTERPRISE_PLUSsubscriptionStatus— active, trialing, past_due, canceled, etc.- Stripe customer and subscription IDs (internal)
Webhooks keep these in sync after Checkout or portal changes.
Platform admin overrides
InsightHire platform admins can:
- Set plan without Stripe (sales-assigned Enterprise)
- Override
candidatesLimitonsubscription_plansrows - Extend trial from org detail in admin
Customer self-serve changes still flow through Stripe when a customer record exists.
Troubleshooting
| Symptom | Action |
|---|---|
| Upgrade button does nothing | Check Stripe keys; browser console for API errors |
| Past due | Update payment method; check Stripe dashboard |
| Usage seems wrong | Usage dashboard refreshes post-webhook — wait ~30s |
| ATS blocked on Professional | Confirm subscriptionPlan synced; Greenhouse requires Professional+ |
| Proration surprise | Expected on mid-cycle plan change — see Stripe invoice |
Related docs
- Settings overview — billing path
- Platform feature flags — features beyond plan tier
- Branding — Enterprise white-label

