Browse the docs

Tracking & attribution

Measurement for an always-on performance stack: one first-party script ties site visits, conversions, and revenue back to your campaigns, with no per-channel pixels to configure.

Installing the tag

The Tracking page in the app gives you a personalized snippet with your site token. It goes in your site’s <head>:

<script async src="https://waverunner.adwave.com/t/YOUR_SITE_TOKEN"></script>
  • One tag per website: each business in your account has its own snippet, conversion rules, webhook, and settings. Run more than one site? Use the switcher on the Tracking page to pick the right one.
  • The tag is SPA-aware: it tracks route changes in single-page apps automatically.
  • Recommended settings (per website, on by default): automatic capture of clicks and form submits, Form conversions (success-gated signup/lead/subscribe), and origin enforcement. Turning any of these off asks you to confirm the impact on Autopilot and tracking. You can restore all recommended settings in one click.

Step-by-step install guides, written for non-technical readers:

Checking that the code is live

The Tracking page has a health card that answers one question: is the code working? It shows the last visit, the last win, how many posts were turned away because they came from a different address than your website, and what reached each ad platform in the last 30 days. Under it is the one thing to do next, in plain words.

  1. Click Send a test ping. This creates a one-time code that is good for ten minutes.
  2. Click Open your site with the test link. Your site opens in a new tab with the code attached to the address. The tag on that page sends the code back, and nothing else.
  3. Return to the Tracking page. The card reads Receiving within about ten seconds of the page loading. No reload needed.

If the link expires before your site reaches us, the most common causes are the code not being on the page you opened, a theme or plugin update that removed it, or the site running on a different address than the one in your business profile. That last case shows up in the Turned away count, together with the address the posts came from.

Opening the test link counts as one ordinary visit. A test ping is never recorded as a conversion.

Defining conversions

The easiest way to set up conversions: on the Tracking page, describe what counts as a win in plain language (“a purchase: people land on an order confirmation page”). Waverunner analyzes your site’s recent traffic and proposes evidence-backed rules you approve with one click. It also watches for conversion-looking pages on its own and asks whether to track them. Nothing is ever tracked without your say-so.

Under the hood, conversions come from four places:

  • Conversion rules: match a URL (or a phone-number tap) and Waverunner records the conversion, no code needed. Approving a suggestion creates one of these; you can also add them manually. Each rule has a trigger so a bare page visit is never mixed up with completing something:
    • Page reach: someone lands on a confirmation page (classic thank-you URL).
    • Form then page: someone submits a form and lands on the page within a minute (needs Autocapture on).
    • Checkout return: someone comes back from a hosted checkout (Stripe, PayPal, and similar). Same-domain checkouts (for example many Shopify stores) still use a thank-you page rule or the purchase webhook.
    • Phone tap: someone taps a tel: link. That counts call intent, not a completed call.
    Rules are per website: a rule you add for one site never fires on another. Prefer confirmation pages over form-entry URLs like /signup. For purchases, a server-side webhook remains the most reliable path when you can wire it.
  • Form conversions: with autocapture + Form conversions enabled, a successful marketing form submit (HTTP 2xx with success UI, or a thank-you navigation) records signup, lead, or subscribe, not every bare submit or form-page visit. No thank-you URL required.
  • The tag API: fire a conversion event from your frontend with a value, currency, and order ID (order IDs de-duplicate automatically).
  • Server-side webhooks: a per-website webhook URL accepts conversions from your backend, with ready-made recipes for Shopify, Stripe, WooCommerce, Zapier, and WhatConverts.

Refunds and value changes

When an order is refunded, cancelled, or its amount changes, send the same webhook with the order id and the name refund or adjust. We never record a second conversion: the original is marked and the ad platforms receive one correction. A refund without an amount reverses the whole order; a refund with an amount lowers the recorded value; adjust sets a new value.

// Full refund of order 1001 (the conversion stops counting).
{ "name": "refund", "orderId": "1001" }

// Partial refund: 20.00 back on a 50.00 order (now worth 30.00).
{ "name": "refund", "orderId": "1001", "value": 20 }

