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().
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:
<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:
<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.
// 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.
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.
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:
| Field | Source |
|---|---|
| host, path | Current URL hostname and path (including query string). |
| referrer | document.referrer, when present. |
| anonymous_id | First-party visitor cookie (_pruuv_vid), 90-day expiry. Stable across sessions. |
| session_id | Session cookie (_pruuv_sid). Cleared when the browser closes. |
| utm_source, utm_medium, utm_campaign, utm_content, utm_term | Last-touch UTMs from the _pruuv_session cookie. |
| gclid, wbraid, gbraid, ttclid, msclkid, li_fat_id, pruuv_aid, fbc, fbp | Click 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_ratio | Device and viewport context. |
| occurred_at | ISO 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.
// 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.
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)
| Parameter | Type | Description |
|---|---|---|
| userId | string (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.email | string | Email address. Hashed with SHA-256 before sending. |
| traits.phone | string | Phone number in E.164 format. Hashed before sending. |
| traits.name | string | Display name (not sent in the webhook — for local reference only). |
| traits.company | string | Company 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.
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)
| Parameter | Type | Description |
|---|---|---|
| eventName | string (required) | Arbitrary event name — e.g. 'Lead', 'Appointment', 'Sale'. Stored in the funnel event for filtering. |
| options.email | string | Overrides stored identity email for this call. Hashed before sending. |
| options.phone | string | Overrides stored identity phone for this call. Hashed before sending. |
| options.value | number | Revenue or conversion value. |
| options.currency | string | ISO 4217 currency code (e.g. GBP, USD, EUR). |
| options.order_id | string | Your order or CRM reference. Overrides identity userId as the event reference. |
| options.reference | string | Explicit event reference. Takes priority over order_id and identity userId. |
| options.<custom> | string | number | boolean | Any 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.reference → options.order_id → identity.userId → pruuv_{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:
| Signal | Cookie | Behaviour |
|---|---|---|
| Last touch | _pruuv_session | Updated on every visit where click IDs or UTMs are present. Sent as the primary attribution in every track() call. |
| First touch | _pruuv_first_touch | Written 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:
| Platform | URL 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.
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():
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):
| Parameter | Platform | Cookie |
|---|---|---|
| utm_source, utm_medium, utm_campaign, utm_content, utm_term | All | _pruuv_session |
| gclid, wbraid, gbraid | Google Ads | _pruuv_session |
| ttclid | TikTok Ads | _pruuv_session |
| msclkid | Microsoft Ads | _pruuv_session |
| fbclid | Meta 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:
- Reads stored click IDs and UTMs from cookies (last-touch)
- Reads stored identity from
_pruuv_identity— merges with any values you pass explicitly - SHA-256 hashes email and phone in the browser — PII never leaves in plaintext
- If the visitor's first-touch differs from their current session, includes it as
first_touchin the payload - Sends a single POST to your Pruuv webhook with all signals included
On Pruuv.page()
When you call page(), the pixel:
- Ensures the visitor cookies exist —
_pruuv_vid(90 days) and_pruuv_sid(session) - Reads the current path and referrer, plus stored click IDs and UTMs from cookies
- Adds device and viewport dimensions
- Sends a POST to
/api/pixel/collectkeyed on your public site key — no PII, no webhook token
Framework examples
React / Next.js
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
<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:
<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.stageIdandwindow.PruuvConfig.tokenare 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/collectand 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 disabled429 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 meansdata-site-keyis missing from the tag - With
data-consent="required", page views are buffered and only sent afterPruuv.consent(true). A Do-Not-Track browser suppresses them entirely
Meta conversion matching rate is low
- Ensure
fbcandfbpare being captured — they are only available via the browser pixel - Include
emailorphoneinPruuv.track()for additional matching signals - Server-side-only integrations will always have lower Meta match rates without the fbc/fbp cookies