All articles

Lead form conversions — one per lead (deduplication)

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).

  1. 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 on event_data)
    • session_id + form_id when both are present
  2. ClickHouse check — Before uploading, the API looks for an older row (any uts_events.id except 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
    • and matching lead identity using the OR rules above.
  3. 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)

PlatformFieldPurpose
Google AdsorderId on the click conversionStable per logical lead; helps Google treat repeats as the same conversion where supported.
Meta CAPIevent_id on the Lead server eventSame id when the same lead repeats; Meta can dedupe overlapping browser + server events when both use event_id.
GA4 MPtransaction_id on generate_leadSame 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.

Related