Connect Stripe

Trace ingests every charge, refund, and subscription invoice from your Stripe account so we can join it back to attribution. We use Stripe Connect's read-only OAuth — Trace can read, never write.

One-click connect

Open your dashboard → pick a site → Settings → click Connect Stripe. You'll land on Stripe's OAuth screen, sign in to your Stripe account, and approve. We bounce you back to the Attribution tab as the backfill kicks off.

One Stripe account per site
If you sell multiple brands across separate Stripe accounts, create one Trace site per brand. Each site connects independently. There's no per-site fee — your $99/mo covers unlimited sites.

What happens at connect

  1. Backfill starts. Trace pulls the last 12 months of charges via paginated charges.list. Larger accounts take a few minutes; the dashboard updates as rows land.
  2. Webhook subscribes. Future charge.succeeded and invoice.paid events stream to Trace within seconds of happening in Stripe.
  3. Attribution joins. Each charge runs through the three-path resolver — metadata → email match → unattributed.

What Trace reads

Stripe Connect's read_only scope grants Trace:

  • Charges, including refunds and disputes
  • Invoices (for subscription renewals)
  • Customer email + metadata (for the email-fallback attribution path)
  • Subscription IDs (so we can group renewals into a customer's LTV)

What Trace cannot do

  • Cannot create, modify, or refund charges
  • Cannot view full card numbers, CVCs, or BIN ranges (Stripe never exposes these)
  • Cannot change your account settings or payouts
  • Cannot connect your Stripe account to anyone else

Custom checkout: pass the visitor ID

If you build your own Checkout Sessions (instead of using a hosted plugin), thread the visitor ID into metadata so Trace can join with 100% accuracy:

ts
import { getAttribution } from '@trace/sdk/next';

const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  metadata: await getAttribution(),  // → { trace_visitor_id: '...' }
  success_url: '...',
  cancel_url: '...',
});

Without this, Trace falls back to email matching — works fine if you collect email at checkout, but it's a fallback. SDK reference →

Refunds

Refunds are not yet auto-deducted (roadmap)
Today, when you refund a charge in Stripe, the original revenue row in Trace stays at its full amount. Refund-aware revenue is on the v2 roadmap. In the meantime, if refund volume is meaningful for your store, sum your refunds in Stripe directly and subtract from Trace's totals when reconciling.

Subscriptions

Renewals come through invoice.paid rather than charge.succeeded in some configurations. Trace handles both and de-duplicates so you never double-count an invoice that also generated a charge.

Each renewal inherits the original attribution from the customer's first charge — so a customer acquired via Reddit who renews monthly will keep showing up under reddit/postfor the lifetime of their subscription. That's how cohort LTV works.

Disconnecting

Settings → Disconnect. Existing revenue rows stay in Trace; only future charges from that Stripe account stop syncing. You can reconnect any time — Trace will resume the webhook stream from the disconnect point (no duplicate backfill).

To permanently delete a site: Settings → Danger zoneDelete site. You'll be asked to retype the domain to confirm. Deletion is immediate and irreversible — every event, identity, revenue row, and ad spend entry tied to the site is removed by foreign-key cascade. There's no soft-delete window, so export anything you want to keep first.

Multi-currency

Trace v1 is USD-only. If your connected Stripe account processes non-USD charges, those charges will still ingest, but the dashboard sums them as if they were USD — which gives you nonsense numbers. We refuse to silently FX-convert at "yesterday's rate."

If you need EUR or GBP support, that's on the v2 roadmap. For now, segment your sites by currency.