PruuvDocs

Pixel SDK

The Pruuv Pixel is a small script for your website (about 16 KB, or 6 KB compressed). It saves the ad click IDs and UTM tags a visitor arrives with, records page views, and links the visitor to their email when they fill in a form. Pruuv uses this to match your leads to the ads that brought them, and to send better-matched conversions back to the ad platforms.

Which calls do you need?

  • Your CRM sends leads to Pruuv (most set-ups): add the tag, and call Pruuv.identify() when the visitor gives their email. You do not need track(). Your CRM must also send each lead’s UTM tags, because the pixel does not add them (see What the pixel saves from the URL).
  • Pruuv gets no leads from your CRM: also call Pruuv.track() when the conversion happens.
  • You only want page views: the tag is enough.

Quick start

1. Add the tag to every page

Copy the tag from Settings → Pixel SDK in Pruuv. It already contains your site key. Only agency users can open this page. If it shows Enable web pixel, an agency admin must click it first. If you cannot open the page, ask your Pruuv contact for the tag. Paste the tag just before the closing </body> tag on every page, and always on the landing pages your ads link to:

html
<script async src="https://ai.pruuv.com/pixel.min.js"
        data-site-key="YOUR_SITE_KEY"
        onload="Pruuv.page()"></script>
  • data-site-key is your site key. It is public, so it is safe in your page.
  • onload="Pruuv.page()" records a page view once the script has loaded. The script is async, so Pruuv does not exist before then. Remove the attribute if you do not want page views.
  • Load the script from https://ai.pruuv.com. The pixel sends its data to the host it was loaded from.

If your Content-Security-Policy blocks inline code, load the pixel from your own script file instead. Your policy must allow https://ai.pruuv.com in both script-src and connect-src.

javascript
var script = document.createElement('script');
script.src = 'https://ai.pruuv.com/pixel.min.js';
script.async = true;
script.dataset.siteKey = 'YOUR_SITE_KEY';
script.onload = function () { window.Pruuv.page(); };
document.head.appendChild(script);

2. List your domains

In Settings → Pixel SDK (agency admins only), add every domain the tag runs on under Allowed domains (one per line), then click Save domains. A domain also covers its subdomains: example.com allows www.example.com and apply.example.com. The same list applies to track().

An empty list allows every website. Pruuv then accepts page views and identify() calls from any site that uses your site key, and track() calls from any site that has your stage token.

3. Identify the visitor

Call Pruuv.identify() as soon as you know the visitor’s email, usually in your form’s submit handler. The first argument must be the email. If you know your CRM’s ID for this lead, pass it as reference.

javascript
Pruuv.identify('jane@example.com', {
  reference: 'crm-contact-12345', // your CRM's ID for this lead (recommended)
  phone:     '+441234567890'      // optional: used by later track() calls
});
  • Call it before your CRM sends the lead to Pruuv, so the click IDs are ready when the lead arrives. Do not wait for your CRM’s reply.
  • The lead your CRM sends must carry the same email, the same reference or the visitor ID. That is how Pruuv links them: see How Pruuv links the pixel to your CRM.
  • It needs the site key. Without one, it logs a warning and sends nothing.

4. Send the conversion from the browser (optional)

You only need this when your CRM does not send the lead to Pruuv. Call Pruuv.track() when the conversion happens, for example after your form is sent:

javascript
Pruuv.track('YOUR_STAGE_ID', 'YOUR_STAGE_TOKEN', {
  reference: 'crm-contact-12345',
  value:     250,
  currency:  'GBP'
});

Each funnel stage has its own stage ID and token. To report a later stage, call track() again with that stage’s pair.

Where to find your keys

ValueWhere to find itIs it secret?
Site keySettings → Pixel SDK (agency users only). Click Show, then Copy key — or copy the whole tag.No. It is public by design. Your Allowed domains list is what protects it.
Stage IDSettings → Funnel & Data source → Funnel. Open the stage: the ID is at the top of the panel, and at the end of the stage’s webhook URL.Not a password, but keep it private: a server can post events to a stage with only its ID (see Webhook API).
Stage tokenThe same stage panel, under Webhook endpoint. The app shows only its first 8 characters, so ask your Pruuv contact for the full token.Not really. It sits in your page’s code, where any visitor can read it. Your Allowed domains list is what protects browser requests.

