PruuvDocs

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.

Important: Server-side integrations cannot automatically capture 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, use the Pixel SDK on your landing pages and include the cookie values in your server-side payload.

Authentication

All requests must include a Bearer token in the Authorization header. The token is the Webhook Token shown alongside each funnel stage in Connections > Funnel Webhooks.

Endpoint

bash
POST https://app.pruuv.io/api/webhooks/funnel/{stageId}
Authorization: Bearer YOUR_WEBHOOK_TOKEN
Content-Type: application/json

Example request

bash
curl -X POST https://app.pruuv.io/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.

FieldTypeDescription
referencestringRequired. Unique identifier for this contact or conversion — CRM contact ID, deal ID, etc. Used for deduplication and enrichment merge.
item_referencestringSecondary reference — deal ID, claim number, order ID. Required for stages that enforce item-level dedup.
event_namestringLogical event label stored against the event — e.g. "Lead", "Sale", "Appointment".
utm_sourcestringUTM source parameter
utm_mediumstringUTM medium parameter
utm_campaignstringUTM campaign parameter
utm_contentstringUTM content parameter — used for ad-level attribution join (stores ad ID).
utm_termstringUTM term parameter — stores keyword for Google Search / Bing keyword-level attribution.
utm_matchtypestringKeyword 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.
gclidstringGoogle click ID — required for Google Conversion Upload API
wbraidstringGoogle web-to-app click ID (iOS, privacy-preserving)
gbraidstringGoogle app-to-web click ID (iOS, privacy-preserving)
ttclidstringTikTok click ID — required for TikTok Events API
msclkidstringMicrosoft/Bing click ID
fbcstringMeta _fbc cookie value — captured automatically by the Pruuv Pixel from fbclid
fbpstringMeta _fbp cookie value — generated by the Pruuv Pixel on first visit
click_timestampISO datetimeTime of the original ad click. Required for Google Conversion Upload API (conversion must postdate the click).
email_hashstringSHA-256 of the lowercased email address. Used for Meta CAPI audience matching. The Pruuv Pixel hashes this in-browser automatically.
phone_hashstringSHA-256 of the phone number in E.164 format. Used for Meta CAPI and TikTok audience matching.
pruuv_aidstringPruuv native attribution ID — captured from URL parameter by the pixel. Used for ad-level attribution without relying on platform click IDs.
identity_user_idstringVisitor identity from Pruuv.identify() — enables cross-session matching.
revenue_valuenumberConversion value — sent to ad platforms as conversion value
currencystringISO 4217 currency code (e.g. GBP, USD, EUR)
data_timestampISO datetimeWhen the conversion occurred in your CRM. Defaults to now if omitted.
data_timezonestringIANA timezone name (e.g. "Europe/London") — used to interpret naive timestamps from CRMs that send local time.
first_touchobjectFirst-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.
genderstringDemographic — for reporting only
age_rangestringDemographic — for reporting only (e.g. "25-34")
location_countrystringDemographic — ISO 3166-1 alpha-2 country code
location_regionstringDemographic — region or state
job_titlestringDemographic — for reporting only
company_sizestringDemographic — for reporting only
industrystringDemographic — for reporting only
<your custom key>string | number | booleanAny 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.
Custom attributes: send extra keys alongside the built-in fields — for example a car-finance CRM posting "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

StatusBodyMeaning
200{ received: true, event_id: "..." }Event accepted and stored
200{ received: true, duplicate: true }Duplicate — same reference at same stage within 60 seconds
400{ error: "Validation failed", details: {...} }Invalid payload — check field types
401{ error: "Unauthorized" }Missing or incorrect bearer token
404{ error: "Stage not found" }stageId does not exist
429{ error: "Rate limit exceeded" }More than 1,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.

Enrichable fields: fbc, fbp, gclid, wbraid, gbraid, ttclid, msclkid, click_timestamp, email_hash, phone_hash, user_agent, revenue_value, currency, identity_user_id, first_touch.

Integration paths compared

PathGoogle (gclid)Meta (fbc/fbp)TikTok (ttclid)Effort
Pruuv Pixel (recommended)Auto-capturedAuto-capturedAuto-capturedPaste snippet + call track()
Backend webhookMust capture yourself (hidden field)Requires JS on landing pageRequires JS on landing pageSelf-implement capture + POST
CRM pull (coming soon)Available if CRM stores gclidRarely stored in CRMsRarely stored in CRMsCRM config only