// The order was edited to 75.00.
{ "name": "adjust", "orderId": "1001", "value": 75 }

Include vendorEventId when your platform can send the same refund twice; a repeat with the same id is ignored. An order id we have never seen answers 202 with unmatched_adjustment. Connected stores and payment providers send these for you.

Calls

Phone calls count as conversions when your pages show a tracking number. Every campaign gets its own number, forwarded to yours, so a completed call of at least the minimum length is recorded as a completed call for that campaign and lands in your leads inbox.

Visitors who arrive from an ad also get a number of their own for the length of their visit: the tag swaps the number on the page from a small pool we keep for your website, and a call to that number is tied to the exact visit, click, and ad that brought the caller. The pool grows and shrinks with your traffic (four to twenty numbers) and is released after two months without ad visits. When every pool number is in use, the page simply shows the campaign number.

Ad platforms only ever receive the caller's own click. A call we can match to a visit or to a known contact carries that click; a call we can only place on a campaign is credited to the campaign and sent without a click id.

WhatConverts

Paste your per-website WhatConverts URL from Tracking (Install → Connect your store → WhatConverts) into WhatConverts at Tracking → Integrations → Automations → Webhooks. Turn on New and Update. Choose completed lead types (Phone Call, Web Form, Chat, Custom Event, and the rest). Do not turn on In Progress.

We record the lead and send it to Google or Meta only when it is not spam, not a duplicate, and not marked not quotable. Turn off WhatConverts' own Google Ads upload so the same lead is not sent twice. Leave Meta CAPI on. We send the same event_id the browser pixel uses so Meta dedupes the pair. Leave WhatConverts Facebook on for phone and chat. Turn WhatConverts Facebook off for website forms only if Events Manager shows two Leads for the same form. If a lead is marked spam after we already sent it to Google, Google keeps that retraction. We still correct our own numbers.

Hosted landing page forms

Forms on the landing pages we host for you carry a small Cloudflare Turnstile check. Most visitors never see a challenge; it runs quietly in the page and stops bots from filling your leads inbox. Every submission is verified on our servers before it becomes a lead, is emailed to you, or is sent to your CRM.

Two more guards run behind the scenes: a submission has to come from the page it belongs to, and a sudden burst of submissions on one page is kept but marked suspect. Held leads appear in your inbox with a Held label and a reason; they are not counted and not sent to ad platforms.

What a win is worth

Cost per conversion works as soon as conversions count. Return on ad spend (ROAS) needs a dollar value on those wins. We never invent a platform default: until you set a value, ROAS shows an honest dash, and Autopilot still optimizes for cost per conversion.

  • Purchases: pass the real order amount in a track call, or connect Shopify / WooCommerce / Stripe so the webhook carries exact totals.
  • Leads and signups: on Tracking, use Set what a win is worth on each goal (or on Form conversions: lead, signup, subscribe). Form conversion values also cover hosted pages, Instant Forms and completed calls. Not selling directly? Estimate from close rate: if about one in ten signups becomes a $500 customer, a signup is worth roughly $50. Reports label that revenue as estimated. You can change or clear an estimate anytime.
  • Real amounts win: if the same conversion name arrives with a value from track, a webhook, or an order-grade page total, that number is used and the Tracking estimate is ignored for that event. Estimates only fill in when the event carries no value. Different names stay independent (purchase does not use a signup estimate).
Values apply to new conversions only. Changing an estimate does not rewrite history.

The tag API

Once the snippet is installed, every page has a global waverunner() function with four commands:

// Record a conversion. Value is in dollars; the optional orderId
// makes the same purchase count once even if it is reported twice
// (for example by this call and your store's webhook).
waverunner("track", "purchase", { value: 129.99, orderId: "1001" });

// Tell us who the visitor is when you learn it (sign-in, checkout).
// This links the browser to that email, so conversions your backend
// sends later still connect to the ad that brought them, even from
// another device. Emails are stored only as salted one-way hashes.
waverunner("identify", "customer@example.com");