Tag settings

Set these as attributes on the tag, or in window.PruuvConfig before the script loads. When both set a value, the tag attribute wins. For consent mode, either one turns it on. Add the tag only once per page.

Tag attributewindow.PruuvConfigWhat it does
data-site-keysiteKeyYour public site key. page() and identify() need it. track() does not.
data-consent="required"requireConsent: trueTurns on consent mode: nothing is saved or sent until you call Pruuv.consent(true).
data-api-hostapiHostWhere the pixel sends its data, for example https://ai.pruuv.com. By default this is the host the script was loaded from, so set it only if you host pixel.min.js yourself.
html
<script>
  window.PruuvConfig = { siteKey: 'YOUR_SITE_KEY', requireConsent: true };
</script>
<script async src="https://ai.pruuv.com/pixel.min.js"></script>

API reference

The script adds one object, window.Pruuv, with seven methods. It only exists once the script has loaded, so write window.Pruuv?.identify(…) in code that might run earlier. The methods catch network errors and never report the server’s answer, so check it in the Network tab (see Troubleshooting).

Pruuv.page()

Records a page view. It takes no arguments and needs the site key. In a single-page app, call it after each route change (see Single-page apps). Each call sends:

FieldWhere it comes from
host, pathThe page’s host name, and its path including the query string.
referrerdocument.referrer, when there is one.
anonymous_idThe visitor ID in the _pruuv_vid cookie.
session_idThe session ID in the _pruuv_sid cookie.
utm_source, utm_medium, utm_campaign, utm_content, utm_termThe last-touch UTM tags saved in _pruuv_session.
gclid, wbraid, gbraid, ttclid, msclkid, li_fat_id, pruuv_aid, fbc, fbpThe saved click IDs, so a page view carries its ad source.
user_agent, screen_width, screen_height, viewport_width, viewport_height, device_pixel_ratioThe browser, and its screen and window size.
occurred_atThe time of the call, from the browser.
ip_addressAdded by Pruuv from the request. Never read from the page.
geo_city, geo_region, geo_countryAdded by Pruuv from the CDN’s IP lookup. Used only to fill the city, state or country your CRM left empty, for Meta’s match keys, and always hashed before it is sent.

Page views go to /api/pixel/collect. Pruuv uses them for the checks in Data Health → Pixel. For visitors you identify, it also uses them to fill in the click IDs, browser details, location and page URL their leads are missing. Page views themselves are never sent to the ad platforms.

Pruuv.identify(email, options)

ParameterTypeWhat it does
emailstring (required)The visitor’s email address. Lowercased, trimmed and hashed (SHA-256) before it is sent. Never pass a CRM ID here.
options.referencestringYour CRM’s ID for this person. Sent lowercased and trimmed, so a lead that arrives with this ID instead of an email still matches. Recommended. If identify() runs after your CRM sends the lead, this ID only matches when your CRM sends it in lowercase, with no spaces around it.
options.phonestringNot sent by identify(). Kept for later track() calls, which hash it. Use E.164 format, for example +441234567890.
options.event_idstringThe event ID your pixel used for this lead: the eventID you passed to fbq(). Pruuv sends it to Meta and TikTok as the lead’s event ID, so they count the lead once instead of twice. Sent exactly as given. identify() only covers the first stage of your funnel; for a later event, send event_id with track() or your webhook post. See Stop Meta and TikTok counting an event twice.
options.<any other key>stringKept in the _pruuv_identity cookie only. Never sent.

identify() sends the email hash, the reference and the event ID, together with the click IDs saved in this browser, the first-touch data and the browser’s user agent. Pruuv adds the IP address and approximate location. It keeps one record per visitor ID (the _pruuv_vid cookie): a later call updates the values it carries, but never clears a click ID saved earlier.

