Stripe
Connect a Stripe account to SaaS Pro Max for read-only revenue analytics. The connector reads customers, subscriptions, charges and invoices, and keeps them current with signed webhooks. It never writes to Stripe.
See the revenue module for how the numbers are derived.
1. Create a restricted API key
Stripe Dashboard → Developers → API keys → Restricted keys → Create restricted key. Read-only is enough:
| Resource | Permission |
|---|---|
| Customers | Read |
| Subscriptions | Read |
| Charges | Read |
| Invoices | Read |
The key looks like rk_live_… (rk_test_… in a sandbox). A standard secret key
(sk_live_…) also works, but a restricted key is what you want: this integration
only ever reads.
If your subscriptions live in a connected account, create the key on the
platform account and put the account id (acct_…) in the connector's
Connected account id field. Every request is then sent with Stripe-Account.
2. Add the connector
In the console: Revenue → Connectors → Add a connector.
- Provider: Stripe
- Label: whatever names this account for your team
- API key: the restricted key from step 1
- Webhook secret: leave this until step 3, or paste a placeholder and edit it after — you need the URL first
- Reporting currency: the currency the dashboard totals in
Creating a connector requires revenue.write and a passkey verified in the last
ten minutes. The key is envelope-encrypted into the vault immediately and is
never shown again.
The connector list then shows the webhook URL:
https://<your host>/api/v1/webhooks/stripe/<connectorId>?t=<token>
3. Create the webhook endpoint
Stripe Dashboard → Developers → Webhooks → Add endpoint, paste the URL, and select these events:
customer.created
customer.updated
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
customer.subscription.paused
customer.subscription.resumed
charge.succeeded
charge.refunded
charge.failed
invoice.paid
Listening to more than you need only adds load; nothing else is read.
Reveal the endpoint's signing secret (whsec_…) and paste it into the
connector's Replace webhook secret field (Revenue → Connectors → Edit).
Rolling the secret later is safe: Stripe signs with both for up to 24 hours and
verification accepts either.
4. Backfill
Adding a connector enqueues a sync immediately; it walks the last 24 months.
After that the hourly revenue.sync job re-reads from the last successful sync
minus one day. Sync now on the connector runs one on demand.
Testing the connection
With the Stripe CLI:
stripe listen --forward-to "https://<your host>/api/v1/webhooks/stripe/<connectorId>?t=<token>"
stripe trigger customer.subscription.created
stripe listen prints its own signing secret, which is different from the
dashboard endpoint's. Use whichever one matches the deliveries you are testing.
Deliveries appear under Recent event on the connector, with the error message
when one fails. A 401 there means the signature did not verify: check that the
secret matches the endpoint and that the URL still carries its ?t= token.
What the connector reads
| Stripe object | Becomes |
|---|---|
| Customer | revenue_customers (email, name), linked to a person by email |
| Subscription | revenue_subscriptions with the normalized monthly value |
| Charge | revenue_payments, keyed on the payment intent, amount_refunded carried as refunded_cents |
| Invoice | The same payment as its charge — both resolve to the payment intent, so revenue is never counted twice |
Subscription period ends are read from items.data[].current_period_end as well
as the subscription itself, so both the pre- and post-2025-03-31 (Basil) API
shapes work and no API version is pinned.
Trial subscriptions are stored as trialing and contribute no MRR until they
convert.