Skip to content

Billing & Stripe

RevTurbine reads billing truth from Stripe — the customer’s current plan, payment health, and subscription lifecycle all flow in through Stripe webhooks and land on the customer record, where decisions read them. Stripe is the supported billing provider today; others are planned. This page covers the three operational pieces: connecting the account, wiring the webhook destinations, and proving the pipeline works before real money moves.

Connection happens in the dashboard — Settings → Stripe. There are two paths, and neither involves pasting API keys:

  • Existing Stripe account — use Connect with Stripe (OAuth). You’re sent to Stripe, approve the connection, and land back in Settings with the account linked. RevTurbine receives scoped access through Stripe Connect; you can revoke it from either side at any time.
  • New Stripe account — use Set up payments (embedded onboarding). Stripe’s onboarding flow runs inside the dashboard via an account session; when Stripe finishes provisioning, the account is linked automatically.

Once connected, plan variations link to Stripe prices (stripe_price_id), the billing page gains a Sync action that compares RevTurbine’s plans against your Stripe catalog, and webhook events begin updating customer records.

RevTurbine is a Stripe Connect platform. When you connect, Stripe starts delivering your account’s events to RevTurbine’s platform-level webhook endpoints — subscription and invoice events, payment-method and person/account events — and RevTurbine verifies each one and resolves it to your tenant from the connected account it came from. There are no destinations to create in your Stripe Dashboard, no signing secrets to copy, and no URL to configure.

If billing state stops updating, the first place to look is Settings → Stripe in the RevTurbine dashboard: the connection status and the events-received summary show whether events are arriving.

Linking a Stripe customer to a RevTurbine user

Section titled “Linking a Stripe customer to a RevTurbine user”

RevTurbine identifies a user by the user.id your app passes to the SDK. To tie that user to their Stripe customer, put the same id on the Stripe Customer object as metadata.revturbine_user_id when you create it (for example, when your checkout creates the customer). RevTurbine links the two on customer.created, and uses the same key to attribute conversions to the prompts that drove them.

TypeScript
// Your checkout, when it creates the Stripe customer
const customer = await stripe.customers.create({
email: user.email,
metadata: { revturbine_user_id: user.id }, // the id you pass to the SDK
});

If a Stripe customer was created without the metadata, RevTurbine can still match it once it has seen the customer’s Stripe id — but the metadata is the explicit, authoritative join, and the one to rely on.

  • customer.subscription.created/updated/deleted → the customer’s current plan — which flows into SDK decisions automatically: the client context carries the server-derived plan handle, and hosted evaluation defaults to it when the caller doesn’t supply one.
  • invoice.payment_failed / payment_intent.payment_failed → the customer’s billing-health issues, which drive the payment_failed retention qualifier server-side (and clear again on invoice.payment_succeeded).
  • Account/person (v2) events → connected-account status shown in Settings.

Walk this once after connecting, before anything real depends on the pipeline:

  1. Fire synthetic events on your connected account. With the Stripe CLI, in test mode:

    Terminal window
    stripe trigger customer.subscription.created --stripe-account acct_…
    stripe trigger invoice.payment_failed --stripe-account acct_…
  2. Watch them land. Settings → Stripe shows an events-received summary; a triggered subscription event should move a test customer’s plan, and the payment-failure event should flip that customer’s billing health to attention_required (visible on the customer record).

  3. Run the catalog comparison. The billing page’s Sync action compares RevTurbine plans against the Stripe catalog and reports mismatched, missing, and orphaned prices — a clean report means plan↔price linkage is coherent.

  4. Confirm recovery. stripe trigger invoice.payment_succeeded should clear the billing-health issue again.

Settings → Stripe is the home for the connection itself: connected-account status, the events-received summary, and the Sync comparison. Stripe is the first integration with a settings surface; outbound analytics forwarding (PostHog — createPostHogIntegration in the SDK) is configured in code today, with a settings surface planned.

RevTurbine decides what to offer; your app owns the checkout itself. A CTA with the open_checkout action resolves to open_checkout_modal carrying the plan handle — your resolver opens Stripe Checkout (or your own flow) for the matching price. After payment, the subscription webhook updates the customer’s plan, and access follows without further app code.

Your server creates the Checkout Session, and Stripe only knows what you put on it. checkoutMetadata(outputId) returns the revturbine_* bag for the output the user clicked — the user, account, placement, decision, output, rule and target plan the SDK holds for it — so the purchase joins the presentation and click it came from with confirmed credit instead of a time-window inference. Send it to your server with the price the user chose:

TypeScript
// Browser: `output` is the decision the slot rendered (`decision.output`).
const checkout = rt.checkoutMetadata(output.output_id);
// `checkout` is null for an output this SDK did not decide.

Then put the bag on the session and on the subscription, and the user id on client_reference_id:

TypeScript
// Server: `checkout` is what the browser sent.
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: stripePriceId, quantity: 1 }],
customer: stripeCustomerId,
client_reference_id: checkout.client_reference_id,
metadata: checkout.metadata,
subscription_data: { metadata: checkout.metadata },
});
  • metadata on the session is what checkout.session.completed carries.
  • subscription_data.metadata mirrors the bag onto the Subscription, so every customer.subscription.* event carries it and every invoice snapshots it under parent.subscription_details.metadata — renewals, failed payments and recoveries stay attributed to the placement without a customer mapping.
  • client_reference_id is Stripe’s own caller-side key for the session; RevTurbine reads it as the user id when the metadata is absent.

The bag’s keys are revturbine_user_id, revturbine_account_id, revturbine_placement_id, revturbine_decision_id, revturbine_output_id, revturbine_rule_id and revturbine_plan_handle. A key the SDK does not hold is absent rather than empty — no id is ever invented — so an unidentified user yields no revturbine_user_id and no client_reference_id, and a CTA that names no plan yields no revturbine_plan_handle.