It also saves the email and options in the _pruuv_identity cookie, so later track() calls can use them. It returns nothing, so do not await it. Call it once per form, not on every page.

Pruuv.track(stageId, token, options)

ParameterTypeWhat it does
stageIdstring (required)The ID of the funnel stage this conversion belongs to.
tokenstring (required)That stage’s webhook token. A token only works for its own stage.
options.referencestringYour ID for this conversion, such as a CRM contact or deal ID. See “How the reference is chosen” below.
options.order_idstringUsed as the reference when reference is not set.
options.item_referencestringA second ID, such as an order or claim ID. Required when the stage has Item reference turned on.
options.event_idstringThe event ID your pixel fired this event with, for example the eventID you passed to fbq(). Pruuv sends it to Meta and TikTok as the event ID, so they count the event once. Sent exactly as given, up to 256 characters. Use a new ID for every event. See Stop Meta and TikTok counting an event twice.
options.emailstringLowercased, trimmed and hashed (SHA-256), then sent as email_hash. Defaults to the email given to identify().
options.phonestringSpaces removed, then hashed (SHA-256) and sent as phone_hash. Defaults to the phone given to identify(). Use E.164 format.
options.valuenumberThe conversion value, sent as revenue_value. Must be 0 or more.
options.currencystringA 3-letter ISO 4217 code, such as GBP. Any other length is rejected.
options.event_namestringA label stored with the event, such as Lead or Sale.
options.<any other key>string | number | booleanSent as you give it, and replaces any value the pixel captured under the same name (for example gclid). Fields the Webhook API knows are handled as described there. Keys declared as custom attributes are stored on the event. Other keys are kept only in the stored copy of the request.

Along with your options, track() sends the saved UTM tags (including utm_matchtype), the click IDs and click time, the first-touch data when it differs, the browser’s user agent, and the page URL without its query string (event_source_url). It also sends identity_user_id: the reference given to identify(), or the email when there was no reference.

How the reference is chosen. track() uses the first of these that is set: options.reference → options.order_id → the reference given to identify() → the email given to identify() → pruuv_<timestamp>.

Always pass a reference. Without one, the visitor’s email becomes the event’s reference and is sent and stored in plain text. With no email either, every call gets a new pruuv_… reference, so your CRM cannot match it and repeated calls are not caught as duplicates.

Duplicates. Pruuv keeps one event per stage and reference (and item_reference, when set). This is permanent, not a time window. A repeat call returns duplicate: true. It never creates a second event. It sends nothing itself, and never re-sends a conversion that was already sent. It only fills in fields the stored event is missing, such as a click ID, email hash, browser details, value or currency. It never adds UTM tags, so it cannot get the event matched to an ad. If the event has not been sent yet, the new fields go with it when it is. Give each real conversion its own reference, or use item_reference for a second order from the same person.

Responses. track() posts to /api/webhooks/funnel/{stageId}. The pixel does not read the answer, so check it in the Network tab or in Data Health → Live:

StatusBodyMeaning
200{ received: true, event_id, status: "accepted" }Stored as a new event.
200{ received: true, duplicate: true, status: "duplicate" }This stage already has an event with this reference. Nothing changed.
200{ received: true, duplicate: true, status: "enriched" }Already stored. Pruuv filled in fields the stored event was missing. If it was already sent, it is not sent again. If not, the new fields go with it when it is sent.
400{ error: "Validation failed", details }The body is invalid: for example, a currency that is not 3 letters, or no item_reference on a stage that needs one.
401{ error: "Unauthorized" }The token is missing, or it is not this stage’s token.
403{ error: "Forbidden origin" }The page’s domain is not in Allowed domains, or the pixel is disabled.
404{ error: "Stage not found" }No stage has this ID. Pruuv logs nothing, so you only see this in the Network tab.
429{ error: "Rate limit exceeded" }This stage has reached its limit of 1,000,000 requests an hour.
500{ error: "Internal error" }Pruuv could not save the event, and it is lost. It does not show in Data Health → Live, so you only see it in the Network tab. One cause: a pruuv_aid in the URL that Pruuv did not give you.

