Docs/Tracking Page/Campaign attribution

Campaign attribution#

Attribution answers one question: which orders did your tracking-page campaigns generate? Karla answers it by tagging every campaign link with query parameters, capturing those parameters in your shop when the customer buys, and matching the result back to the campaign in your analytics.

How attribution works#

  1. A campaign (main, product, or banner promotion) is shown on your tracking page, in a notification email, or on the thank-you page.
  2. When the customer clicks the call-to-action, Karla has already appended its attribution parameters to the target URL — no setup needed on the link side.
  3. Your storefront captures the parameters. This is the only step where you have choices to make — see capture paths.
  4. When the order is created, the captured parameters are stored on the Karla order as order_analytics.
  5. Karla matches the campaign value back to the campaign and reports impressions, clicks, orders, and revenue in the portal analytics.
flowchart LR
    A["Campaign shown<br/>(tracking page, email,<br/>thank-you page)"]
    B["Customer clicks CTA<br/>URL carries karla_* params"]
    C["Storefront captures params<br/>(pixel, SDK, plugin,<br/>cart attributes, or API)"]
    D["Order created<br/>stored as order_analytics"]
    E["Campaign matched<br/>reported in Analytics"]
    A --> B --> C --> D --> E

Discount codes are an independent, complementary signal that skips steps 2–3 entirely — see discount code attribution.

The attribution parameters#

Every campaign call-to-action link carries these query parameters:

ParameterExample valueMeaning
refkarlaMarker present on every Karla-originated link. See source markers for variants.
karla_sourcetrackpagesThe surface the click came from.
karla_mediumproduct_promotionThe promotion type: basic_promotion, product_promotion, or banner_promotion.
karla_campaign9f1c6a7e-3b2d-4e8f-9a10-1234567890abThe campaign's unique ID — this is what links the order to the exact campaign.

Product-promotion links that are rendered on a specific order's tracking page additionally carry the originating order, so you can trace a repeat purchase back to the order whose tracking page produced it:

ParameterMeaning
karla_order_external_idPlatform ID of the originating order.
karla_order_numberOrder number of the originating order.
karla_order_nameDisplay name of the originating order.

Two things worth knowing:

  • Karla never overrides existing parameters. If a target URL you configured already contains karla_source (or any other parameter), your value wins.
  • The parameters describe the campaign, never the shopper. They contain no user data, which keeps the privacy story simple — see the consent notes under each capture path.

Source markers#

The ref parameter distinguishes which Karla surface produced the click:

MarkerSet onCaptured by
ref=karlaAll campaign links on tracking pages and in notifications.All capture paths.
ref=karla-thankyouCall-to-action links of the thank-you page promotion in the Karla Shopify app.Browser SDK global mode. The Shopify pixel records the karla_* parameters those links also carry.
ref=karla-loungeLinks from the Karla Lounge brand-deals page.Browser SDK global mode (also as legacy source=karla-lounge).

Capture paths#

The parameters only matter if something in your storefront records them and attaches them to the order. All five paths below produce the same result — an order_analytics record on the Karla order. On Shopify with the Karla app installed, the app pixel is the default choice; the other paths cover the stacks where the pixel can't capture — headless storefronts, Shopware, and fully custom platforms. Paths can coexist; the first captured attribution wins. Each path also implies its own attribution window — how long a captured click can still be matched to an order.

Shopify app pixel#

The Karla Shopify app ships a web pixel that captures the parameters on landing and sends them to Karla only when a checkout completes.

  • Enable it once in the Karla app under Settings → Campaign Attribution (pixel toggle, consent requirement, optional browse tracking).
  • The pixel stores the parameters in session storage (key karla_attribution) — scoped to the current tab, first click wins within the session. The stored attribution is cleared when the tab's session ends or when a checkout completes (it is sent to Karla, then cleared). Alongside the karla_* parameters it records ref only when the value is exactly karla.
  • Consent is yours to configure: No consent required (default — the parameters are non-PII and short-lived), Analytics consent required, or Marketing consent required. With a consent level set, the pixel stays idle until your privacy banner reports that consent.
  • Browse tracking (beta) buffers viewed product IDs in local storage (key karla_browse) and always requires analytics consent, independent of the attribution setting. The buffer is purged as soon as an order completes, consent is withdrawn, or the feature is turned off.

Browser SDK global mode#

If you embed the tracking page with the Browser SDK, you can load it site-wide in global mode. With the Karla Shopify app installed, the app pixel already covers capture — global mode mainly serves Shopify storefronts running without the app:

<script
  id="karla-bundle"
  src="https://browser.gokarla.io/latest/bundle.min.js"
  data-shop-slug="your-shop-slug"
  data-starter-page="global"
></script>

