This runbook documents the planned price migration for Pro and Agency plans once the research-lookups feature ships to production. The migration is not executed here. Follow these steps manually when the feature is confirmed stable.
Summary of changes
| Plan | Current price | New price | Reason |
|---|---|---|---|
| Pro | EUR 19 / mo | EUR 49 / mo | 100 lookups / mo included; higher cost basis per enrichment credit |
| Agency | EUR 99 / mo | EUR 249 / mo | 500 lookups / mo pooled; bulk enrichment API included |
Annual prices adjust proportionally (same 17% discount):
| Plan | Current annual | New annual |
|---|---|---|
| Pro | EUR 190 / yr | EUR 490 / yr |
| Agency | EUR 990 / yr | EUR 2,490 / yr |
Grandfathering policy
Customers subscribed before the migration date keep the old price for 12 months from the date their subscription was created (not the migration date). After the 12-month window expires, their next renewal invoices at the new price. No action required from the customer; Stripe routes renewals automatically after the grandfathered price ID is deactivated.
Step-by-step
1. Create new Stripe price objects
Run the commands below in the Stripe Dashboard (live mode) or via the Stripe CLI. Do not use the seed scripts; they target test mode and would overwrite humanhours_key metadata.
# Pro monthly (new)
stripe prices create \
--unit-amount=4900 \
--currency=eur \
--recurring[interval]=month \
--product=<STRIPE_PRO_PRODUCT_ID> \
--metadata[humanhours_key]=pro_monthly_v2 \
--metadata[includes_lookups]=100
# Pro annual (new)
stripe prices create \
--unit-amount=49000 \
--currency=eur \
--recurring[interval]=year \
--product=<STRIPE_PRO_PRODUCT_ID> \
--metadata[humanhours_key]=pro_annual_v2 \
--metadata[includes_lookups]=100
# Agency monthly (new)
stripe prices create \
--unit-amount=24900 \
--currency=eur \
--recurring[interval]=month \
--product=<STRIPE_AGENCY_PRODUCT_ID> \
--metadata[humanhours_key]=agency_monthly_v2 \
--metadata[includes_lookups]=500
# Agency annual (new)
stripe prices create \
--unit-amount=249000 \
--currency=eur \
--recurring[interval]=year \
--product=<STRIPE_AGENCY_PRODUCT_ID> \
--metadata[humanhours_key]=agency_annual_v2 \
--metadata[includes_lookups]=500Each command prints the new price ID (price_...). Save all four for the next step.
2. Route new signups to the new prices
Update the four env vars in the hosting environment (Coolify or .env.local for local testing):
STRIPE_PRO_MONTHLY_PRICE_ID=price_<new_pro_monthly_id>
STRIPE_PRO_ANNUAL_PRICE_ID=price_<new_pro_annual_id>
STRIPE_AGENCY_MONTHLY_PRICE_ID=price_<new_agency_monthly_id>
STRIPE_AGENCY_ANNUAL_PRICE_ID=price_<new_agency_annual_id>
Restart the app. From this point, new signups via /billing/upgrade checkout with the new prices. Existing subscribers are unaffected.
3. Keep existing subscriptions on grandfathered prices
Do not archive or deactivate the old price objects immediately. Stripe continues renewing existing subscriptions on the price ID attached to the subscription item. The old prices stay active for at least 12 months from the migration date.
To confirm which customers are on the old prices:
stripe subscriptions list \
--price=<OLD_PRO_MONTHLY_PRICE_ID> \
--limit=100Repeat for each old price ID. Record the count for the comms plan.
4. Archive old prices after the grandfathering window closes
Twelve months after the migration date, run the following for each old price:
stripe prices update <OLD_PRICE_ID> --active=falseAny subscription still on an archived price continues to renew at the archived amount until the customer's subscription is manually updated or they cancel. Archiving prevents new checkouts from using the old price; it does not alter existing subscriptions.
If a small number of customers remain on old prices past the 12-month window, contact them individually before archiving. Do not force-migrate subscriptions without notice.
5. Comms plan
Send the following communications, timed as shown:
| When | Channel | Audience | Message |
|---|---|---|---|
| 4 weeks before migration | Email (transactional) | All active Pro and Agency customers | Research lookups are coming. Your plan price will increase in 4 weeks. Your current rate is locked in for 12 months. No action needed. |
| 1 week before migration | Email (transactional) | Same list | Reminder: price update in 7 days. Your grandfathered rate stays in place for 12 months from today. |
| Day of migration | Email (transactional) | Same list | Research lookups are now live. You have 100 (Pro) or 500 (Agency) lookups per month at no extra cost. Your current rate is locked for another 12 months. |
| 30 days before grandfathering ends | Email (transactional) | Customers whose 12-month window is closing | Your grandfathered rate ends in 30 days. From [date], your plan renews at EUR 49 / mo (Pro) or EUR 249 / mo (Agency). No action needed to stay subscribed. |
Use the existing transactional email infra (Resend / apps/web/lib/email). Template IDs to be created separately.
Rollback
If the new prices need to be pulled back before any customers have subscribed on them, update the four env vars to point back to the old price IDs and restart. No Stripe objects need to be deleted; unused prices can remain active.
If customers have already subscribed on the new prices, contact them individually to honour any rollback commitment. Do not silently downgrade subscriptions.
Related files
apps/web/app/(marketing)/pricing/page.tsx: marketing copy, updated in Phase 5 to show the new lookup quotas.apps/web/lib/api/plan-gate.ts:PLAN_FEATURES.monthly_lookupsvalues per plan (free 10, pro 100, agency 500, enterprise null).packages/db/scripts/seed-stripe-products.ts: test-mode seed only; not used in this migration.docs/STRIPE.md: general Stripe setup and webhook configuration.