Pruuv.visitorId()

Returns the visitor ID, the value of the _pruuv_vid cookie, and creates the cookie if it does not exist yet. It creates no other cookie. It returns null before consent (in consent mode), for a bot, and on a page that is not served over https, where the browser cannot make the ID.

Put it in a hidden field of your form, and have your CRM send it with the lead as pruuv_visitor_id (see Webhook API). Pruuv then finds this browser’s click IDs even when the lead’s email differs from the one identify() saw.

CallWhat it does
Pruuv.consent(true)Saves the visitor’s consent for 90 days, saves the click data held from this page, and sends the waiting calls. Does nothing if the browser has Do Not Track turned on.
Pruuv.consent(false)Saves the refusal for 90 days and drops the waiting calls. Cookies that already exist are kept.
Pruuv.optOut()Saves the refusal, drops the waiting calls, and deletes the other seven cookies: _pruuv_session, _pruuv_first_touch, _pruuv_identity, _pruuv_vid, _pruuv_sid, _fbp and _fbc, and their saved copies. An _fbp or _fbc that Meta’s own pixel set for your whole domain is not deleted.
Pruuv.reset()Forgets the identify() data and deletes the _pruuv_identity cookie. Use it when a user logs out. Click IDs and the visitor ID stay.

Examples

A form whose leads your CRM sends to Pruuv

html
<form id="lead-form">
  <input name="email" type="email" required>
  <button type="submit">Get a quote</button>
</form>

<script>
  document.getElementById('lead-form').addEventListener('submit', function () {
    var email = this.querySelector('[name="email"]').value;
    // Your CRM sends this lead to Pruuv with the same email
    if (window.Pruuv) window.Pruuv.identify(email);
  });
</script>

If the page knows your CRM’s ID for the lead, pass it too: identify(email, { reference: leadId }).

A form that also reports the conversion

javascript
async function handleSubmit(data) {
  window.Pruuv?.identify(data.email, { reference: data.leadId });

  const result = await submitForm(data);
  if (result.ok) {
    window.Pruuv?.track('YOUR_STAGE_ID', 'YOUR_STAGE_TOKEN', {
      reference: data.leadId,
      value:     250,
      currency:  'GBP'
    });
  }
}

Stop Meta and TikTok counting an event twice

If you also run the Meta pixel (or the TikTok pixel) and fire an event on your site, the platform gets it twice: once from your pixel and once from Pruuv. Give both the same event ID and the platform keeps only one (Meta’s guide, TikTok’s guide):

javascript
function handleSubmit(email) {
  var eventId = 'lead-' + crypto.randomUUID();
  fbq('track', 'Lead', {}, { eventID: eventId });
  window.Pruuv?.identify(email, { event_id: eventId });
}
  • The event names must match too. The platform only merges two events with the same event ID and the same name. Your pixel’s event (Lead above) must be the event that stage is mapped to in Pruuv.
  • Use a new event ID for every event. identify() carries the ID of the lead itself (your first stage). For a later event, such as a signature, fire your pixel with a new ID and send that ID with the same event: Pruuv.track(stageId, token, { event_id: eventId }), or event_id in your CRM’s webhook post for that stage.
  • One ID belongs to one lead. If the same ID reaches Pruuv on several events, only the earliest one sends it. The others go with Pruuv’s own ID.
  • Claims and other items. If your site fires one event per claim with the claim number as its event ID, set that destination’s Event ID to Item reference in Pruuv. Pruuv then sends each event’s item_reference as the event ID. The text must be exactly the same. Two stages that use Item reference on one ad account need different event names.
  • Send the ID before Pruuv sends the event. The platform merges the two only within 48 hours, and Pruuv can only use an event ID that reaches it before the event is sent. So call identify() in the submit handler, or send event_id in the same request as the event.
  • Pruuv never creates an event ID for you. Without one, events go with Pruuv’s own ID, as before.

Single-page apps

