Install the tracking
Three ways in, depending on your platform. Pick one — you don't need more than one.
Shopify
Install the app. It links your Project ID automatically and activates a Web Pixel in Settings → Customer events. No code, nothing to paste.
WooCommerce
Integrations → Download plugin (.zip) → in WordPress, Plugins → Add New → Upload Plugin → activate → paste your Project ID → Save settings. Use Test connection to confirm.
The plugin tracks server-side from WooCommerce's own order hooks, so purchases are recorded even if the browser never fires.
Any other site
One snippet, pasted once in <head>. You'll find yours already filled in under Installation:
<!-- SetRoasFlow Universal Smart Tag -->
<script>
(function(w,d,s,l,i){w[l]=w[l]||function(){(w[l].q=w[l].q||[]).push(arguments)};
var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=1;j.src=i;f.parentNode.insertBefore(j,f);
w[l]('init','YOUR_PROJECT_TOKEN');
})(window,document,'script','SetRoasFlow','https://YOUR_DOMAIN/srf.js');
</script>
It's an async loader: it never blocks your page, and it weighs a few kilobytes.
What is tracked without you doing anything
Once the snippet is in, the Smart Tag watches the page and fires events on its own:
| Event | Detected from |
|---|---|
PageView | every page load |
ViewContent | product pages |
AddToCart | add-to-cart buttons and cart changes (via a MutationObserver) |
InitiateCheckout | reaching the checkout |
Purchase | order confirmation / thank-you pages |
Lead · CompleteRegistration | form submissions |
You can turn each family off in Websites → your site → Smart tracking.
When you need to write code
Automatic detection covers standard storefronts. It can't see a conversion that never appears in the DOM — a custom headless checkout, a purchase confirmed inside an iframe, a booking finalised server-side. In those cases you add one explicit call on the confirmation step:
<script>
window.SetRoasFlow('track', 'Purchase', {
value: 99.90,
currency: 'USD',
order_id: 'ORD-2026-0001'
});
</script>
order_id matters more than it looks. It's what lets us deduplicate: if the same order arrives from both the browser and your server, it's counted once. Without it you risk inflated numbers.
Installation → Manual events has ready-made examples for the common cases.
"One snippet" vs the longer examples. Installing is one snippet. The longer blocks on the Installation page are optional manual events, for sites where automatic detection can't reach the conversion. Most stores never need them — and Shopify and WooCommerce need no code at all.
Best practice #1: pass your customer ID on login
If you do only one thing beyond the snippet, do this. When a customer logs in (or is recognized as logged in), fire one event with your own stable customer ID as external_id:
<script>
// Anywhere you know who the user is — right after login, or on any page while logged in.
window.SetRoasFlow('track', 'Login', {
user_data: {
external_id: 'customer-42' // YOUR user id — from your database or session
}
});
</script>
Why this one line matters more than anything else:
- It unifies devices. Cookies identify a browser. Your customer ID identifies a person: the same
external_idfrom a phone and a laptop merges both histories into one profile — the one thing no cookie can do. - It survives everything. Safari clearing cookies, ad-blockers, a new device: the moment the customer logs in again, the profile reconnects.
- It lifts attribution on every platform.
external_idis a first-class match key for Meta, TikTok, Google and the audience syncs — it's hashed at the edge like everything else.
Send it on every event where you know the user, or just once per session — either works. If your platform has no login (pure guest checkout), skip this and rely on the email captured at checkout.
Passing customer data (worth doing)
Ad platforms match your conversions to real accounts using identifiers. The more you pass, the more conversions get attributed — this is what the match quality score on the Delivery page measures.
<script>
window.SetRoasFlow('track', 'Purchase', {
value: 99.90,
currency: 'USD',
order_id: 'ORD-2026-0001',
user_data: {
email: '[email protected]',
phone: '+15551234567',
external_id: 'customer-42'
}
});
</script>
Pass these in clear text. Hashing happens server-side, at the edge, before anything leaves for an ad platform — you never have to hash them yourself, and the raw values never reach Meta or Google.
You don't have to pass them every time. Once we know a customer, later events from the same browser are enriched automatically: a returning customer's anonymous add-to-cart goes out with the email we already had, so it can still be attributed. This only ever reuses data that was collected with consent, and never happens on an event without consent.
Check it works
- Installation → Verify installation — confirms the snippet is live and reachable.
- Load a page on your site, then open Delivery: you should see rows appear within seconds.
- Websites → your site → Test all destinations — fires a synthetic purchase to every configured platform in its test mode and reports what each one answered.
Nothing showing up? See Testing & troubleshooting.