// Wire in your cookie banner: declined visitors get the same
// do-not-share treatment as Global Privacy Control.
waverunner("consent", { adSharing: userAcceptedAdCookies });

// Confirm the install from the browser console. The Tracking page
// gives you a one-time code; the tag sends that code and nothing else.
waverunner("ping", "CODE_FROM_THE_TRACKING_PAGE");
  • track accepts any conversion name you choose, plus optional value, currency, and orderId.
  • identify takes an email string, or an object with email, phone, firstName, and lastName. It is the glue between browser visits and server-side webhook conversions: without it, a backend-reported sale can only match through the same browser.
  • consent stores the visitor's choice for a year. Measurement for your own reports continues either way; declining stops all sharing with ad platforms.
  • ping is the manual form of the test link described under checking that the code is live. Each code works once and expires after ten minutes.

How attribution works

  • Last-touch: a conversion is credited to the most recent qualifying touch. That stamp drives billed results, wallet charges, and ROAS. All touches are still stored.
  • Clicks beat views: ad clicks carry a first-party click ID (30-day window) and always win over view-through exposure.
  • View-through: TV and web display impressions are recorded by a first-party pixel; the view-through window is configurable at 7, 14, or 30 days.
  • Comparison models on Tracking: when someone sees more than one of your ads before converting, Tracking shows how first touch, last touch, linear, time decay, position, and data-driven models would split the same dollars. Those columns are for learning only. Money and ROAS stay on last touch.
  • Assisted conversions: on a campaign's Performance tab, assists show when another channel touched someone earlier (click, household view, or person-resolved view) before a different channel got last-touch credit. Assists are reporting-only and never change wallet charges or ROAS. Details: Assisted conversions and Lift.
  • Closed loop: attributed conversions are forwarded to Meta (Conversions API) and Google (offline click-conversion uploads). For Google, those offline uploads are the primary signal used for bidding. When Google Ads is connected, the tag also loads a sitewide Google tag (AW config) and may fire observation-only website conversion events that share the same transaction id; they do not double-count against the offline primary. Install our first-party tag; you do not need a separate Google conversion snippet on the page. Our house tag already writes utm_source=adwave-meta on Meta clicks. Google Analytics will not file that source as Paid Social on its own. Add a custom GA4 channel for adwave-meta if you want that label in your property.

The click id on landing links

Every ad we run sends people to your site through a short redirect that appends a first-party click id to the landing link. The tag reads it on the first page, so the visit, and any win that follows, can be credited to the exact ad. Tracking's Signal tab shows Landing links are missing the click id when ad clicks arrive but no visit carries one: a redirect on your side (a www to non-www hop, a trailing-slash rule, a consent page that reloads without the query string) is stripping it. Keep the query string through every redirect, or land the ads on the final address so no redirect runs.

Cookies the tag sets

All cookies are first-party, set on your own domain:

CookiePurposeLifetime
_wr_vidVisitor ID: recognizes a returning browser365 days
_wr_sidSession ID: groups a visit30 minutes, sliding
_wr_cidClick ID: links a visit to the ad click that caused it30 days
_wr_aidAd ID: which specific ad drove the click30 days
_wr_liLine ID: which audience line of the campaign the click came from30 days
_wr_campCampaign ID: which campaign the click came from30 days
_wr_utmSession UTM parameters (source, medium, campaign, term, content). Last campaign-tagged landing in the session wins.30 minutes, sliding
wr_consentSet only when your site calls the tag's consent API; records whether the visitor declined ad-platform sharing365 days

When Waverunner adds UTM parameters on an ad click-through (for your own analytics tools), utm_campaign is a readable slug from the campaign name plus a short id (for example summer-sale-3f2a), not the raw campaign UUID. Existing UTM parameters already on your landing URL are never overwritten. Money attribution still uses the first-party click id, not UTMs.

Emails and IP addresses are hashed with a salt before storage; Waverunner never stores raw personal identifiers from your visitors. See the privacy policy for the full data-handling picture.