Webhook API
If you prefer to send conversion events from your server rather than the browser, you can POST directly to the Pruuv webhook endpoint. This is useful when you already have lead data in a CRM or backend system and want to forward it to Pruuv without adding JavaScript to your pages.
fbc / fbp (Meta cookies) or ttclid (TikTok click ID) — these can only be read from the browser at the moment of the ad click. For best Meta and TikTok match rates, install the Pixel SDK on your landing pages and call Pruuv.identify()when the visitor fills in your form. When your server then sends the lead with the same email or reference, or with the browser's visitor ID, Pruuv adds the browser's click IDs to it.Authentication
Pruuv checks the token only on requests that come from a browser — any request with an Origin header, such as a Pruuv.track()call. Those must send the stage's token as Authorization: Bearer …, and must come from a domain in the Allowed domains list under Settings → Pixel SDK (when the list is not empty).
Requests from a server have no Origin header. Pruuv accepts them without checking a token: the stage ID in the URL is all they need, and only the hourly rate limit applies. So treat your stage IDs as private. Sending the token from your server anyway is fine.
Each stage has its own token. Find it in Settings → Funnel & Data source → Funnel: open the stage and look under Webhook endpoint. The app shows only its first 8 characters, so ask your Pruuv contact for the full token.
Endpoint
POST https://ai.pruuv.com/api/webhooks/funnel/{stageId}
Content-Type: application/json
Authorization: Bearer YOUR_WEBHOOK_TOKEN # checked on browser requests onlyExample request
curl -X POST https://ai.pruuv.com/api/webhooks/funnel/YOUR_STAGE_ID \
-H "Authorization: Bearer YOUR_WEBHOOK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reference": "CRM-REF-001",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "brand-search",
"gclid": "EAIaIQobCh...",
"revenue_value": 250,
"currency": "GBP"
}'Payload reference
All fields are optional except reference. An optional field may be omitted, sent as null, or sent as an empty string — all three mean “not provided” and are stored as no value. reference itself must be a non-blank string.
| Field | Type | Description |
|---|---|---|
| reference | string | Required. Unique identifier for this contact or conversion — CRM contact ID, deal ID, etc. Used for deduplication and enrichment merge. |
| item_reference | string | Secondary reference — deal ID, claim number, order ID. Required for stages that enforce item-level dedup. |
| event_id | string | The event ID your browser 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 (a longer one is ignored and the event is still stored). Use a new ID for every event. Not the same as the event_id in the response, which is Pruuv’s own ID. |
| event_name | string | Logical event label stored against the event — e.g. "Lead", "Sale", "Appointment". |
| utm_source | string | UTM source parameter |
| utm_medium | string | UTM medium parameter |
| utm_campaign | string | UTM campaign parameter |
| utm_content | string | UTM content parameter — used for ad-level attribution join (stores ad ID). |
| utm_term | string | UTM term parameter — stores keyword for Google Search / Bing keyword-level attribution. |
| utm_matchtype | string | Keyword match type — the {matchtype} ValueTrack code: b (broad), p (phrase), e (exact), a (AI Max). Captured by the Pruuv Pixel. Drives the match-type breakdown; AI Max (a) is shown on its own axis. |
| gclid | string | Google click ID — what Google Conversion Upload matches on (for a lead dated from 22 Oct 2026, a hashed email or phone can stand in) |
| wbraid | string | Google web-to-app click ID (iOS, privacy-preserving) |
| gbraid | string | Google app-to-web click ID (iOS, privacy-preserving) |
| ttclid | string | TikTok click ID — improves the TikTok Events API match; a hashed email or phone also works |
| msclkid | string | Microsoft/Bing click ID |
| fbc | string | Meta _fbc cookie value — captured automatically by the Pruuv Pixel from fbclid |
| fbp | string | Meta _fbp cookie value — set by the Pruuv Pixel (or by Meta’s own pixel) |
| li_fat_id | string | LinkedIn click ID — captured by the Pruuv Pixel from the li_fat_id URL parameter. Stored with the event. |
| click_timestamp | ISO datetime | Time of the original ad click. Optional — when present, Pruuv keeps the conversion time it reports to Google after the click. |
| email_hash | string | SHA-256 of the trimmed, lowercased email address. Used for Meta CAPI audience matching. The Pruuv Pixel hashes this in-browser automatically. |
| phone_hash | string | SHA-256 of the phone number in E.164 format. Used for Meta CAPI and TikTok audience matching. The Pruuv Pixel only removes spaces before hashing, so give it E.164. |
| pruuv_aid | string | Pruuv native attribution ID — captured from the URL by the Pruuv Pixel. Stored with the event; attribution does not use it yet. |
| pruuv_visitor_id | string | The visitor ID of the browser the lead’s form was sent from: Pruuv.visitorId(), carried by a hidden form field to your CRM. Pruuv tries that browser’s click IDs first. Letters, digits, dots, underscores and hyphens, up to 128 characters; another value is ignored and the event is still stored. |
| identity_user_id | string | The reference given to Pruuv.identify() — or the email, when there was no reference. Sent by the Pruuv Pixel with track(), and sent to Meta (hashed) as external_id. |
| revenue_value | number | Conversion value — sent to ad platforms as conversion value |
| currency | string | ISO 4217 currency code (e.g. GBP, USD, EUR) |
| data_timestamp | ISO datetime | When the conversion occurred in your CRM. Defaults to now if omitted. |
| data_timezone | string | IANA timezone name (e.g. "Europe/London") — only needed if data_timestamp has no UTC offset (e.g. "2026-08-05 14:30:00"). Pruuv resolves such naive timestamps using this field, falling back to the timezone set on your client’s Settings page, then UTC. Prefer sending an offset-aware ISO timestamp (with a trailing Z or +HH:MM) when your CRM supports it — then this field isn’t needed at all. |
| first_touch | object | First-touch attribution snapshot — same fields as the root level UTMs/click IDs. Send when the visitor's landing page visit differs from the conversion page. |
| gender | string | Demographic — for reporting only |
| age_range | string | Demographic — for reporting only (e.g. "25-34") |
| location_country | string | Demographic — ISO 3166-1 alpha-2 country code |
| location_region | string | Demographic — region or state |
| job_title | string | Demographic — for reporting only |
| company_size | string | Demographic — for reporting only |
| industry | string | Demographic — for reporting only |
| <your custom key> | string | number | boolean | Any custom attribute declared under Funnel → Custom attributes. Stored on the event — coerced to its declared type, trimmed, and lowercased when normalisation is on. Undeclared keys are kept only in the raw payload. |
"credit_band": "B". Declare the key under Funnel → Custom attributes and Pruuv stores it on every event (here as credit_band: "b" with lowercase normalisation), ready for conversion value rules. Re-posting the same reference merges new attribute keys first-write-wins — an already-stored key is never overwritten.Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | { received: true, event_id: "...", status: "accepted" } | Event accepted and stored — event_id is Pruuv’s own ID for it |
| 200 | { received: true, duplicate: true, status: "duplicate" } | Duplicate — this reference already exists at this stage (permanent, not a time window) |
| 200 | { received: true, duplicate: true, status: "enriched" } | Duplicate, but it filled in fields the stored event was missing (see below) |
| 400 | { error: "Validation failed", details: {...} } | Invalid payload — check field types, or a null/blank/missing reference |
| 401 | { error: "Unauthorized" } | Browser request (with an Origin header) with a missing or incorrect token |
| 403 | { error: "Forbidden origin" } | Browser request from a domain that is not in Allowed domains, or the Pixel SDK is disabled |
| 404 | { error: "Stage not found" } | stageId does not exist |
| 429 | { error: "Rate limit exceeded" } | More than 1,000,000 events/hour for this stage |
| 500 | { error: "Internal error" } | Server error — contact support if persistent |
Deduplication and enrichment
Events are deduplicated on the combination of stage_id + reference (+ item_reference if present) using a unique index — this is a permanent deduplication, not a time-window. Sending the same reference twice will never create two rows. This makes it safe to retry on network errors.
However, if the second event arrives with new data that the first event lacked — for example, a CRM posts UTMs first and the Pruuv Pixel fires moments later with fbc/gclid/ttclid — Pruuv performs an enrichment merge: it fills in any NULL fields on the existing row from the new payload, without overwriting anything already stored. The response is still { received: true, duplicate: true }, but the row now has both datasets. A merge never itself sends anything, and never re-sends a conversion that was already sent. If the lead has not been sent yet, the fields the merge added (a click ID or an email, say) are used when it is. A merge cannot help a lead get matched to an ad account, though: UTM parameters are not merged.
Enrichable fields: fbc, fbp, gclid, wbraid, gbraid, ttclid, msclkid, li_fat_id, click_timestamp, email_hash, phone_hash, the name, date-of-birth and postcode hashes, user_agent, event_source_url, the visitor's IP address, revenue_value, currency, identity_user_id, first_touch, event_id, pruuv_visitor_id. A repeat post that adds the visitor ID also fills in the click IDs and browser details of that browser.
To get the browser's click IDs onto the lead before the conversion is sent, call Pruuv.identify() in the browser before your server posts the lead. See Pixel SDK → How Pruuv links the pixel to your CRM.
Integration paths compared
| Path | Google (gclid) | Meta (fbc/fbp) | TikTok (ttclid) | Effort |
|---|---|---|---|---|
| Pruuv Pixel (recommended) | Auto-captured | Auto-captured | Auto-captured | Paste snippet + call identify() (and track() if no CRM sends the lead) |
| Backend webhook | Must capture yourself (hidden field) | Requires JS on landing page | Requires JS on landing page | Self-implement capture + POST |
| CRM pull (coming soon) | Available if CRM stores gclid | Rarely stored in CRMs | Rarely stored in CRMs | CRM config only |