Past op je bestaande stack
Advertentiekosten halen we op via de API's van de platforms, conversies uit je site, shop en CRM. Zo staat naast elke bron niet alleen het aantal, maar ook de kosten en de opbrengst.
Conversietypes
Alles wat geld oplevert, telt mee
Niet alleen de checkout. Ook het telefoontje op dinsdagochtend en de deal die drie weken later in je CRM landt.
Formulieren
Offerte, contact, download
Reserveringen
Restaurant, afspraak, tafel
Telefoontjes
Telefoonklik of bevestigd gesprek via call tracking
E-mails
Klik op een e-maillink, geen ontvangen e-mail
Webshoporders
Met geregistreerde orderwaarde
Offline deals
CRM-import van gesloten omzet
Integraties
Koppelt met je stack, niet andersom
Kostendata uit de advertentieplatforms, conversies uit je site en shop. Samen in één rapportage met echte ROAS per bron.
Advertentiebronnen en analytics
- Google Ads
- Meta Ads
- LinkedIn Ads
- TikTok Ads
- Microsoft Ads
- GA4
Webshop- en websiteplatforms
- WordPress
- Shopify
- WooCommerce
- Google Tag Manager
- Magento
- Webflow
- Wix
- Squarespace
- Eigen code (JS + API)
Installatie per platform
Plaats eerst de basispixel. Richt daarna je conversies en waarden in en controleer ontvangst. De stappen hangen af van je platform, toegang en betaalproces; soms is hulp van een ontwikkelaar nodig.
WordPress
Afhankelijk van platform en toegangScript in de <head> van je thema, via de themabewerker of het 'header scripts'-veld van je thema.
- 1Log in op je beheeromgeving via jouwdomein.nl/wp-admin.
- 2Klik links in het zwarte menu op Weergave (Appearance) → Thema-bestandsbewerker (Theme File Editor).
- 3Kies rechts in de lijst 'Themabestanden' het bestand Thema-header (header.php). Zie je die lijst niet, dan gebruik je stap 6.
- 4Zoek in de code de regel </head> - meestal rond regel 20 tot 40. Tip: gebruik Ctrl+F (Cmd+F op Mac) in de editor.
- 5Zet je cursor op de lege regel vlák bóven </head>, plak het script en klik onderaan op Bestand bijwerken (Update File).
- 6Werk je met Elementor, Divi, Astra of een child-theme? Ga dan naar Weergave → Aanpassen → Extra CSS/Head-code, of naar de thema-instelling 'Header scripts' / 'Custom code' en plak het script daar in het HEAD-veld.
- 7Geen toegang tot code? Installeer de plugin 'WPCode' of 'Insert Headers and Footers', ga naar Code Snippets → Header & Footer, plak het script in het vak Header en klik op Save.
Controleren of het werkt
- • Open je website in een privévenster en bezoek twee pagina's.
- • Ga in Roots naar Websites & pixel: controleer of een echt bezoek is ontvangen en de pixel als geverifieerd staat.
Let op
- • Plak het script niet in footer.php - click-ID's uit advertenties worden dan te laat gelezen.
- • Cachingplugins (WP Rocket, LiteSpeed, W3 Total Cache) serveren de oude pagina: leeg de cache na het opslaan.
Bij een thema-update kan header.php overschreven worden. Gebruik daarom bij voorkeur een child-theme of de WPCode-plugin.
Shopify
Afhankelijk van platform en toegangBasispixel in theme.liquid; aankopen via een Custom Pixel in Customer events.
- 1Open Shopify → Online Store → Themes → actief thema → Edit code → Layout → theme.liquid. Plaats de basispixel vóór </head> en sla op.
- 2Ga voor aankopen naar Settings → Customer events → Add custom pixel. Geef de pixel een naam, plak de Shopify-code hieronder en vervang sr_xxx door je pixelsleutel.
- 3Stel de analytics-toestemming in volgens je Shopify privacyinstellingen en verbind de Custom Pixel. De adapter gebruikt checkout_completed en Shopify browser.cookie, niet het window van je winkelpagina.
- 4De adapter stuurt order-id, bedrag en valuta. Zonder geregistreerde Roots-advertentietoestemming verstuurt hij geen e-mail of telefoon en activeert hij geen platformdoorgifte.
- 5Gebruik daarnaast de order-webhook voor serverbevestiging. Configureer de Roots-webhooksleutel en Shopify HMAC-handtekening via een beveiligde serverkoppeling.
const ROOTS_KEY = "sr_xxx";
const ROOTS_ENDPOINT = "https://rootsmetrics.com/api/public/collect";
let privacyStatus = init.customerPrivacy;
const privacyApi = typeof api !== "undefined" ? api.customerPrivacy : customerPrivacy;
privacyApi.subscribe("visitorConsentCollected", (event) => {
privacyStatus = event.customerPrivacy;
});
// Self-contained adapter inside Shopify's sandbox, not the storefront window.
const srAttribution = {
async track(name, { value, currency, order_id, email, phone }, event) {
if (!privacyStatus.analyticsProcessingAllowed) return;
const visitor = await browser.cookie.get("sr_vid");
const url = new URL(event.context.document.location.href);
const response = await fetch(ROOTS_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
key: ROOTS_KEY,
visitor_id: visitor || event.clientId,
event_type: "conversion",
event_name: name,
value_cents: Math.round(Number(value) * 100),
currency,
external_id: "order:" + order_id,
dedup_id: "order:" + order_id,
page_url: url.origin + url.pathname,
consent_state: "unknown",
advertising_consent: false
// Shopify permission is not a recorded Roots advertising consent.
// Email and phone are intentionally not transmitted by this adapter.
})
});
if (!response.ok) throw new Error("Roots purchase was not accepted: " + response.status);
}
};
analytics.subscribe("checkout_completed", async (event) => {
const checkout = event.data.checkout;
if (!checkout.order?.id) return;
await srAttribution.track("purchase", {
value: checkout.totalPrice.amount,
currency: checkout.currencyCode,
order_id: checkout.order.id.split("/").pop(),
email: checkout.email,
phone: checkout.phone
}, event);
});Controleren of het werkt
- • Controleer een bezoek op je gepubliceerde winkeldomein in Roots. Alleen dit bevestigt de basispixel, nog niet de aankoopmeting.
- • Doe een Shopify-testbestelling en vergelijk order-id, bedrag en valuta in Roots. Controleer dat pixel en webhook dezelfde order niet dubbel tellen.
Let op
- • Shopify Custom Pixels draaien in een sandbox. De storefront srAttribution is daar niet beschikbaar; gebruik de volledige adapter hieronder.
- • Additional scripts is uitgefaseerd. Gebruik het alleen bij oude winkels waar Shopify dit nog aanbiedt, niet voor nieuwe installatie.
- • Checkout op een ander domein vereist een toegestane herkomst bij je Roots-website. Zonder bezoekerskoppeling blijft de bron mogelijk onbekend.
checkout_completed kan ontbreken als de bedankpagina niet laadt. Een geauthenticeerde order-webhook is een aanvullende serverbevestiging, geen vervanging voor toestemming.
Magento / Adobe Commerce
Afhankelijk van platform en toegangScript via Content → Design → Configuration in de HTML Head van je storeview.
- 1Log in op je Magento-adminpaneel (meestal jouwdomein.nl/admin).
- 2Klik links in het menu op Content → onder 'Design' op Configuration.
- 3Je ziet nu een tabel met Default, Website en Store View. Klik bij de storeview waarvoor je wilt meten rechts op Edit.
- 4Klap de sectie HTML Head open (klik op de titel).
- 5Plak het script in het veld 'Scripts and Style Sheets' - onderaan bestaande regels, niet ertussen.
- 6Klik rechtsboven op Save Configuration.
- 7Ga naar System → Tools → Cache Management, vink Page Cache en Configuration aan, kies Refresh en klik op Submit.
Controleren of het werkt
- • Open je storefront in een privévenster en klik naar een categoriepagina.
- • Controleer in Roots bij Test je tracking of er pageviews binnenkomen.
Let op
- • Bewerk je Default in plaats van de storeview, dan meet je ook testomgevingen mee.
- • Vergeet de cache-refresh niet: zonder dat blijft de oude head-HTML uitgeleverd.
Webflow
Afhankelijk van platform en toegangScript in de site-brede custom code head van je project.
- 1Open je project in de Webflow Designer.
- 2Klik linksboven op het Webflow-logo → Site settings (of ga vanuit je dashboard naar het project → Settings).
- 3Klik bovenin op de tab Custom code.
- 4Plak het script in het bovenste vak: 'Head code' (staat boven 'Footer code').
- 5Klik rechtsboven op Save changes.
- 6Klik daarna rechtsboven op Publish → selecteer je domein(en) → Publish to selected domains.
Controleren of het werkt
- • Open je gepubliceerde site (niet de .webflow.io preview in de Designer) en klik één pagina door.
- • Verstuur een testformulier: Roots registreert form submits automatisch als conversie.
Let op
- • Custom code werkt niet in de Designer-preview - je moet publiceren.
- • Site-brede head code vereist een betaald Site plan.
Wix
Afhankelijk van platform en toegangScript als custom code op alle pagina's, geladen in de head.
- 1Ga naar manage.wix.com en kies je site.
- 2Klik links onderaan op Settings (Instellingen).
- 3Scroll naar het blok 'Advanced' en klik op Custom code.
- 4Klik rechtsboven bij 'Head' op + Add Custom Code.
- 5Plak het script in het grote codevak en geef het de naam 'Roots pixel'.
- 6Kies bij 'Add Code to Pages' de optie All pages en zet 'Load code on each new page' aan.
- 7Kies bij 'Place Code in' de optie Head en klik op Apply.
- 8Klik rechtsboven in de editor op Publish als je site nog wijzigingen heeft openstaan.
Controleren of het werkt
- • Open je live site (jouwdomein.nl, niet de editorweergave) in een privévenster.
- • Controleer in Roots of 'Script geplaatst' groen wordt.
Let op
- • Custom code vereist een premium Wix-plan en een gekoppeld domein.
- • Wix Studio? Daar zit custom code onder Settings → Custom code, verder identiek.
Squarespace
Afhankelijk van platform en toegangScript via code injection in de header van je site.
- 1Log in en open je site in de Squarespace-beheeromgeving.
- 2Klik in het linkermenu op Settings → Developer tools (bij versie 7.1: Website → Website tools).
- 3Klik op Code injection.
- 4Plak het script in het bovenste veld met de kop Header.
- 5Klik linksboven op Save.
- 6Heb je een webshop? Plak het script ook in het veld 'Order confirmation page' met srAttribution.track("purchase", { value: {orderGrandTotal} }).
Controleren of het werkt
- • Open je site in een privévenster en bekijk twee pagina's.
- • Vul een formulierblok in en check in Roots of de conversie verschijnt.
Let op
- • Code injection is beschikbaar vanaf het Business-plan; op Personal werkt het niet.
- • Code injection laadt niet in de Squarespace-editorweergave.
WooCommerce
Afhankelijk van platform en toegangBasispixel via WPCode of functions.php; purchase op de bedankpagina via woocommerce_thankyou.
- 1Plaats de basispixel via WPCode → Header & Footer → Header, of gebruik de wp_head-hook uit het PHP-voorbeeld hieronder. Gebruik één van beide, niet allebei.
- 2Voeg de PHP-code voor woocommerce_thankyou toe via een child theme of een PHP-snippet in WPCode. Gebruik je eigen pixelsleutel.
- 3Het purchase-event wordt alleen voor een betaalde order verstuurd, met orderwaarde, valuta, order-id en facturatie-e-mail. Roots verzendt contactgegevens alleen bij geregistreerde advertentietoestemming.
- 4Maak voor serverbevestiging een beveiligde orderkoppeling naar /api/public/order met x-roots-platform: woocommerce en x-roots-secret. Bewaar sr_vid in ordermeta zodat bezoek en order te koppelen zijn.
- 5WooCommerce → Settings → Advanced → Webhooks biedt order.created/order.updated. Een serverrelay kan x-roots-secret toevoegen en betaalstatus filteren; de native WooCommerce-signature vervangt de Roots-sleutel niet.
// PHP: child theme functions.php or a PHP snippet in WPCode.
add_action('wp_head', function () {
echo '<script async src="https://rootsmetrics.com/sr.js" data-key="sr_xxx"></script>';
});
add_action('woocommerce_thankyou', function ($order_id) {
$order = wc_get_order($order_id);
if (!$order || !$order->is_paid()) return;
$payload = wp_json_encode([
'value' => (float) $order->get_total(),
'currency' => $order->get_currency(),
'orderId' => (string) $order->get_id(),
'email' => $order->get_billing_email(),
'phone' => $order->get_billing_phone()
], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
echo '<script>window.addEventListener("load", function () {
if (window.srAttribution) srAttribution.track("purchase", ' . $payload . ');
});</script>';
});Controleren of het werkt
- • Plaats een betaalde testorder en controleer purchase, order-id, bedrag en valuta in Roots.
- • Herlaad de bedankpagina en controleer dat dezelfde order één keer telt. Test ook de serverkoppeling afzonderlijk.
Let op
- • Bij vertraagde betalingen is de order op de bedankpagina mogelijk nog niet betaald. Gebruik dan de betaalstatus-webhook via je serverkoppeling.
- • Zet webhooksleutels nooit in browsercode. Stem caching en betaalstatus af met je ontwikkelaar.
Google Tag Manager
Afhankelijk van platform en toegangBasispixel als Custom HTML-tag op All Pages; eigen conversies via een dataLayer-event.
- 1Maak in GTM een Custom HTML-tag met de basispixel en trigger All Pages. Vervang sr_xxx door je pixelsleutel.
- 2Zet voor deze Roots-tag geen extra consent-trigger of aanvullende consentvereiste aan: sr.js verwerkt toestemming zelf. Dit geldt niet voor andere advertentietags. Je cookiebanner blijft vóór GTM laden.
- 3Maak Data Layer Variables roots_name, roots_value en roots_currency en een tweede Custom HTML-tag met de conversiecode hieronder. Trigger: Custom Event roots_conversion.
- 4Laat je website roots_conversion pushen nadat de reservering of conversie werkelijk is bevestigd. Gebruik niet alleen een knopklik. Publiceer de container na controle.
<!-- Custom HTML: All Pages, no additional consent requirement.
sr.js handles consent. Keep your CMP before GTM. -->
<script async src="https://rootsmetrics.com/sr.js" data-key="sr_xxx"></script>
<!-- Push ONLY after a confirmed conversion, not a button click. -->
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({event: "roots_conversion", roots_name: "reservering", roots_value: 84.5, roots_currency: "EUR"});
</script>
<!-- Second Custom HTML tag: Custom Event trigger roots_conversion.
Create Data Layer Variables roots_name, roots_value, roots_currency.
Tag sequencing: load the Roots base tag first. -->
<script>
(function () {
var name = {{roots_name}}, value = {{roots_value}}, currency = {{roots_currency}};
var attempts = 0;
function send() {
if (window.srAttribution) srAttribution.track(name, {value: value, currency: currency});
else if (++attempts < 100) setTimeout(send, 100);
}
send();
})();
</script>Controleren of het werkt
- • Controleer in GTM Preview dat de basistag één keer laadt en de conversietag alleen na roots_conversion afgaat.
- • Controleer in Roots het event en de waarde; test ook weigeren van toestemming.
Let op
- • Gebruik geen tweede purchase-tag voor een standaard ecommerce purchase-event: sr.js leest dit al uit de dataLayer en ontdubbelt op order-id.
- • Een afgevuurde GTM-tag bewijst nog niet dat Roots het event heeft ontvangen.
Eigen website of app
Afhankelijk van platform en toegangScript vlak voor de sluitende </head>-tag in je gedeelde template of layout.
- 1Open het bestand dat op elke pagina wordt geladen: index.html, base.html.twig, layout.blade.php, _document.tsx, app.html of je root-layoutcomponent.
- 2Plak het script als laatste regel vóór </head>, zodat click-ID's al bij de eerste render worden vastgelegd.
- 3Gebruik je React, Vue of Svelte met SSR? Zet het script in het HTML-template, niet in een component - zo laadt het ook op de eerste pageview.
- 4Commit en deploy je site.
- 5Eigen conversies stuur je met srAttribution.track("naam", { value: 89.5, currency: "EUR" }) op het moment dat de conversie slaagt.
- 6Server-side conversies (refunds, offline orders, telefonische boekingen) stuur je via de conversie-API met dezelfde pixelsleutel.
Controleren of het werkt
- • Open je site en controleer in de netwerktab van je browser dat sr.js met status 200 laadt.
- • Gebruik in Roots de pagina 'Test je tracking' om een testevent te sturen en de eventstroom live mee te kijken.
Let op
- • Laad het script niet via een tag manager met consent-vertraging als je click-ID's wilt bewaren; plaats het direct in de head.
- • Zet het script niet in een component die pas na hydration mount - de eerste pageview mis je dan.
Order-webhook en conversie-API
Shopify, WooCommerce en Magento kunnen orders vanaf hun server bevestigen via POST /api/public/order?key=sr_xxx. Gebruik x-roots-secret en x-roots-platform. Sleutels vind je na inloggen bij je website; bewaar ze alleen op je server.
Shopify vereist bovendien X-Shopify-Hmac-SHA256 over de ongewijzigde requestbody. Sla het Shopify-geheim op in Roots en gebruik een serverkoppeling die ook de Roots-sleutel meestuurt. Voor WooCommerce en Magento moet de serverkoppeling authenticatie en betaalstatus bewaken.
curl -X POST "https://rootsmetrics.com/api/public/order?key=sr_xxx" \
-H "Content-Type: application/json" \
-H "x-roots-secret: YOUR_WEBHOOK_SECRET" \
-H "x-roots-platform: woocommerce" \
-d '{"id":1234,"total":"84.50","currency":"EUR","billing":{"email":"customer@example.com"},"meta_data":[{"key":"sr_vid","value":"VISITOR_ID"}]}'Gesloten deals en gekwalificeerde leads
De conversie-API accepteert POST /api/public/v1/offline-conversions met een accountgebonden API-sleutel als Bearer-header, site_id en maximaal 500 conversies per verzoek. Gebruik een unieke external_id, werkelijke waarde en oorspronkelijke occurred_at. visitor_id koppelt de conversie waar mogelijk aan een gemeten bezoek.
curl -X POST "https://rootsmetrics.com/api/public/v1/offline-conversions" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"site_id":"YOUR_SITE_UUID","conversions":[{"external_id":"crm-deal-1234","visitor_id":"VISITOR_ID","event_name":"qualified_lead","value":84.5,"currency":"EUR","occurred_at":"2026-10-29T12:00:00Z"}]}'Ontvangst is geen bewijs van bezoekerskoppeling. Onbekende toestemming wordt niet toegestaan gemaakt; doorgifte vereist geregistreerde toestemming. GET /api/public/v1/conversions is een export, geen ontvangstendpoint.
Rootboard partner-API
Rootboard kan met een accountgebonden partnersleutel attributietotalen ophalen per website, periode, kanaal en model via GET /api/public/partners/rootboard/attribution. De uitwisseling omvat conversiecredit, waarde in centen en gekoppelde kosten. Dit is geen ingebouwde Analytics Hub of uitwisseling van klantprofielen.
Wat wordt automatisch gemeten?
Zodra het script op je site staat, meet Roots de belangrijkste conversies zonder dat je iets instelt. Alles wat een waarde nodig heeft, geef je mee met één extra attribuut op de knop, link of het formulier.
| Event | Wanneer | Waarde |
|---|---|---|
Paginaweergave pageview | Bij elke pagina die laadt. Hier worden ook de click-ID's (gclid, fbclid, ttclid, li_fat_id) en utm-parameters vastgelegd. | Geen waarde |
Formulier verzonden form_submit | Zodra een bezoeker een <form> op je site verstuurt. Werkt voor elk formulier, ook die van je thema of pagebuilder. | Geen waarde, tenzij je data-sr-value op het formulier zet. |
Telefoonklik phone_call | Klik op een link die begint met tel: - bijvoorbeeld <a href="tel:+31612345678">. | Geen waarde. Wil je een gemiddelde waarde per belletje? Zet data-sr-value op de link. |
E-mailklik (mailto) email_click | Klik op een link die begint met mailto:. | Geen waarde, tenzij je die zelf meegeeft. |
Elke knop of link met data-sr-event eigen naam | Klik op een element met het attribuut data-sr-event. De waarde van dat attribuut wordt de eventnaam - bijvoorbeeld reservering, whatsapp of download. | Optioneel via data-sr-value en data-sr-currency. |
Waarde doorgeven met data-attributen
Je hoeft geen JavaScript te schrijven: zet de attributen rechtstreeks op het element. Roots leest ze bij de klik of de verzending uit.
data-sr-eventDe naam van de conversie. Zet dit op een knop, link of formulier. Zonder dit attribuut heet een formulier form_submit.
data-sr-event="reservering"data-sr-valueDe waarde van de conversie in euro's. Gebruik een punt als decimaalteken, geen euroteken.
data-sr-value="89.95"data-sr-currencyValuta van de waarde. Laat je dit weg, dan rekent Roots met EUR.
data-sr-currency="EUR"data-sr-ignoreZet dit op een formulier dat je juist níét wilt meten, bijvoorbeeld een zoek- of filterformulier.
<form data-sr-ignore> … </form>Waarde die pas bij het afrekenen bekend is
Bij een webshop staat de orderwaarde niet vooraf vast. Stuur die dan mee vanaf de bedankpagina - dan telt Roots de echte omzet per bron.
<!-- Bedankpagina van je webshop: echte orderwaarde meesturen -->
<script>
srAttribution.track("purchase", {
value: ORDER_TOTAL, // vervang door de variabele van je shop
currency: "EUR"
});
</script>Heb je geen bedrag, zoals bij een reservering of aanvraag? Stel dan per conversietype een vaste waarde in bij Conversies - bijvoorbeeld het gemiddelde besteedbedrag per couvert.