PruuvDocs

Pixel SDK

The Pruuv Pixel is a lightweight JavaScript snippet (~4 KB) that you add to your landing pages. It automatically captures UTM parameters and ad platform click IDs on page load, sends a conversion event to Pruuv when you call Pruuv.track(), and records page views for traffic analytics when you call Pruuv.page().

Both steps are required. The pixel captures click IDs automatically on page load and stores them in first-party cookies. Pruuv.track() is what actually delivers those click IDs to Pruuv so matched conversions can be sent back to your ad platforms. Without track(), Pruuv has no data to send.

Quick start

1. Add the snippet to your landing page

Paste the following before the closing </body> tag on every landing page where you want to track conversions:

html
<script async src="https://app.pruuv.io/pixel.min.js"
        data-site-key="YOUR_SITE_KEY"
        onload="Pruuv.page()"></script>

Replace YOUR_SITE_KEY with the site key from Funnel Configuration > Web pixel in your Pruuv dashboard. The key is a public identifier — it is safe to expose in the page. The tag auto-initialises on load, so no separate config block is needed.

The onload="Pruuv.page()" attribute records a page view as soon as the pixel is ready. Because the script is async, Pruuv does not exist yet when the surrounding HTML parses — firing from onload is what guarantees the call lands. Drop the attribute if you do not want page-view tracking; conversion tracking works without it. See Page-view tracking below.

If your Content-Security-Policy blocks inline event handlers, set the site key via window.PruuvConfig before the script loads and call Pruuv.page() from your own bundle instead:

html
<script>
  window.PruuvConfig = { siteKey: 'YOUR_SITE_KEY' };
</script>
<script src="https://app.pruuv.io/pixel.min.js" async></script>

2. (Optional) Identify the visitor

Call Pruuv.identify() when you know who the visitor is — for example, when they submit a form step or log in. This stores their identity in a first-party cookie so subsequent track() calls don't need to repeat it. The userId becomes the reference field in the funnel event, enabling cross-session identity stitching.

javascript
// Call when you know who the user is (form step, login, etc.)
Pruuv.identify('crm-contact-id', {
  email:   'jane@example.com',
  phone:   '+441234567890',
  name:    'Jane Smith',
  company: 'Acme Ltd'
});

// Then track() with no identity fields needed — they're stored
Pruuv.track('Lead', {
  value:    250,
  currency: 'GBP'
});

3. Fire a conversion event

Call Pruuv.track() at the moment a lead or sale is confirmed — for example, after a form is successfully submitted. If you called identify() earlier, email and phone are already stored and will be sent automatically.

javascript
Pruuv.track('Lead', {
  email:    'jane@example.com',   // hashed in browser — or omit if identify() was called
  phone:    '+441234567890',      // hashed in browser — or omit if identify() was called
  value:    250,                  // revenue value
  currency: 'GBP',               // ISO 4217 currency code
  order_id: 'CRM-REF-001'        // your internal reference (overrides identify userId)
});

All fields are optional except the event name. The pixel handles everything else automatically.

Page-view tracking

Pruuv.page() records a page view for traffic analytics and first-touch attribution. It is separate from track(): page views go to /api/pixel/collectand are stored against your client, while conversions go to a funnel stage's webhook. Page views are never sent to ad platforms.

javascript
Pruuv.page()   // call on each page load / route change

The install snippet above already fires this once per full page load via onload. Call it again yourself whenever the visitor reaches a new page without a full reload.

What each call sends

Everything is collected automatically — page() takes no arguments:

FieldSource
host, pathCurrent URL hostname and path (including query string).
referrerdocument.referrer, when present.
anonymous_idFirst-party visitor cookie (_pruuv_vid), 90-day expiry. Stable across sessions.
session_idSession cookie (_pruuv_sid). Cleared when the browser closes.
utm_source, utm_medium, utm_campaign, utm_content, utm_termLast-touch UTMs from the _pruuv_session cookie.
gclid, wbraid, gbraid, ttclid, msclkid, li_fat_id, pruuv_aid, fbc, fbpClick IDs captured on landing — so page views carry attribution, not just traffic counts.
user_agent, screen_width, screen_height, viewport_width, viewport_height, device_pixel_ratioDevice and viewport context.
occurred_atISO 8601 timestamp generated in the browser at call time.

Single-page apps

A client-side route change fires no browser load event, so call Pruuv.page()from your router's navigation hook. The pixel already re-captures UTMs and click IDs on pushState, replaceState, popstate and hashchange, so late-arriving parameters are recorded either way — but the page view itself is yours to fire.

jsx
// Next.js App Router — in a client component
'use client'

