Skip to content

Lead Capture

The lead capture system in src/lead.ts handles form submissions from marketing websites and enriches them with behavioral intelligence before forwarding to CRM.

Flow

  1. Parse — Accepts JSON, form-data, multipart, and text/plain request bodies
  2. Extract — Pulls lead fields: name, email, phone, project, location, team
  3. Enrich — Adds geolocation from Cloudflare headers (country, city, timezone, region)
  4. Detect — Checks for bot submissions using honeypot signal (__pg_hp) when BOT_LEAD_DETECTION is enabled
  5. Score — Looks up visitor's tracking session for engagement scoring (see Lead Scoring)
  6. Assess suspicion — Calculates fraud/suspicion score from honeypot, time-to-submit, and other signals (see Fraud Detection)
  7. Link projects — Finds other projects this visitor previously inquired about (see Related Projects)
  8. Resolve primary lead — Finds existing lead by project + identity (visitor_id > email > phone > IP)
  9. Store submission history — Writes every form submit to LeadSubmissions (CTA/page history)
  10. Forward — Sends enriched payload to Make.com webhook for new primary leads
  11. Store — Writes/updates canonical lead in D1 Leads table
  12. Redirect — Returns 301 redirect to the source domain's thank-you page

Lead Payload

Every lead sent to the webhook and stored in D1 includes:

CategoryFields
Contactfirst_name, phone_mobile, email, email1 (legacy compatibility), country_code
Projectproject_name, user_location, lead_source, source_team
Geouser_country, user_city, user_timezone, user_region
Scoringengagement_score, engagement_label, session_duration_seconds, pages_visited, return_visits
Fraudsuspicion_score, suspicion_reasons, time_to_submit, form_submit_count
Identityvisitor_id, session_id, vpn_suspected
Contextrelated_projects, refered_by, searched_keyword, user_agent, user_ip
Statusis_duplicate, is_bot_lead, test_lead, dev_environment

Storage Model

  • Leads stores one canonical row per user+project (primary lead record).
  • LeadSubmissions stores every submit attempt (including repeated submits from different CTAs/pages). Each row includes PayloadJson (raw form body at submit time) so the dashboard can show exactly what was submitted in each follow-up.

Dashboard

In the dashboard Leads page, when you click a primary lead the detail panel shows:

  • Follow-up history — A list of every form submission (primary + follow-ups) for that lead. Expand any row to see the data as submitted at that time: name, email, phone, project, source, CTA, and optional message. Use this to see how lead data changed across multiple submits (e.g. corrected phone number or name).
  • Activity timeline — Combined submissions and sessions with optional session replay links.

Configuration

VariableDefaultDescription
ENVIRONMENTdevelopment or production
BOT_LEAD_DETECTIONtrueEnable bot handling using honeypot detection (__pg_hp)

Webhook Integration

Leads are forwarded to Make.com for CRM integration. Two webhook endpoints are used:

WebhookTrigger
Make.comProduction non-duplicate leads
Bot webhookBot-detected leads (when BOT_LEAD_DETECTION is enabled)

Bot-detected submissions are still persisted to D1 (Leads + LeadSubmissions) for audit/history; only CRM forwarding is suppressed.

Retry Strategy

Webhook delivery uses exponential backoff retry via src/lib/webhook.ts:

  • Max retries: 3 attempts
  • Backoff: 1s → 2s → 4s between attempts
  • Timeout: 10s per request
  • 5xx responses: Retried (server error, likely transient)
  • 4xx responses: Not retried (client error, won't resolve on retry)
  • Network errors: Retried (DNS failure, connection reset, timeout)

Dead Letter Queue

On final failure (all retries exhausted), the payload is stored in KV:

  • Key: dead_letter:{timestamp}:{random_id}
  • TTL: 7 days
  • Contents: webhook URL, full payload, error message, attempt count, failure timestamp

Leads are always persisted to D1 first, so the dead letter queue is a safety net for webhook delivery only — no data is lost even if the webhook permanently fails.