leadflow

capture → identity → attribution → routinginteractive demo · synthetic data

One prospect, four channels, one CRM record — and every rupee of ad spend attributed to the click that earned it.

This is a working lead pipeline. Fire the synthetic events below one at a time and watch each one get validated, attributed to a canonical source, matched against the contact book, and routed — with the full reasoning printed at every step.

Inbound events

0/12 fired
  • Priya Raghavan
    paid Google click · founder
    web form
  • Priya Raghavan
    same person, back via LinkedIn
    web form
  • Arjun Mehta
    gmail with dots + tag
    web form
  • Arjun Mehta
    same gmail, different spelling
    web form
  • Kavitha Nair
    phone only, no email
    WhatsApp
  • Kavitha N.
    same phone, now with email
    web form
  • Daniel Osei
    Lead Gen Form, zero UTMs
    LinkedIn LGF
  • Sofia Lindqvist
    forwarded link, ChatGPT referrer
    web form
  • BestSEO Services
    bot — hidden field filled
    web form
  • test
    garbage input
    web form
  • Rohit Bansal
    trade-fair booth list
    CSV import
  • Grace Whitfield
    paid Meta, no role data
    web form

Pipeline trace

0 created · 0 merged · 0 rejected · 0 alerts
Fire an event from the queue — every lead prints its full audit trail here: validation, source attribution, identity resolution, routing.

Contact book

0 contacts
ContactReachFirst touchTouchesQueue
No contacts yet — the book fills as events clear the pipeline.

The attribution difference

Without resolution

Bucketed by whatever the form happened to carry. Paid spend vanishes into “direct / unknown”.

Nothing yet — fire some events.

With resolution

One canonical source per touch, decided by signal priority: click ID → UTM → channel → referrer.

Nothing yet — fire some events.

The business story

Why this system exists

A B2B company I ran marketing for captured leads from a website form, LinkedIn Lead Gen Forms, WhatsApp, and event lists. Before this pipeline, that meant four disconnected streams: the same prospect arriving twice became two records, sales followed up on stale duplicates, and the channel report credited most revenue to “direct” — because the form couldn't see the ad click that actually started the relationship. You cannot decide where next month's budget goes when your best channel is invisible.

What I built (the production original)

A single serverless capture endpoint behind every channel: rate-limited, honeypot-guarded, input-sanitized. Each lead got one canonical source, resolved by strict signal priority — ad-platform click IDs beat UTMs, UTMs beat referrers — then flowed to the CRM as the primary system, with a database shadow copy, an instant email alert to sales, and server-side conversion events back to the ad platforms so bidding learned from real leads instead of ad-blocked pixels. It ran in production on a live company website; the alert-to-follow-up gap is where deals actually started.

The judgment calls

What went wrong along the way

The first version trusted UTMs completely — until a paid lead arrived through a forwarded link tagged utm_source=newsletter and the spend report under-counted the ad channel that had actually paid for the click. That failure produced the strict signal-priority ladder this demo runs. The honeypot's randomized-delay fake success came from watching bots adapt to instant rejections.

What I'd change today

Server-side identity should extend beyond email/phone with a privacy-safe fingerprint for the anonymous-visit-to-form gap, and the routing rules belong in versioned configuration a marketer can edit without a deploy — both are noted in the repo as deliberate limitations of this reconstruction.