A route change inside a single-page app does not reload the page, so the tag’s onload fires only once. Call Pruuv.page() from your router after each route change. The pixel already re-reads the URL for UTM tags and click IDs on pushState, replaceState, popstate and hashchange. For the Next.js App Router:

jsx
'use client'

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

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

  useEffect(() => {
    // The tag's onload already recorded the first page
    if (isFirstPage.current) {
      isFirstPage.current = false
      return
    }
    window.Pruuv?.page()
  }, [pathname, searchParams])
}

Google Tag Manager

Paste the tag, exactly as copied from Pruuv, into a Custom HTML tag that fires on all pages. Then add a second Custom HTML tag for identify(), fired by your form-submit trigger:

html
<script>
  if (window.Pruuv) {
    window.Pruuv.identify('{{DLV - email}}', { reference: '{{DLV - lead_id}}' });
  }
</script>
  1. A visitor clicks an ad and lands on your site. The pixel saves the click IDs.
  2. They fill in your form. identify() sends their email hash and your reference to Pruuv, with the click IDs.
  3. Your CRM sends the lead to Pruuv with the same email or reference, and with the lead’s UTM tags (see Webhook API).
  4. When the lead carries a visitor ID (pruuv_visitor_id, see Pruuv.visitorId()), Pruuv tries that browser first. It skips the browser if it was last identified with another email, as on a shared computer. Then Pruuv finds the visitor by email hash, then by reference. It adds the click IDs, browser details and location the lead is missing. It never overwrites what your CRM sent.

This also works across devices. If the visitor clicked the ad on their phone and identify() ran there, a lead with the same email gets the phone’s click ID.

Pruuv does this when the lead arrives, and again every 30 minutes for leads up to 7 days old. So a late identify() still counts, if the lead has the same email, the same reference in lowercase, or the browser’s visitor ID. But Meta and TikTok may already have received the conversion without the click ID, which gives a weaker match. Call identify() first.

What the pixel saves from the URL

On every page load, and on every route change inside the page, the pixel reads the URL. It saves the UTM tags utm_source, utm_medium, utm_campaign, utm_content, utm_term and utm_matchtype, and sends them with track() calls (see Attribution). It does not add them to the leads your CRM sends. It also saves these click IDs:

URL parameterPlatformSaved inWhat Pruuv uses it for
gclid, wbraid, gbraidGoogle Ads_pruuv_sessionMatching the conversions Pruuv sends to Google Ads.
fbclidMeta_fbc, as fb.1.<time>.<fbclid>Matching the conversions Pruuv sends to Meta’s Conversions API.
— (no URL parameter)Meta_fbpMeta’s browser ID, used for matching. The pixel creates it, or reuses one that Meta’s own pixel set.
ttclidTikTok_pruuv_sessionMatching the conversions Pruuv sends to TikTok’s Events API.
li_fat_idLinkedIn_pruuv_sessionStored only. Sending conversions to LinkedIn is not available yet.
msclkidMicrosoft Ads_pruuv_sessionStored only. Pruuv does not send conversions to Microsoft Ads.
pruuv_aidPruuv_pruuv_sessionStored only. Attribution does not use it yet. Use only a pruuv_aid that Pruuv gave you. Any other value makes that visitor’s track() calls fail, and stops Pruuv adding their click IDs and browser details to your leads.

Click IDs are only in the URL of the first page after the ad click. So the tag must be on your landing pages, not only on the thank-you page.

Conversions need UTM tags that match an ad. Pruuv sends a conversion only when the lead’s own UTM tags match it to one of your ad accounts. utm_source must name the platform. For Meta and TikTok, the tags must carry the ad ID. For Google Search, the ad group ID. For Performance Max and other campaign-level Google types, the campaign ID and a utm_medium that names the type. A click ID, email or phone alone is not enough. If your CRM sends the lead, it must send these tags. Attribution lists the tags for each platform; Conversion sending has the full rules.

First touch and last touch

SignalCookieHow it behaves
Last touch_pruuv_sessionReplaced by the newest landing URL that carries any UTM tag or click ID. Values the new URL does not carry are dropped. Sent with every track() call.
First touch_pruuv_first_touchThe first landing that carried a UTM tag or click ID. Written once and never changed. Sent as first_touch with track() and identify() when it differs from the last touch.

