Attribution & onsite tracking
Credit every sale to the marketing that earned it. Install the pixel, tell us who the visitor is, and send us the order — we join the ad click, the email, the browsing and the purchase into one journey.
How it fits together
Four things flow in, and they are independent — you can ship them in any order and each one improves the picture on its own.
Reads UTMs and ad click IDs off the landing URL, and records browsing. This is where attribution actually comes from.
Turns an anonymous browser into a contact. Everything they did beforehand is attached to them at this moment.
The only thing that counts as revenue. Automatic on Shopify; one POST per sale everywhere else.
Every 30 minutes, credits each paid order to the most recent marketing touch before it.
What we support
The pixel runs on any website. The difference between platforms is only ever how the order reaches us — we cannot see your sales unless something tells us about them.
Everything automatic. Connect the store and orders, refunds and cancellations arrive by webhook. Add the pixel for browsing and attribution.
Pixel works as-is: browsing, UTMs, ad click IDs and attribution are automatic. Orders need one server-side POST per sale (below).
Pixel works, so you get traffic sources and journeys. No orders unless something server-side can post them — a serverless function or your payment provider's webhook.
No direct connector today. The pixel works, and orders can be posted from your own backend using the events API.
Not supported. The pixel is a browser script. Events can be posted server-side, but there is no in-app SDK and no anonymous stitching.
Shopify is the only turnkey commerce integration today
1. Install the pixel
Your public key is in Dashboard → Onsite Tracking. It is safe in page source: on its own it can only record browsing for contacts you already have.
<script async
src="https://brainito.com/brainito-pixel.js"
data-key="YOUR_PUBLIC_KEY"
data-auto-pageview></script>Put it before </head> — in theme.liquid on Shopify, or your root layout on Next.js. Optional attributes:
data-auto-pageview— send a page view on every page automatically.data-capture-forms— read an email from forms the visitor submits, so you do not have to callidentifyyourself. Only a field that looks like an email is read, and nothing else on the form.data-anonymous— also track visitors before they identify, so a journey can include the ad click that brought them. Off by default. See consent.
Verify your domain
2. Identify your visitors
Nothing is attributed to a person until you tell us who they are. Call identify on login, on checkout, or when someone arrives from one of your emails.
// Call this as soon as you know who the visitor is.
brainito('identify', { email: 'jane@example.com' });
// Optional extras, merged rather than replaced:
brainito('identify', { email: 'jane@example.com', first_name: 'Jane' });Call it as often as you like — later calls merge rather than replace, so an identify carrying only an email will not erase a name captured earlier. On logout, call brainito('reset'): it clears the identity, the browser id and the recorded campaign, so the next person to use that computer does not inherit the previous one's journey.
3. Track browsing
Optional, but it is what makes a journey readable and what powers Browse Abandonment. Five event names are accepted from the browser: viewed_product, added_to_cart, started_checkout, viewed_page, active_on_site.
brainito('track', 'viewed_product', {
product_id: 'SKU-123',
title: 'Blue Shoes',
price: 49.0,
url: window.location.href
});
brainito('track', 'added_to_cart', { product_id: 'SKU-123', quantity: 1 });The pixel cannot record a purchase, by design
order_paid is not accepted from the browser. Your public key is visible in page source, so if the pixel could report revenue, anyone viewing source could invent it. Orders go server-side, with a secret key.4. Send orders
Shopify users can skip this. Connect the store and orders, refunds and cancellations arrive automatically.
On any other stack, post the sale from your backend. Create an API key with the events.write scope in Dashboard → API Keys.
// Server-side only. Run this where the payment confirms —
// a Stripe webhook handler, your checkout route, a queue worker.
await fetch("https://brainito.com/api/events", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BRAINITO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "jane@example.com", // links the sale to the contact
event: "order_paid",
value: 84.00, // the amount paid
properties: {
order_id: "1234", // REQUIRED — refunds match on this
currency: "GBP", // REQUIRED — omitted means skipped
},
dedup_key: "stripe:order_paid:1234", // REQUIRED — retries double revenue
}),
});Three fields that fail silently
- No
currency— the sale is skipped entirely. Revenue goes missing rather than being guessed, because a dashboard labelling GBP as dollars is worse than one that is short a row. - No
dedup_key— payment providers retry webhooks. One sale becomes two and revenue doubles. - No
order_idin properties — refunds are matched to the original order on it. Without it refunds never reverse and revenue stays permanently overstated.
Refunds
Send refunded_order with the amount refunded, not the order total. A partial refund reverses only that amount and leaves the rest credited. We never edit the original — a refund is recorded as its own negative entry, so the history stays auditable.
await fetch("https://brainito.com/api/events", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BRAINITO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "jane@example.com",
event: "refunded_order",
value: 10.00, // the REFUNDED amount, not the order total
properties: {
order_id: "1234", // must match the original order
currency: "GBP",
},
dedup_key: "stripe:refund:re_abc123",
}),
});5. Consent
Tracking visitors before they identify themselves is off by default and governed by a setting in Dashboard → Onsite Tracking:
- Only with consent (default) — nothing anonymous is recorded until your banner calls
brainito('consent', true). - Always — record without waiting for a signal. Choose this only if you have a lawful basis.
- Never — identified visitors only.
// Call from your own cookie banner.
brainito('consent', true); // visitor accepted analytics
brainito('consent', false); // withdrawn — also clears what we holdnavigator.doNotTrack is honoured as a refusal in every mode, and is never treated as agreement. You are the data controller for your visitors; anonymous records carry no personal data and are deleted after 30 days.
How credit is decided
A sale is credited to the most recent marketing touch in the 90 days before it — last-touch attribution. A touch is an ad click, an email or SMS click, a social click, or a landing carrying UTM parameters.
When there is no touch to credit, we say which kind of nothing it was rather than calling it all “direct”:
| Shown as | Means |
|---|---|
| Direct | A real touch, and it genuinely was direct traffic. |
| No recent touch | We have touches for this person, none within 90 days. |
| Unattributed | No touch at all — an imported contact, or a sale from before tracking started. |
Results refresh every 30 minutes. Revenue is never summed across currencies — you pick one, because a single figure adding GBP to USD looks authoritative and means nothing.
Known limits
Worth reading before you compare our numbers with anyone else's.
- Cross-device is not linked. Someone who browses on a phone and buys on a laptop is two separate journeys. We do not guess a connection between them.
- Safari clears local storage after about seven days of no return visit. Long consideration cycles on Safari will lose the earlier part of the journey. The same applies to private windows and cleared browser data.
- Attribution is not backdated. Sales from before you installed the pixel show as Unattributed, because the journey that produced them was never recorded.
- Ad blockers block the pixel, as they do every analytics script. Orders posted from your server are unaffected, which is another reason they go server-side.
- One model. Last-touch only today. Multi-touch models are not available.
Go-live checklist
The last line is the one that matters. Open the contact you tested with, and you should see the ad or email that brought them, the pages they viewed, and the order marked with the channel that earned it.