4 min read
Stripe billing
Formspring charges teams for paid plans through a Stripe subscription layer. This is a platform-level integration: one Stripe account per Formspring environment, not per team.
Distinct from the Stripe customer driver which mirrors form leads as Stripe customers per form.
What you need
- A Stripe account.
- Permission to view API keys + create webhook endpoints.
- Products + Prices created in Stripe matching the plans configured in the billing config.
Step 1 - Get API keys
- Go to https://dashboard.stripe.com/apikeys.
- Copy:
- Publishable key -
pk_test_orpk_live_. - Secret key -
sk_test_orsk_live_.
- Publishable key -
For initial setup, use test mode keys. Swap to live only when ready to charge real cards.
Step 2 - Create the webhook endpoint
The platform expects Stripe to forward subscription events to Formspring.
- https://dashboard.stripe.com/webhooks → + Add endpoint.
- Endpoint URL:
https://formspring.io/spark/webhook(the default mount path; check the billing config and routes for the active value). - Events to send: select the canonical subscription set:
customer.created
customer.updated
customer.deleted
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
customer.subscription.trial_will_end
invoice.payment_succeeded
invoice.payment_action_required
invoice.payment_failed
customer.tax_id.created
customer.tax_id.deleted
checkout.session.completed
- Add endpoint.
- On the endpoint page, reveal Signing secret -
whsec_.... Copy it.
Step 3 - Configure Formspring env
# config/services.php → stripe block
STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Currency / locale for invoice formatting
CASHIER_CURRENCY=eur # or usd, etc.
CASHIER_CURRENCY_LOCALE=de_DE
# Billing model wiring
SPARK_BILLABLE_TYPE=team
Restart the app after changing.
Step 4 - Create products and prices in Stripe
- https://dashboard.stripe.com/products → + Add product.
- Create one product per plan (
Free,Pro,Team, etc. - match theidkeys inconfig/plans.php). - For each product, add monthly and yearly prices.
- Copy the price IDs (
price_...).
Step 5 - Wire prices into config/plans.php
Open config/plans.php. Each plan entry has a stripe_monthly and stripe_yearly slot:
'team' => [
'name' => 'Team',
'stripe_monthly' => 'price_1Abc...',
'stripe_yearly' => 'price_2Def...',
// ... features, limits ...
],
After editing, clear the cached config and restart.
Step 6 - Smoke test
- Visit
/billing(or wherever the billing portal mounts). - Subscribe to a paid plan with Stripe's test card
4242 4242 4242 4242, any future expiry, any CVC. - Confirm:
- The team's
subscriptionrow appears in the database. - Stripe shows the customer + subscription.
- Webhook logs (Stripe Dashboard → your webhook → Webhook attempts) show 200 on each event.
- The team's
Where the credential lives
- Server:
.env→ loaded by the Stripe service config plus the billing config files. - Wiring:
app/Providers/SparkServiceProvider.php.
Going live
- Switch
STRIPE_*env vars from_test_to_live_. - Re-create the webhook endpoint in Live mode (Stripe separates test/live webhooks).
- Update
STRIPE_WEBHOOK_SECRETto the livewhsec_.... - Re-create products + prices in Live mode (test products don't carry over).
- Update
config/plans.phpto live price IDs. - Cache the production config.
- Run a real test charge (refund yourself afterward).
Security
- Restrict access to the Stripe Dashboard with two-factor required + workflow approvals on changes to webhook endpoints.
- The webhook secret prevents replay/forgery - never log Stripe webhook bodies in plaintext (the billing layer handles signature verification correctly).
- For multi-region: the billing layer works fine across Stripe accounts per region; Formspring isn't multi-Stripe-account out of the box.
Troubleshooting
| Symptom | Cause |
|---|---|
| Webhook attempts show 400 in Stripe | Signing secret mismatch. Re-check STRIPE_WEBHOOK_SECRET matches the endpoint's secret. |
| Webhook attempts show 419 | CSRF middleware is hitting /spark/webhook. Confirm the billing webhook route is excluded from CSRF (default config does this; verify if customised). |
| Subscription created in Stripe but not on the team | Check the application log - billing handler exceptions surface there. |
| Test card declines | Use 4242 4242 4242 4242 for clean success; other Stripe test cards trigger specific failures (https://stripe.com/docs/testing). |
Provider docs
- Stripe API ref: https://stripe.com/docs/api
- Webhook event types: https://stripe.com/docs/api/events/types