Conversions use the last touch. The first touch is stored with the event.

Cookies

All cookies are first-party. The pixel sets them with JavaScript on the page’s own host (with no Domain attribute), with path=/ and SameSite=Lax.

CookieWhat it holdsLifetime asked for
_pruuv_sessionThe last-touch UTM tags and click IDs, and when they were seen.90 days
_pruuv_first_touchThe same data from the first tracked visit. Written once.90 days
_pruuv_identityThe email and options given to identify(), in plain text.90 days
_pruuv_consentgranted or denied. Written only by consent() or optOut().90 days
_pruuv_vidA random visitor ID. Pruuv.visitorId() returns it.90 days
_pruuv_sidA random session ID.Until the browser closes
_fbpMeta’s browser ID. Reused if one already exists.90 days
_fbcMeta’s click ID, built from fbclid.90 days

_pruuv_vid, _pruuv_sid and _fbp are created by the first page(), identify() or track() call, not when the script loads. visitorId() creates _pruuv_vid only.

Browsers can shorten these lifetimes. Safari keeps cookies set by JavaScript for at most 7 days. It keeps them for only 24 hours when the visitor arrives from a site Safari treats as a tracker, through a link with query parameters, which is how many ad clicks arrive. Click IDs that identify() already sent to Pruuv are not affected.

Saved copies in localStorage

The pixel also saves a copy of _pruuv_session, _pruuv_first_touch, _pruuv_vid, _fbp and _fbc in the browser’s localStorage, under keys that start with _pruuv_mirror.. Each copy has the same expiry as its cookie. When one of those cookies is missing on page load and its copy has not expired, the pixel writes the cookie back from the copy, keeping the original expiry.

This keeps click IDs for visitors who convert a few days after the click. Safari deletes localStorage only after 7 days pass with no visit to the site, so each visit restarts that clock. A visitor who comes back after a longer gap has no saved click IDs.

  • A cookie that exists always wins. If Meta’s own pixel has written a newer _fbc or _fbp, the copy is updated to match it.
  • The copies are written and restored only when consent allows, and optOut() deletes them.
  • _pruuv_identity, _pruuv_sid and _pruuv_consent are never copied.

Cookies and localStorage both belong to one host. Data saved on www.example.com is not visible on apply.example.com, so a visitor who lands on one host and converts on another has no click IDs on the second.

By default the pixel does not wait for consent: it writes cookies and sends calls straight away. The tag you copy from Pruuv does not turn consent mode on. If the browser sends Do Not Track, the pixel writes no cookies and sends nothing, even after consent(true).

To wait for consent, add data-consent="required" to the tag (or set requireConsent: true). Then:

  • Nothing is written or sent until you call Pruuv.consent(true). Until then, the page’s click data and any page(), identify() and track() calls wait in memory.
  • If the visitor opens another page before accepting, the waiting data is lost, including the landing page’s click IDs.
  • The choice is saved in _pruuv_consent for 90 days and applies to later pages.
javascript
// The visitor accepts
Pruuv.consent(true);

// The visitor declines
Pruuv.consent(false);

// The visitor withdraws consent later: this also deletes the pixel's cookies
Pruuv.optOut();

Security and limits

  • The site key is public. Your Allowed domains list is what stops other websites from using it.
  • A track() call from a browser must carry the stage token and come from an allowed domain. The token is visible in your page, so the domain list is the real protection.
  • Requests from a server (with no Origin header) need no token: see Webhook API → Authentication. Keep your stage IDs private.
  • Rotate site key (in Settings → Pixel SDK, agency admins only) stops page() and identify() at once on every tag that uses the old key. Update your tags straight after. It does not stop track(), which uses the stage token. To stop a stage’s browser track() calls, an agency admin can click Rotate secret on that stage. Server posts need no token, so they keep working.
  • Disable pixel (agency admins only) stops page views, identify() calls and browser track() calls.
  • Each site key and each stage can receive 1,000,000 requests an hour. This is a safety limit, not a quota you should expect to reach.

