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.
What happens at connect
- 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. - Webhook subscribes. Future
charge.succeededandinvoice.paidevents stream to Trace within seconds of happening in Stripe. - 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:
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
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 zone → Delete 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.