Stripe

Connect your Stripe account to see revenue next to your traffic. Two clicks, read-only, no webhook setup required.

Two distinct Stripe roles

Stripe serves two purposes in Produl. First, Stripe is how Produl bills you for your own subscription — fully automatic, no configuration. Second, you can connect your Stripe account to display revenue metrics in your dashboards. This page covers the second role.

What it does

Connect Stripe once and Produl reads your charges, subscriptions, customers, and payment intents via a restricted read-only key. Revenue metrics appear on:

  • Site dashboards — a dedicated Revenue widget with MRR, ARR, new customers, churn, failed payments, revenue trend chart, top customers, and recent transactions.
  • Overview — a Revenue-today stat strip plus per-site revenue on the 1×1 cards.
  • Live map — revenue total + recent charges feed + per-site revenue panel.

Setup (2 minutes)

  1. 1

    Open the Revenue widget on your site dashboard

    Navigate to the site you want to track, find the Revenue Tracking widget (or add one via the dashboard editor), and click Create restricted key in Stripe.

  2. 2

    Produl opens Stripe with the right scopes pre-selected

    The button deep-links into Stripe's restricted-key creation form with the key name ("Produl Analytics") and all 11 required read scopes already filled in. You just review and click Create key on Stripe.

    Testing first?

    There's a test-mode link right below the button that opens the same form but on your Stripe test dashboard. Test keys start with rk_test_and only return data for test-mode charges / subscriptions.
  3. 3

    Copy the key back to Produl

    Stripe shows the key once. Copy it and paste into the Step 2field in Produl's Revenue widget, then click Connect Stripe. Produl validates the key against Stripe's /v1/balanceendpoint to confirm the scopes are correct — if anything's missing, you'll see Stripe's exact error text.

No webhooks to configure

Unlike older integrations, the pre-scoped key flow pulls live data directly from Stripe's API on every dashboard load. You don't need to register a webhook endpoint or configure event types — the connection is read-only and one-directional.

Metrics surfaced

Recurring revenue

  • MRR — Monthly Recurring Revenue. Computed by summing the unit amount of every active subscription item, normalized to a monthly figure (yearly plans ÷ 12, weekly × 4.33, daily × 30).
  • ARR — MRR × 12.
  • Active subscriptions — Count of subscriptions with status active.

Range-scoped (selectable 7d / 30d / custom)

  • Total revenue — Sum of succeeded (non-refunded) charges.
  • Transactions — Count of succeeded charges.
  • New customers — Customers created in range.
  • Churned subscriptions — Subscriptions canceled in range.
  • Failed payments — Payment intents that didn't complete (requires_payment_method, canceled, or with a last_payment_error).
  • Avg order value — Revenue ÷ transactions.
  • Revenue per visitor — Revenue ÷ unique visitors (the attribution your traffic analytics cares about).

Rankings & feeds

  • Top 5 customers by revenue in range.
  • Recent transactions — Last 10 succeeded charges with description + amount + timestamp.
  • Daily revenue timeseries — Bar chart of revenue per day, bucketed from charge timestamps.

Permissions & scopes

The pre-scoped key link requests exactly these Stripe scopes — all read-only, nothing else:

ScopeUsed for
rak_balanceValidation — proves the key can authenticate
rak_chargeTotal revenue, transactions, recent transactions
rak_customerCustomer count, top customers, new customers
rak_subscriptionMRR, ARR, active subs, churned subs
rak_invoiceSubscription invoice timing
rak_productProduct names on subscription items
rak_pricePricing info on subscription items
rak_planLegacy plan attribute on sub items
rak_payment_intentFailed payments
rak_refundRefund-adjusted revenue totals
rak_disputeChargeback tracking

Rotate or revoke anytime

The key lives only in your Stripe account — you can rotate or revoke it at any time from the Stripe dashboard. Revoking the key in Stripe immediately breaks live fetches; you'll see the webhook-fallback data until you reconnect with a new key.

Troubleshooting

"That doesn't look like a Stripe restricted key"

The key must match rk_live_... or rk_test_... followed by a long alphanumeric string. Make sure you copied the whole key, including the rk_live_ or rk_test_ prefix. Secret keys (sk_...) and publishable keys (pk_...) won't work — only restricted keys.

"Stripe rejected this key: …"

This is Stripe's own error text forwarded verbatim. Common causes:

  • Missing scopes — The key was created without one of the 11 required scopes. Delete it on Stripe and use the Create restricted key button so the scopes are filled in correctly.
  • Key revoked — Someone deleted the key from Stripe. Create a new one.
  • Wrong mode — A test key used in live mode or vice versa; Stripe will reject it.

No revenue shows up after connecting

If the dashboard is empty after a successful connection, check the range — most views default to the last 7 days, and your account might not have any charges in that window. Widen to 30 days or all time. If the Revenue widget shows a live_error note, the connection worked initially but a later fetch failed — the widget fell back to webhook data so the dashboard still renders.

Attribution (visitor → charge)

The tracker exposes getVisitorId(). Pass the returned value as client_reference_id when you create a Stripe Checkout Session and Produl will link the resulting charge back to the exact visitor who converted — including referrer, UTM, and landing page.

app/checkout/page.tsxtsx
import { getVisitorId } from 'produl-tracker/next'

async function handleCheckout(priceId: string) {
  const res = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      priceId,
      clientReferenceId: getVisitorId(),
    }),
  })
  const { url } = await res.json()
  window.location.href = url
}
app/api/checkout/route.tsts
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function POST(req: Request) {
  const { priceId, clientReferenceId } = await req.json()

  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: 'https://example.com/welcome',
    cancel_url:  'https://example.com/pricing',
    client_reference_id: clientReferenceId,     // ← Produl visitor id
    metadata: { site_id: 'YOUR_PRODUL_SITE_ID' },
  })

  return Response.json({ url: session.url })
}

Why both client_reference_id AND metadata.site_id?

client_reference_id lets Produl match the charge to the visitor. metadata.site_id tells Produl which site the revenue belongs to, since server keys are scoped per-site. Include both for full attribution.

Webhook fallback

Produl also accepts Stripe webhooks at https://app.produl.tech/api/webhooks/stripe. When events arrive with metadata.site_idset, Produl saves them and uses them as a fallback data source when the restricted key isn't set or a live fetch fails.

Webhook-sourced metrics are capped at total revenue, transaction count, and daily timeseries — MRR/ARR/customer listings require the API scopes and are zero-filled in fallback mode.

Stripe webhook events capturedtext
charge.succeeded
invoice.payment_succeeded
invoice.payment_failed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted

To enable webhooks, register the above events against https://app.produl.tech/api/webhooks/stripe. Add site_id: "YOUR_SITE_ID" to the metadata of any event you want attributed to a specific Produl site.