---
title: Your Marketing Webhooks Are Losing Leads, and Nobody Is Watching
description: Webhooks power lead routing, enrichment, and lifecycle automation, yet most teams run them with no duplicate protection or loss monitoring. Here is the receiver pattern that stops silent lead loss.
author: LETSGROW Dev Team
date: 2026-10-02
category: Analytics
tags: ["Webhooks", "Marketing Automation", "Idempotency", "Data Reliability", "CRM Integration"]
url: "https://letsgrow.dev/blog/marketing-webhook-reliability-idempotency-retries-lead-loss"
---
Your marketing automation is not as reliable as your dashboards say it is. Somewhere between a form submit and a CRM record, a webhook failed, retried, or fired twice, and nobody noticed because the failure was silent. Webhooks are the quiet plumbing under lead routing, enrichment, attribution, and lifecycle email, and most marketing teams run them with zero delivery guarantees.

This post makes one argument: treat every webhook in your stack as an unreliable message, and build the receiving side to survive duplicates, delays, and outages. It is a day of engineering work, and it removes a whole class of "why is this lead missing" tickets.

## Webhooks Are At-Least-Once, Not Exactly-Once

Every major sender retries failed deliveries. Stripe, for example, keeps retrying a failed webhook for up to three days in live mode before giving up. That is good news for delivery and bad news for your logic, because a retry means the same event can arrive more than once. If your handler creates a CRM contact, sends a Slack alert, or enrolls someone in a nurture sequence, a duplicate delivery does it twice.

The failure modes are predictable. Your endpoint is slow and the sender times out, so it retries while your first run is still finishing. Your endpoint returns a 500 during a deploy and the event lands out of order. A sender replays a batch after its own incident. None of this is exotic. It happens every week in a busy stack.

::stat-block
title: The retry window
value: 3 days
label: How long a sender like Stripe keeps retrying a failed webhook in live mode
context: Your handler may see the same event again days after the first attempt, so it has to be safe to run twice
::

## Make Handlers Idempotent Before Anything Else

Idempotency is the single most valuable fix. Every webhook payload carries a unique event ID. Store it, and before doing any work, check whether you have seen it. If you have, return a 200 and stop. If you have not, record the ID and process the event.

The details matter here. Write the event ID and your side effect in the same transaction where you can, or record the ID first and mark it complete after. Use a unique constraint on the ID column so two concurrent deliveries cannot both pass the check. A simple table with an event ID, a received timestamp, and a status is enough. In a no-code tool, the equivalent is a lookup against a data store before the action step, and it is worth the extra module.

Do not use "email address" or "contact ID" as your dedupe key. A person can legitimately trigger two different events in a minute. Dedupe on the event, not on the human.

## Acknowledge Fast, Process Later

The second rule is to separate receiving from processing. Your endpoint should validate the signature, write the raw payload to a queue or table, and return a 200 within a second or two. A worker then picks events off the queue and does the real work: enrichment calls, CRM writes, scoring.

This fixes the timeout-retry loop at the source. It also gives you a replay button. When your CRM API is down for an hour, events wait in the queue and drain when it recovers, instead of failing against a sender that eventually stops trying. Always verify the sender's signature before queueing, because an open webhook URL is an open door for forged leads.

::checklist
title: Webhook receiver baseline
items:
  - Verify the HMAC signature or shared secret on every request before reading the body
  - Return a 2xx within a couple of seconds and push the raw payload to a queue or table
  - Store the event ID with a unique constraint and skip anything already seen
  - Retry failed jobs with exponential backoff and a hard cap on attempts
  - Route jobs that exhaust retries to a dead-letter table that a human reviews daily
  - Log the event ID, source, status, and processing time so a lost lead is traceable in minutes
  - Alert on dead-letter growth and on a sudden drop in event volume, not only on errors
::

## Monitor the Silence, Not Just the Errors

Most alerting watches for errors. Webhook failures are often silence: the sender disabled your endpoint after repeated failures, a key was rotated, or a form integration was quietly unsubscribed. No error ever reaches you because nothing arrives.

Add a volume check. If your form-submit webhook normally delivers forty events a day and delivers four, page someone. Keep a heartbeat per source and compare against the same weekday last week. Review the dead-letter table daily at first, then weekly once it stays empty. Reconcile against the source of truth monthly: count form submissions in the form tool and contacts created in the CRM over the same period. The gap is your real loss rate, and most teams have never measured it.

## What to Do This Week

Pick your three highest-value webhooks, usually form submissions, enrichment callbacks, and billing or trial events. For each one, add an event ID check, move heavy work behind a queue, and set a volume alert. Then run one reconciliation between your form tool and your CRM and see what the gap looks like. If it is above one percent, you have been losing pipeline to plumbing, and the fix takes less time than the weekly pipeline review where the missing leads get discussed.
