When the same person triggers LeadFormSubmitted more than once (thank-you page refresh, multi-step flows, tracker retries), the ingestion server should not send multiple “submit lead form” conversions to Google Ads / Meta for that logical lead.
What the server does
Applies only to event_name === LeadFormSubmitted (not phone or email CTA events).
-
Build a stable lead key from any available signals (in priority order used for matching):
- normalized email
- last 10 digits of phone
gclid,wbraid,gbraid(from attribution / stored onevent_data)session_id+form_idwhen both are present
-
ClickHouse check — Before uploading, the API looks for an older row (any
uts_events.idexcept the current one) with:- same
site_domain event_name = LeadFormSubmitted- successful upload already recorded:
- Google Ads:
google_capi_status = sent_gads - Meta CAPI:
meta_capi_status = sent_meta
- Google Ads:
- and matching lead identity using the OR rules above.
- same
-
If a match exists, the new row does not call the Ads upload / Meta Lead send for that destination. Status is set to:
skipped_gads_duplicate_lead— Google Ads offline conversion skipped (lead already counted).skipped_meta_duplicate_lead— Meta Lead skipped (lead already counted).
Identifiers sent to platforms (alignment)
| Platform | Field | Purpose |
|---|---|---|
| Google Ads | orderId on the click conversion | Stable per logical lead; helps Google treat repeats as the same conversion where supported. |
| Meta CAPI | event_id on the Lead server event | Same id when the same lead repeats; Meta can dedupe overlapping browser + server events when both use event_id. |
| GA4 MP | transaction_id on generate_lead | Same stable token when present, so reporting can align duplicates. |
Implementation lives in server/lib/leadConversionDedupe.js and is invoked from server.js inside forwardToGoogleAds, forwardToMetaCAPI, and forwardToGA4.
When deduplication does not run
If no identity signal is available (no email/phone, no click IDs, no session+form pair), the server cannot correlate multiple rows to one lead for skipping. It still generates a per-event fallback id where needed so uploads remain well-formed.