On every page load it checks the URL for attribution parameters and writes them as cart attributes via Shopify's /cart/update.js: _karla_source, _karla_campaign, _karla_medium, _karla_captured_at, _karla_landing_url, _karla_landing_path, _karla_referrer, _karla_version. Shopify copies cart attributes onto the order, where Karla reads them back into order_analytics.

  • First click wins per cart — once _karla_source is on the cart, later visits don't overwrite it.
  • The attribution lives exactly as long as the cart. It ends when the checkout completes (the attributes move onto that order), the cart is emptied, or the cart cookie expires — typically around 14 days after the last cart activity (Shopify doesn't document the exact lifetime).
  • Accepts karla_source or the legacy markers ref/source equal to karla or karla-lounge.
  • The landing URL is stripped of sensitive query parameters (tokens, emails, click IDs, and ~40 more) and truncated to Shopify's 255-character attribute limit. Nothing is written to cookies or browser storage — the data lives on the cart itself.
  • Shopify-only: the mode deactivates quietly when /cart.js doesn't exist. This includes headless Shopify storefronts — see the next path.

Headless Shopify cart attributes#

On a headless Shopify storefront (Hydrogen, Next.js, or any custom frontend on the Storefront API) the two automatic paths above cannot run: the app pixel only fires on pages Shopify serves, and global mode deactivates because /cart.js doesn't exist on your origin. The order side is unaffected — checkout still happens on Shopify, and cart attributes still flow onto the order — so your storefront writes the same attributes the automatic paths would have written:

  1. Capture on landing. When a visitor arrives with karla_* parameters, record them — including the originating-order parameters (karla_order_external_id, karla_order_number, karla_order_name) that product-promotion links carry — and persist them in your own storage, so they survive client-side navigation and are still available when a cart is created later. First click wins: don't overwrite values you already captured.

  2. Write them to the cart as soon as one exists, via the Storefront API's cartAttributesUpdate mutation. Skip the write if the cart already carries _karla_source. The attributes mirror what global mode writes, prefixed keys and all:

    {
      "_karla_source": "trackpages",
      "_karla_campaign": "9f1c6a7e-3b2d-4e8f-9a10-1234567890ab",
      "_karla_medium": "product_promotion",
      "_karla_order_external_id": "8412374597941",
      "_karla_order_number": "1001",
      "_karla_order_name": "#1001",
      "_karla_captured_at": "2026-08-07T10:30:00Z",
      "_karla_landing_url": "https://shop.example.com/products/foo?ref=karla",
      "_karla_landing_path": "/products/foo",
      "_karla_referrer": "https://shop.example.com/pages/tracking"
    }

    The underscore prefix hides the attributes from the customer in the checkout, and each value must stay within Shopify's 255-character attribute limit. Strip secrets and PII from _karla_landing_url before writing it.

  3. Nothing is needed on Karla's side. Shopify copies the cart attributes onto the order as note attributes when the checkout completes, and the regular order sync delivers them to Karla as order_analytics.

Karla reads attribution from order-level note attributes only. Attributes attached to cart lines (the attributes input of cartLinesAdd / cartLinesUpdate) end up on the line items, never in the order's note attributes, and are invisible to Karla. Write them at the cart level with cartAttributesUpdate.

Shopify deletes a cart when its checkout completes, so the next cart starts without attribution. Re-apply the parameters from your persisted copy when a new cart appears — how long you keep that copy is your storefront's attribution window (see below).

Shopware plugin#

The Karla Shopware plugin forwards Shopware's native affiliate tracking: affiliateCode and campaignCode on the order are translated into the canonical source and campaign fields when the order syncs to Karla. See the Shopware guide for plugin setup.

Direct API#

On non-Shopify custom stacks — or whenever your backend is the only place that knows the attribution — capture the parameters yourself (URL params, session, server-side) and send them in the order_analytics field when you create or update the order via the Orders API. On headless Shopify, prefer cart attributes; this path is the fallback when writing the cart isn't an option:

{
  "order_analytics": {
    "source": "trackpages",
    "campaign": "9f1c6a7e-3b2d-4e8f-9a10-1234567890ab",
    "medium": "product_promotion",
    "landing_url": "https://shop.example.com/products/foo?ref=karla",
    "landing_path": "/products/foo",
    "referrer": "https://track.gokarla.io/",
    "captured_at": "2026-08-07T10:30:00Z"
  }
}

Use the canonical keys source (required for attribution), campaign, medium, landing_url, landing_path, referrer, and captured_at — they map 1:1 to what the other capture paths produce. To trace repeat purchases back to the originating order, also include order_external_id, order_number, and order_name when the click carried the karla_order_* parameters. The legacy alias keys affiliate_code and campaign_code are accepted and rewritten to source and campaign. Strip secrets and PII from landing_url before sending; the Browser SDK's sanitizer is a good reference for what to remove.

This path gives you full control (server-side, immune to ad blockers, works on any platform) at the cost of owning the capture logic yourself.

Discount code attribution#

Attach a discount code to a campaign in the portal and any order using that code is attributed to the campaign — no parameters involved, so it works on every platform and survives ad blockers, disabled JavaScript, and cross-device checkouts.

Best practices:

  • Keep the code exclusive to the campaign — shared or leaked codes (coupon sites, social media) inflate attribution.
  • Create unique codes per campaign and rotate or time-limit them.
  • Keep codes simple but distinctive — e.g. KARLA-WELCOME-DEC instead of SAVE10.

Verification: filter orders by discount code in your platform (Shopify: Orders → Filter by discount code; WooCommerce: Orders → Filter by coupon).

Discount attribution complements the parameter-based paths: parameters catch non-discount campaigns, codes catch customers who saw a campaign but converted later on another device.

Attribution windows#

A captured click doesn't attribute orders forever. Each capture path stores the click somewhere, and that storage determines the attribution window: which later orders the click can still be matched to. The windows are not configured on Karla's side — they follow from where each path stores the click.

PathClick is stored inWindow ends when
Shopify app pixelSession storage of the tabA checkout completes (the attribution is sent, then cleared) or the tab's session ends.
Browser SDK global modeThe Shopify cartThe checkout completes, the cart is emptied, or the cart cookie expires — typically ~14 days after the last cart activity (Shopify doesn't document the exact lifetime).
Headless Shopify cart attributesYour Storefront API cart, plus your own storageThe checkout completes (Shopify deletes the cart) or the cart expires — Shopify expires unused carts within 30 days of creation. Your own persistence decides whether the next cart gets the attribution re-applied.
Shopware pluginThe Shopware orderNot applicable — captured at order time.
Direct APIYour systemsYour choice — you decide how long a click stays attributable.
Discount codesThe code itselfNever — the code attributes any order it is applied to, for as long as the code is active.

Two practical consequences:

  • A first click only wins within its window. Once the window closes (order placed, cart gone, session ended), the next qualifying click starts a fresh attribution.
  • Changing capture paths changes your numbers. Migrating your storefront — for example from a Shopify theme to a headless setup — swaps the attribution window along with the capture mechanism, so attributed-order counts can shift even when capture works perfectly. Compare like with like before concluding that attribution is broken.

Verify your setup#

Walk the chain once end-to-end:

  1. Click a campaign. Open your tracking page for a test order, click the campaign's call-to-action, and check the address bar: you should see ref=karla, karla_source, karla_medium, and karla_campaign.
  2. Check the capture.
    • Pixel: in the storefront tab, open dev tools → Application → Session Storage and look for the karla_attribution key.
    • Browser SDK global mode: add something to the cart, then open /cart.js in the browser — the attributes object should contain _karla_source and friends.
    • Headless cart attributes: query the cart with your storefront client and confirm the cart-level attributes contain _karla_source and friends (not the line-item attributes).
  3. Place a test order in that same session, then confirm the attribution arrived: fetch the order via the API and check order_analytics, or open the order in the portal.
  4. Check the reporting. The campaign's numbers appear in Analytics → Purchases and Analytics → Campaign Stats (allow some time for processing).

When something is missing:

  • No parameters on the click — the campaign target URL already carried its own karla_* parameters (yours win), or you tested a link outside a campaign.
  • Pixel captured nothing — the pixel is disabled, the configured consent level wasn't granted by your banner, or the link only carried a marker the pixel ignores (it records ref only for ref=karla).
  • SDK captured nothing — the store isn't a Shopify theme storefront (/cart.js missing — headless setups need cart attributes), or the cart already carries an earlier _karla_source (first click wins).
  • Headless: capture works but orders carry nothing — the attributes were written as line-item properties instead of cart-level attributes, or onto a cart that was replaced before checkout.
  • Order has no attribution — the customer checked out in a different browser, device, or session than the click; parameter-based attribution can't bridge that gap, discount codes can.
  • Numbers dropped after a storefront migration — the capture path changed, and with it the attribution window; verify the new path end-to-end and compare windows before comparing counts.
  • Attribution looks too high — a campaign discount code is circulating outside Karla surfaces.

Where attribution shows up#

  • Analytics → Purchases — attributed orders and revenue per campaign.
  • Analytics → Campaign Stats — impressions, click-through rate, and conversion per campaign; this is also what A/B tests read.
  • Order dataorder_analytics on the order via the Orders API.
  • Karla MCP — the get_order_campaigns tool returns the campaigns shown for a specific order, so you can query attribution from Claude or any MCP client.

Parameter-based attribution undercounts cross-device journeys; discount codes overcount when codes leak. For A/B tests, prefer parameter or API attribution, run tests for at least 7 days, and treat ~100+ orders per variant as the minimum for significance.

Was this helpful?