Revenue attribution
Stripe
Connect Stripe with a restricted read-only key and a webhook so ProductLense records charges, refunds, disputes and the exact fee on each payment.
ProductLense reads your Stripe customers, subscriptions, charges, refunds and disputes, with the fee from each balance transaction. ProductLense only reads: it never writes to your Stripe account, never refunds, cancels or changes anything, and never moves money.
What you need
| Field | Secret | Where to find it |
|---|---|---|
| Secret key | Yes | A restricted key (rk_live_...) from Developers → API keys. A full secret key (sk_live_...) also works; a restricted key is safer. |
| Webhook signing secret | Yes | The whsec_... secret of the webhook endpoint you point at ProductLense. |
Every plan includes one billing connection per site, Free too; Growth and Scale allow any number. See plans and limits.
Permissions
Create a restricted key with Read access to:
- Customers
- Subscriptions
- Charges (includes refunds and disputes)
- Balance transactions
- Invoices
- PaymentIntents
ProductLense calls GET /v1/customers, /v1/subscriptions, /v1/charges, /v1/refunds, /v1/payment_intents/{id} and /v1/invoice_payments. No write permission is needed or used.
Set up
- In Stripe, open Developers → API keys → Create restricted key and give it the read permissions above.
- In ProductLense, open your site's Settings → Billing connections → Stripe, paste the key and click Connect and import history. ProductLense makes one read call to check it.
- Copy the webhook URL from the connection card.
- In Stripe, open Developers → Webhooks → Add endpoint, paste the URL and select the events below.
- Copy the endpoint's signing secret into the Stripe connection in ProductLense.
Webhook
URL: https://api.productlense.app/api/webhooks/stripe/<connection-id> (ProductLense shows the exact URL after you save).
Events ProductLense uses:
| Event | What ProductLense does |
|---|---|
charge.succeeded | Records the charge with its fee |
charge.refunded | Records the refunds on the charge |
refund.created, refund.updated, charge.refund.updated | Records or updates the refund |
charge.dispute.created | Records a chargeback with the dispute fee |
customer.created, customer.updated | Updates the customer and its metadata |
customer.subscription.created, customer.subscription.updated, customer.subscription.deleted | Updates the subscription |
invoice.paid | Links the subscription to its charge |
Verification: the Stripe-Signature header (timestamp plus HMAC-SHA256 with your signing secret). Deliveries older than 5 minutes are rejected.
What ProductLense reads
- Customers: email, name, country and metadata.
- Subscriptions: plan, interval, amount and status.
- Payments: successful charges, linked to their invoice and subscription.
- Refunds and chargebacks: refunds from the refunds API (fetched by charge when not embedded), and disputes recorded as chargebacks.
- History: the last 12 months on first connect, then a nightly reconcile at 03:00 UTC from the last sync minus 48 hours.
How fees are derived
The fee is balance_transaction.fee, what Stripe kept on that payment. Stripe reports it in your settlement currency, so ProductLense converts it to the charge currency with the balance transaction's exchange rate. Refunds carry the refund's balance transaction fee, and chargebacks carry the dispute fee. The fee is exact, not estimated. When Stripe returns no balance transaction for a payment, its fee is recorded as 0.
Match payments to visitors
ProductLense reads the user id from the customer's metadata, in this order: pl_user_id, user_id, userId, uid. Use the same id you pass to identify. A ProductLense visitor id in pl_visitor_id or visitor_id is also accepted. Without either, ProductLense falls back to the customer email, matched to the email trait sent with identify, or to a user id that is that email.
const customer = await stripe.customers.create({
email: user.email,
metadata: { pl_user_id: user.id },
});