Privacy

  • identify() and track() hash the email and phone (SHA-256) in the browser before sending them.
  • The _pruuv_identity cookie keeps the email and identify() options in plain text in the visitor’s browser for up to 90 days. Other scripts on your page can read it.
  • If you pass no reference, the email becomes the event’s reference and is sent and stored in plain text. Pass a reference to avoid this.
  • Other fields you pass to track() are sent as you give them. Pruuv hashes the personal fields it knows (see Webhook API), but keeps a copy of each request.
  • Page views store the visitor’s IP address, user agent, approximate location and the full page URL, including its query string. Do not put personal data in your URLs.
  • The pixel only talks to Pruuv. It loads no other scripts and sets only first-party cookies. Pruuv sends a conversion only to the connected ad account the lead was matched to (see Conversion sending).

Browser support

  • The page must use HTTPS (or run on localhost). On plain HTTP, browsers block the crypto functions the pixel needs, so nothing is sent.
  • It needs a recent browser. It uses crypto.randomUUID(), which arrived in Chrome and Edge 92, Firefox 95 and Safari 15.4.
  • It ignores bots and automated browsers. Playwright, Puppeteer and Selenium set navigator.webdriver, so nothing is sent from them. Test in a normal browser window.

Troubleshooting

Check the requests

Open your browser’s developer tools, go to the Network tab and load the page. Look for these requests to https://ai.pruuv.com:

RequestSent bySuccess
POST /api/pixel/collectPruuv.page()200 { received: true }
POST /api/pixel/identifyPruuv.identify()200 { received: true }
POST /api/webhooks/funnel/<stage ID>Pruuv.track()200, with status "accepted", "duplicate" or "enriched"

Errors from page views and identify():

StatusBodyMeaning
400{ error: "Validation failed", details }The request is invalid. For identify(), this is usually a missing visitor ID because the browser blocks cookies. A site key in the wrong format, such as the YOUR_SITE_KEY placeholder, also gives 400.
403{ error: "Forbidden origin" }The page’s host is not in Allowed domains. Subdomains of a listed domain are allowed.
404{ error: "Unknown or inactive site" }The site key is wrong or was rotated, or the pixel is disabled.
429{ error: "Rate limit exceeded" }This site key has reached its limit of 1,000,000 page views (or identified visitors) an hour.
500{ error: "Internal error" }Pruuv could not save the request.

For track() errors, see the responses under Pruuv.track().

No request at all

  • The Console shows [Pruuv] siteKey is required for page() (or for identify()): the tag has no data-site-key.
  • The Console shows [Pruuv] stageId and token are required for track(): an argument is missing.
  • The Console shows Pruuv is not defined: the call ran before the script loaded, or the script did not load. Use window.Pruuv?. and check the script URL.
  • The browser has Do Not Track turned on, or the visitor declined consent.
  • Consent mode is on and Pruuv.consent(true) has not been called yet.
  • The browser is automated, or looks like a bot.
  • The page uses plain HTTP. The Console shows an error about randomUUID or digest.

Check in Pruuv

  • Data Health → Pixel shows when each site last sent a page view, and how many CRM leads were linked to an identify() call.
  • Data Health → Live shows track() requests as they arrive, including most rejected ones (wrong token, domain not allowed, invalid body).
  • Page-view and identify() errors are not shown in Pruuv. Use the Network tab.

Leads arrive without click IDs

  • The tag must be on the landing pages your ads link to.
  • Call identify() on every form, before your CRM sends the lead, and make sure the lead carries the same email or reference.
  • The visitor moved to another host (cookies belong to one host), or came back after Safari removed the cookies.
  • In consent mode, the visitor opened another page before accepting.

Low Meta match rate

  • Call identify() before your CRM sends the lead, so the lead gets the fbc, fbp, IP address and user agent.
  • Send the phone number with each lead: from your CRM, or in track(). Use E.164 format.
  • Make sure the tag is on your landing pages, so fbclid is saved as _fbc.