Integrations
Stripe integration
Connect your Stripe account: SaaS payments, renewals and refunds reach the campaign that brought each user, with one server call at sign-up.
Stripe bills your SaaS customers and reports each payment with a webhook. Connected to Traqio, your Stripe account brings every paid invoice, renewal and refund onto the user who signed up — so a campaign is credited with the money its sign-ups paid, month one and month twelve, with one server call at sign-up and none per payment. It is the web counterpart of the RevenueCat integration, with the same rules for refunds, test payments and duplicates.
Your Stripe, not Traqio's billing
Set up#
Report the sign-up once#
Where your backend creates the account, report it with the user id and the trackID the visitor arrived with — see the Node SDK (PHP and Python work the same way). Passing the email is optional: the SDK hashes it on your server and sends only the hash, the fallback key described below.
await traqio.signup({ userId: user.id, trackId: trackIdFromRequest(req), email: user.email, // optional — hashed locally, never sent });Put the user id in Stripe metadata#
Set
metadata.traqio_user_idto the same user id when you create the Subscription — Stripe copies a subscription's metadata onto every invoice it produces, renewals included. On the Customer works too.await stripe.subscriptions.create({ customer: customerId, items: [{ price: priceId }], metadata: { traqio_user_id: user.id }, }); // Stripe Checkout: await stripe.checkout.sessions.create({ mode: "subscription", subscription_data: { metadata: { traqio_user_id: user.id } }, // … });Create a restricted key#
Stripe Dashboard → Developers → API keys → Create restricted key. Set Customers: Read, Invoices: Read and Webhook Endpoints: Write; leave everything else at None. A live key (
rk_live_…) measures real payments; a test key (rk_test_…) lets you try the wiring with test payments only.Paste it in Traqio#
In the console: your product → Integrations → Revenue → Stripe. Traqio reads one customer and one invoice to prove the key, then creates its webhook endpoint in your Stripe account — URL
https://api.traqio.app/webhooks/stripe/<product id>— and keeps its signing secret. A key Stripe refuses is not saved, and the message names the permission to tick.No Webhook Endpoints permission?
Add the endpoint yourself in Stripe → Developers → Webhooks, with the URL shown in the console and the events listed below, tick « I added the webhook endpoint myself » and paste its signing secret (whsec_…) with the key.Make a test-mode payment#
With a test key, a test-mode payment shows in the console's Recent Stripe events as Test mode — wiring verified, with the amount Traqio parsed. It is never counted. Live payments are the ones counted; each line says which user it was credited to, or why it is unmatched.
How a payment finds its user#
Traqio tries these in order, and stops at the first that answers:
| Where | What Traqio reads |
|---|---|
| The invoice's subscription | metadata.traqio_user_id — the snapshot Stripe copies onto each invoice |
| The invoice | metadata.traqio_user_id |
| The Stripe Customer | metadata.traqio_user_id, read with the restricted key |
| The customer's email | SHA-256 of the trimmed, lowercased email, matched against the emailHash sent at sign-up — only if exactly one user has it |
A payment that none of them ties to a user is unmatched: recorded, counted in the product's revenue, credited to no campaign. A user id Traqio has not seen yet — the payment arrived before the sign-up call — is kept, and the payment joins the user when the sign-up lands.
What each Stripe event does#
| Stripe event | In Traqio |
|---|---|
| invoice.paid | a purchase — amount_paid, in the invoice's currency; a €0 invoice (trial, credit balance) moves no money |
| charge.refunded | a refund — negative revenue, its own entry against the invoice it refunds; each partial refund once |
| customer.subscription.created / updated / deleted | logged with the state change (trialing → active…): no money moves |
| Anything in test mode | logged as test mode, never counted |
- Currency: each payment is kept in the currency it was paid in, and converted to the product's reporting currency at that day's rate in the reports.
- Duplicates: a Stripe event is processed once, and a payment is keyed on its invoice id — a redelivery, or the same invoice also reported by your server with
transactionId, is one row. See Counting one purchase once. - Refunds are only taken back from payments Traqio recorded: an invoice paid before Stripe was connected was never added, so its refund subtracts nothing.
- Amounts are gross: what the customer paid, tax included; the tax share is stored, not applied. Stripe's fees are not subtracted.
Limits#
- Traqio counts invoices. A one-off Checkout payment counts when its session creates an invoice (
invoice_creation.enabled); a bare PaymentIntent with no invoice does not. - One Stripe account per Traqio product. A Stripe account that sells several products sends all their payments to each connected product, where the others' customers show as unmatched.
- Website payments are attributed and reported in Traqio, but are not sent to Meta or TikTok yet.
Frequently asked questions#
- Why a restricted key and not a Stripe Connect sign-in?
- A restricted key works on any Stripe account the moment it is created, and it is narrower: it reads customers and invoices, and may create one webhook endpoint. It cannot charge, refund or change anything. Traqio refuses a full secret key (sk_…) and stores the restricted key encrypted.
- Does Traqio write anything in my Stripe account?
- One thing: the webhook endpoint it creates for itself, described as Traqio's. Disconnecting Stripe in the console deletes it. Nothing else is written, and no money object is ever touched.
- What if a payment names no user?
- It is still revenue: it is recorded and counted in the product's total, as unmatched — it reaches no campaign. Traqio never gives a payment to a user by guess, and the Stripe events list says why each payment is unmatched.
- Do I still call track() for each payment?
- No. With Stripe connected, signup() is the only server call. If you keep a track() for a Stripe payment, pass the invoice id (in_…) as transactionId with store: "stripe": both reports land on one row.