One Form Submission, Two Salesforce Leads: The Lifecycle Bug I Had to Trace

A duplicate Salesforce lead looks like a simple defect: one form was submitted, so one CRM record should exist. The investigation I worked on was harder because duplication appeared through several legitimate features interacting over time. One form had overlapping create feeds. Another entry was manually resent and later processed by an abandonment job. A third configuration had a latent duplicate risk that had not yet produced matching records. The lesson was not “add a duplicate check.” It was to model the full entry lifecycle and give every path a shared identity.
I started with evidence, not the current settings screen
Configuration shows what should happen now; retained integration logs show what actually happened before. I grouped log rows by form entry and compared the Salesforce record IDs, feed IDs, timestamps, and normalized identity fields. That revealed entries with two or three distinct leads when one was expected.
This distinction mattered because some feeds had been edited after the incidents. I separated confirmed current behavior, confirmed historical behavior, latent risk, and configurations with no duplicate evidence. That prevented one explanation from being stretched across every form.
Overlapping conditions created the obvious duplicates
One form had two keyless create feeds. A blank branch in the first feed’s OR condition overlapped the condition for a second feed. The same submission legitimately matched both rules, and each rule created a lead one second apart.
The fix was not a fuzzy CRM lookup. It was to make the routing conditions mutually exclusive and verify the actual submission categories. When two feeds represent different business destinations, their predicates should be testable as a partition: exactly one matches each supported case.
Time created a second class of duplicate
Partial-entry and abandonment features turn one form entry into a sequence. A visitor can save progress, an operator can resend, a scheduled job can detect abandonment, and a later completion can run the normal feed. Without durable state, each event looks like a fresh opportunity to create.
I changed the integration to preserve the authoritative Salesforce record across that lifecycle. Later pages and retries update the existing record instead of creating a new one. The important part is that identity lives with the entry, not inside the request that happened to run first.
Manual actions need the same rules as automation
Admin resend buttons are useful during support, but they cannot be a separate universe. A manual push should inspect the same stored record identity and follow the same create-or-update contract as the scheduled scanner.
I also made the operator-facing result explicit. “Sent” is not enough; the system should say whether it created, updated, skipped, or failed, and which durable record it used.
The test matrix followed the lifecycle
- Submit a complete entry that matches exactly one feed.
- Save a partial entry with the minimum mapped identity.
- Run the abandonment job twice.
- Manually resend before and after abandonment.
- Complete a previously partial entry.
- Confirm every later action targets the same CRM record.
I tested later result pages as well as the first page. Pagination and query limits can hide old entries or cause scanners to revisit the wrong slice.
Duplicates are an operational cost, not just untidy data
Duplicate leads split notes, trigger repeated outreach, distort attribution, and make staff doubt the integration. Cleanup also carries risk because the wrong merge can erase the true history.
The safer investment is an idempotent lifecycle: stable entry identity, mutually exclusive routing, durable remote IDs, visible outcomes, and tests that replay events in different orders.
Why a broad dedupe search was not enough. It would have been easy to search Salesforce for a matching email before every create. That can suppress some duplicates, but it introduces new ambiguity: families can share contact details, fields may be incomplete during a partial entry, and two requests can still race between search and creation. A fuzzy lookup also hides the configuration error that made two feeds run.
I used the form entry as the durable workflow identity and preserved the remote record ID once one existed. Business rules still decide whether a new submission represents a new lead, but later events in the same entry lifecycle no longer make that decision again. For routes that truly need cross-entry deduplication, I would define an explicit CRM identity policy and enforce it with an atomic server-side mechanism where possible. Idempotency and deduplication are related but different: the first makes retries of one operation safe; the second decides whether two apparently different operations represent the same person or opportunity.
The lifecycle is the unit I debug now
When a form-to-CRM integration duplicates data, I would resist fixing only the event that exposed it. First draw every automatic and manual event from initial save through completion, then mark where identity is created and reused. The duplicate usually lives in the gap between two individually reasonable workflows.
Photo by cottonbro studio on Pexels.
Written by
Adrian Saycon
A developer with a passion for emerging technologies, Adrian Saycon focuses on transforming the latest tech trends into great, functional products.


