Skip to content

Stripe

Stripe takes the payment and keeps the subscription, so RoutingCat hears of both from Stripe. Connecting your Stripe account installs RoutingCat’s Stripe app: RoutingCat then receives Stripe’s events and turns each into the outcomes it makes, with no webhook code of yours. Your part is the checkout: it carries the visitor id, so that each payment joins the visit that led to it. This guide covers both. It builds on Track conversions.

Connect your Stripe account

The site’s owner connects Stripe once: with npx routingcat connect stripe, or with Connect Stripe under Payment connections in the site’s settings on the dashboard. Either opens Stripe’s page for RoutingCat’s Stripe app, where you install it and choose the account whose payments the site counts: your live account, or a sandbox.

$ routingcat connect stripe --site lumen
Opened in your browser: install RoutingCat in Stripe and choose the account whose payments lumen should count. Until 2026-10-17 10:28Z:
https://marketplace.stripe.com/apps/install/link/com.routingcat.outcomes?redirect_uri=https%3A%2F%2Fhooks.routingcat.com%2Fv1%2Finstall%2Fstripe&state=q3Zt8kL0wXc5Vb7Nm2Hj4Gf6Ds9Ap1Qe3Rt5Yu7Io0P
✓ stripe connected acct_1NxYbQ2eZvKYlo2C (live): RoutingCat records its payments from now, and reads back the last 30 days
  • The link lasts 30 minutes. The command waits 50 seconds for you to come back from Stripe; run again, it goes on with the same link. Until you choose an account, doctor says the connection waits for you.
  • A new connection reads back the account’s last 30 days, so recent payments are recorded too.
  • The app only reads. It changes nothing in your Stripe account. What RoutingCat collects says what RoutingCat keeps of what it reads.
  • A sandbox’s payments stay in preview, out of your results: connect one to try a checkout end to end, then run npx routingcat connect stripe again and choose your live account to count real payments.

npx routingcat connections lists the connection with its latest delivery. Once it is in place, webhook code of yours that tracks Stripe’s payments with RoutingCat can go: npx routingcat status says when it repeats the connection.

Pass the visitor id at checkout

Stripe’s events carry no visitor cookie, so the checkout carries the visitor id instead. Where your server creates the Checkout Session, read the rc_vid cookie and put its value in the metadata of the session and of what it creates.

Your server creates each Checkout Session with Stripe’s API, in whatever language it is written, then sends the buyer to the session’s url. The session’s parameters:

Parameter Value
metadata { "rc_vid": <the rc_vid cookie's value> }, only when the request carries the cookie
payment_intent_data.metadata The same, in payment mode
subscription_data.metadata The same, in subscription mode
success_url The page the buyer comes back to, with rc_return=stripe in its query

The visitor id is the request’s rc_vid cookie, which RoutingCat sets on your registrable domain, so an app on a subdomain reads it too. Laravel empties a cookie it didn’t encrypt, so init leaves rc_vid out of its cookie encryption, as Track conversions says.

  • metadata, on the session and on what it creates: payment_intent_data.metadata in payment mode, or subscription_data.metadata in subscription mode. Each of Stripe’s events names a different object, and each then carries the visitor.
  • rc_return=stripe on success_url. The buyer’s return from Stripe’s checkout is then a return, not a new arrival, so sources still credits the purchase to where the visit came from.
  • Where the buyer is signed in, put their account id in the same metadata, as account_id, and tell the connection where it is: npx routingcat connect stripe --account-key account_id, when you connect or afterwards. The purchase then joins through the account too, as conversions do. Without it, RoutingCat names Stripe’s customer, as stripe:<customer id>.

A Payment Link or a pricing table takes no metadata, but it takes a client_reference_id, which RoutingCat reads the visitor from. Append ?client_reference_id= and the visitor id, the rc_vid cookie’s value, to a Payment Link’s address, or give it to a pricing table as its client-reference-id attribute, where you haven’t set one already; the id fits Stripe’s rule for that field. A page can add it to a link as the buyer clicks, as RoutingCat’s demo store does:

public/buy.js
// A Payment Link takes the visitor id from its address, as `client_reference_id`, so the payment
// counts toward the variants the buyer saw (docs: guides/stripe.md).
document.addEventListener('click', (event) => {
const link = event.target.closest('a[data-buy]')
const visitor = document.cookie.match(/(?:^|; )rc_vid=([^;]+)/)?.[1]
if (link && visitor) link.href = `${link.dataset.buy}?client_reference_id=${visitor}`
})

Its links keep the Payment Link’s address in data-buy, so each click builds the address afresh.

What the connection tracks

Outcome When
purchase A one-time payment, or a subscription’s first paid invoice above zero, after a trial included, marked after_trial
renewal Each later paid invoice of a subscription, an upgrade’s proration included
trial_started A subscription that starts with a trial, its plan as its items
cancel_scheduled, cancel_effective A subscription set to end, and one that has ended
refund A refund that succeeded, as a negative value, or reversed
dispute A dispute’s opening, its funds withdrawn or reinstated, and its end

Values are in major units, with tax and shipping left out. Each outcome’s time is when the buyer acted, so a late delivery still counts when it happened. Each payment counts once, whichever of Stripe’s events report it.

A trial’s start carries no amount, since nothing is paid yet, and names its plan instead: its items are its subscription’s prices, stripe:price_…, each with its quantity and what one unit charges per period once the trial ends, the price’s amount as you set it in Stripe, before discounts, in the subscription’s currency. It leaves out a price that charges by usage, by tiers or by the package, or in another currency than the subscription’s, since such a price charges no fixed amount per unit: a plan of a base price and its usage names the base price, and a trial whose subscription has no other price names no items.

Declare in outcomes the ones you want reported, purchase at least. Refunds and disputes need no declaring: they correct a purchase, and results link each to the purchase it names, as counts and signed values per currency, with revenue net of refunds.

Choose what counts as the conversion

The objective counts within its window: 24 hours after the visitor first saw the page, by default. A payment at a trial’s end comes days or weeks after the visit: the default 24-hour window misses it, and a longer window, up to the visit’s length, delays learning by as much. So a SaaS with trials makes sign-up or trial start its objective, and tracks purchase beside it as an outcome: counted and reported with its revenue, without steering routing.

Where your pages can change which plan people choose, give the objective a value, so that routing learns what each conversion is worth. A purchase within the window is then worth its amount, 'amount', or its plan’s lifetime value through its price’s id, as { items: { 'stripe:price_m': 120, 'stripe:price_y': 400 } }. A trial’s start names its plan by the same ids, so a table values each trial by what a trial of its plan is worth to you, the plan’s lifetime value times the share of its trials that pay, as trial_started: { items: { 'stripe:price_m': 30, 'stripe:price_y': 120 } }, and otherwise values a plan the table leaves out at a share of its price. A trial names its plan’s prices charged per unit at a fixed amount and leaves out those charged by usage or by tiers, so a plan with none of the first names no items: give its trials a number instead, such as the plan’s lifetime value times the share of trials that pay. An amount counts in the value’s unit alone: a payment in another currency is a conversion of unknown value.

Next

RoutingCat routes each visitor of your site to the variants that convert.