import { usePathname, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'

export function usePruuvPageViews() {
  const pathname = usePathname()
  const searchParams = useSearchParams()

  useEffect(() => {
    window.Pruuv?.page()
  }, [pathname, searchParams])
}

The optional chaining (?.) makes the call a no-op if the pixel has not loaded yet.

Authorisation

Page views are authorised by the public site key plus the domain allowlist you set under Funnel Configuration > Web pixel — posts from any other origin are rejected with 403. No webhook token is involved, which is why the site key is safe to publish. Requests are also rate-limited per site key.

A site key is required. Without data-site-key (or PruuvConfig.siteKey), page() logs a warning and does nothing. Disabling the pixel or rotating the site key stops existing tags from recording page views immediately.

Full API reference

Pruuv.identify(userId, traits)

ParameterTypeDescription
userIdstring (required)Your internal contact or lead ID — becomes the reference field in the funnel event. Use your CRM contact ID, email, or any stable identifier.
traits.emailstringEmail address. Hashed with SHA-256 before sending.
traits.phonestringPhone number in E.164 format. Hashed before sending.
traits.namestringDisplay name (not sent in the webhook — for local reference only).
traits.companystringCompany name (not sent in the webhook — for local reference only).

Identity is stored in a 90-day first-party cookie (_pruuv_identity). It persists across page loads untilPruuv.reset() is called.

Calling identify() multiple times is safe — the most recent call wins. If the same visitor calls identify() with different userIds across sessions (e.g. after switching accounts), each event is stored independently.

Pruuv.track(eventName, options)

ParameterTypeDescription
eventNamestring (required)Arbitrary event name — e.g. 'Lead', 'Appointment', 'Sale'. Stored in the funnel event for filtering.
options.emailstringOverrides stored identity email for this call. Hashed before sending.
options.phonestringOverrides stored identity phone for this call. Hashed before sending.
options.valuenumberRevenue or conversion value.
options.currencystringISO 4217 currency code (e.g. GBP, USD, EUR).
options.order_idstringYour order or CRM reference. Overrides identity userId as the event reference.
options.referencestringExplicit event reference. Takes priority over order_id and identity userId.
options.<custom>string | number | booleanAny other key is forwarded to the webhook verbatim. Keys declared under Funnel → Custom attributes are stored on the event (typed and normalised); undeclared keys are kept in the raw payload only.

Reference resolution order: options.referenceoptions.order_ididentity.userIdpruuv_{timestamp}

Custom keys — e.g. Pruuv.track('Lead', { credit_band: 'B' }) — travel with the event untouched. Declare them under Funnel → Custom attributes to store them on the event for conversion value rules.

Pruuv.page()

Records a page view. Takes no arguments — path, referrer, visitor and session IDs, captured click IDs and device context are all read automatically. Requires a site key. Returns a promise that resolves once the request has been dispatched; it never rejects, so you do not need to catch it. Safe to call on every route change. See Page-view tracking above.

Pruuv.reset()

Clears the stored identity. Call this on logout so subsequent events are not associated with the previous user. Does not clear click ID cookies — those persist for attribution purposes.

First-touch vs last-touch attribution

The Pruuv Pixel automatically maintains two attribution signals:

SignalCookieBehaviour
Last touch_pruuv_sessionUpdated on every visit where click IDs or UTMs are present. Sent as the primary attribution in every track() call.
First touch_pruuv_first_touchWritten once on the visitor's very first tracked visit. Never overwritten. Sent as first_touch alongside last-touch when the two differ.

When a visitor clicks a Google ad, then later clicks a Meta ad and converts, Pruuv records both: the Meta click as last-touch (used for conversion sending) and the Google click as first-touch (surfaced in agent analysis and cross-channel reporting). Both signals are stored in the funnel event automatically — no extra code required.

Native attribution (pruuv_aid)

Pruuv's native attribution path lets you track conversions back to a specific ad creative without relying on platform click IDs. Instead of waiting for a gclid or ttclid in the URL, you embed a Pruuv-generated ID (pruuv_aid) directly in your ad's destination URL template. The pixel captures it automatically on landing.

This approach is platform-agnostic — it works with any ad network that supports URL parameters, and does not depend on the ad platform's click ID system. It is particularly useful when click IDs are stripped by ad blockers or browser privacy settings.

Setup: add pruuv_aid to your ad URL template

In your ad platform's URL template field, add pruuv_aidas a custom parameter. Use the ad's own dynamic substitution macro to fill it per creative. Here are examples for each platform:

PlatformURL template parameter
Meta Ads?pruuv_aid={{ad.id}}
Google Ads?pruuv_aid={creative}
TikTok Ads?pruuv_aid=__ADID__
Any platform?pruuv_aid=YOUR_STATIC_AD_ID

The value can be a platform macro (so each ad gets its own ID automatically) or a static string you define per campaign. The pixel reads whatever value is in the URL parameter — it does not validate the format.

How it works

On page load, the pixel reads the pruuv_aid URL parameter and stores it in the session cookie alongside other click IDs. When Pruuv.track() is called, the pruuv_aid is included in the webhook payload. Pruuv then matches the event to the specific ad in the pruuv_native_ads table, enabling attribution to that creative, ad set, and campaign — fully server-side, no pixel ID required.

Platform click IDs take precedence for conversion sending. If both a pruuv_aid and a gclid / ttclid are present, the platform click ID is used for conversion sending (Google Upload / TikTok Events API) while pruuv_aid is used for creative-level attribution reporting. You do not need to choose — the pixel captures both.

Manual override in track()

If you need to pass a pruuv_aid that was not in the URL (e.g. stored in your own session state), you can pass it directly in track():

javascript
Pruuv.track('Lead', {
  pruuv_aid: 'your-ad-id-here',
  email:     'jane@example.com',
  value:     250,
  currency:  'GBP'
});

An explicit pruuv_aid passed to track() overrides any value captured automatically from the URL.

How it works

On every page load

The pixel runs silently as soon as the page loads. It reads the following URL parameters and stores them in first-party cookies (90-day expiry):

ParameterPlatformCookie
utm_source, utm_medium, utm_campaign, utm_content, utm_termAll_pruuv_session
gclid, wbraid, gbraidGoogle Ads_pruuv_session
ttclidTikTok Ads_pruuv_session
msclkidMicrosoft Ads_pruuv_session
fbclidMeta Ads_fbc

Two special cookies are also maintained:

  • _pruuv_first_touch — a snapshot of the very first click that brought this visitor. Written once and never overwritten.
  • _fbp — Meta's browser ID. Generated on first visit and persists for 90 days. Required for Meta Conversions API matching.

On Pruuv.track()

When you call track(), the pixel:

  1. Reads stored click IDs and UTMs from cookies (last-touch)
  2. Reads stored identity from _pruuv_identity — merges with any values you pass explicitly
  3. SHA-256 hashes email and phone in the browser — PII never leaves in plaintext
  4. If the visitor's first-touch differs from their current session, includes it as first_touch in the payload
  5. Sends a single POST to your Pruuv webhook with all signals included

On Pruuv.page()

When you call page(), the pixel:

  1. Ensures the visitor cookies exist — _pruuv_vid (90 days) and _pruuv_sid (session)
  2. Reads the current path and referrer, plus stored click IDs and UTMs from cookies
  3. Adds device and viewport dimensions
  4. Sends a POST to /api/pixel/collect keyed on your public site key — no PII, no webhook token

Framework examples

React / Next.js

jsx
async function handleSubmit(data) {
  const result = await submitForm(data)
  if (result.ok) {
    window.Pruuv?.track('Lead', {
      email:    data.email,
      value:    250,
      currency: 'GBP',
      order_id: result.referenceId
    })
  }
}

The optional chaining (?.) means the call is a no-op if the pixel is not loaded.

HTML form

html
<form id="contact-form">
  <!-- your fields -->
  <button type="submit">Get a quote</button>
</form>

<script>
  document.getElementById('contact-form').addEventListener('submit', function(e) {
    e.preventDefault();
    var email = this.querySelector('[name="email"]').value;

    // submit your form here, then on success:
    Pruuv.track('Lead', { email: email, currency: 'GBP' });
  });
</script>

Google Tag Manager

Create a Custom HTML tag triggered on your form success event or thank-you page:

html
<script>
  Pruuv.track('Lead', {
    email:    '{{DLV - email}}',
    value:    {{DLV - lead_value}},
    currency: 'GBP',
    order_id: '{{DLV - reference}}'
  });
</script>

Privacy

  • Email and phone are hashed with SHA-256 in the browser before being sent — Pruuv never stores plaintext PII
  • The pixel sets first-party cookies only — no third-party cookies, no cross-site tracking
  • No data is sent to any third party by the pixel — all data goes directly to your Pruuv webhook endpoint
  • The pixel does not load any external scripts or resources

Troubleshooting

No events appearing in Pruuv

  • Check the browser network tab — look for a POST to /api/webhooks/funnel/... and inspect the response
  • Verify window.PruuvConfig.stageId and window.PruuvConfig.token are set before the pixel loads
  • Confirm the token matches the one shown in Connections > Funnel Webhooks

Events appear but click IDs are missing

  • Ensure the pixel snippet is on the landing page — the page the user arrives on after clicking the ad — not just the thank-you page
  • Click IDs are only available in the URL on the initial landing. They cannot be recovered if the user navigates before the pixel loads

No page views appearing in Pruuv

  • Check the browser network tab for a POST to /api/pixel/collect and read the response body
  • 403 Forbidden origin — the page's host is not in the allowed domains list under Funnel Configuration > Web pixel. Add it (subdomains of a listed domain are accepted)
  • 404 Unknown or inactive site — the site key is wrong, was rotated, or the pixel is disabled
  • 429 Rate limit exceeded — the site key exceeded its hourly page-view budget
  • No request at all — check the console for [Pruuv] siteKey is required for page(), which means data-site-key is missing from the tag
  • With data-consent="required", page views are buffered and only sent after Pruuv.consent(true). A Do-Not-Track browser suppresses them entirely

Meta conversion matching rate is low

  • Ensure fbc and fbp are being captured — they are only available via the browser pixel
  • Include email or phone in Pruuv.track() for additional matching signals
  • Server-side-only integrations will always have lower Meta match rates without the fbc/fbp cookies