In brief
- Repeated event delivery is normal in a distributed system; the danger is not the repeat itself, but repeating the business action.
- Each lead must have a stable identifier, and the handler must atomically record both that identifier and the result.
- Transient errors require a limited number of delayed retries; permanent errors require a separate error queue and a clear recovery procedure.
- Even with reliable delivery, the source and CRM must be reconciled regularly: reconciliation finds rare discrepancies that cannot be seen from individual requests.
What the Problem Looks Like to the Business
A customer submits a form and sees a success message, but the manager never receives the record. Or one lead appears in the CRM twice: first from a webhook, then after the form is resubmitted or an automatic retry runs. Sometimes the record exists but has no phone number, acquisition source, or selected service. In other cases, the CRM shows a new lead, but the Telegram notification never arrives, so the team responds too late.
What these situations have in common is a break between accepting an inquiry and confirming that it has been processed. The website interface knows that the request was sent. The integration service knows that it received an HTTP response. The CRM knows that it created a record. But if the systems do not share an identifier and a transition log, no one can prove whether a repeated request belongs to the same lead or where the original stopped.
- The number of form submissions does not match the number of unique leads in the CRM.
- One contact has several active leads with identical content.
- Statuses in the CRM, payment system, and customer portal do not match.
- Managers manually transfer data from email or a messenger after a customer complains.
- The technical team sees only request errors and cannot reconstruct the business journey of a specific lead.
Why the Promise to “Send It Once” Does Not Work
Between the website and the CRM are a network, load balancer, application, queue, and database. Any participant may finish its work while the neighboring system has not yet received confirmation. For example, the CRM may have already created a lead, but its response is lost in transit. The sender sees a timeout and retries the request. If the recipient does not recognize the retry, a second record appears.
The opposite failure occurs when a handler acknowledges receipt of a message before the data is committed. The process crashes after the acknowledgement, the record is never created, and the queue considers the work complete. Delivery semantics must therefore be selected together with processing rules, not as a separate transport setting.
| Approach | What It Guarantees | Risk to Leads |
|---|---|---|
| At most once | The message is processed no more than once | If a failure occurs, the lead may disappear without another attempt |
| At least once | The message is retried until it is acknowledged | The recipient must safely recognize a retry |
| Exactly once | A specific platform limits duplicate delivery within defined boundaries | A transport guarantee does not automatically mean that an external effect happens only once |
A reliable integration does not try to eliminate every retry. It makes retries predictable and safe.
Agentix Labs design principle
Where Duplicates and Losses Actually Occur
It helps to break lead delivery into separate transitions: the source saves the inquiry, publishes an event, the integration accepts it, the CRM creates or updates an entity, and a notification reaches the manager. Every boundary has a window of uncertainty. A response may fail to arrive, a process may restart, an external API may temporarily rate-limit requests, or the data format may no longer match an updated schema.
- The form calls the CRM directly and does not save the inquiry locally before making the external request.
- The user clicks the button again, and the interface creates a new identifier for every attempt.
- A webhook is sent again after a timeout, but the CRM always executes a create command.
- The queue redelivers a message after its acknowledgement deadline expires, even though the handler has already recorded the result.
- An error in a required field is retried indefinitely as if it were transient, blocking valid messages.
- An employee manually creates a lead from an email without realizing that the automatic integration will recover later.
- Systems normalize phone numbers, email addresses, or external numbers differently and mistakenly treat one customer as different people.
Idempotency Key: How to Distinguish a Retry From a New Inquiry
An idempotency key is a stable key for one business command. The source creates it when the lead is first accepted and reuses it for every retry. Before changing data, the recipient checks the key: if the command has already been completed, it returns the previously saved result instead of creating a second record. The AWS Builders' Library specifically highlights the role of a client identifier and the need to distinguish cases where the same key is used with different parameters.
The key must not be regenerated for every network attempt. For an integration serving multiple companies, ownership context usually matters: a combination of organization, source, and external event identifier is safer than globally checking a short number. Alongside the key, it is useful to store a fingerprint of material parameters, the status, the resulting CRM identifier, and the processing time.
-
01
Create the key at the point of first acceptance
The website or gateway saves the lead and assigns an immutable event_id before calling the CRM.
-
02
Pass the key through the entire route
The same identifier goes into the queue, logs, CRM, and notifications so that the events can be connected.
-
03
Check the key before the side effect
The handler looks for a previously completed command and does not create a second record, payment, or notification.
-
04
Commit the key and the change atomically
The execution record and business change must be saved as one transactional operation or through a reliable inbox/outbox flow.
-
05
Reject a mismatched retry
If the same key arrives with different content, the system reports a conflict instead of silently merging the data.
| Event | Suitable Key | Behavior on Retry |
|---|---|---|
| Form submission | Identifier of the saved lead | Return the existing lead |
| Payment webhook | Provider event identifier and organization | Do not execute the operation again |
| Row import | Batch identifier and row number | Show the previously recorded result |
| Status change | Command identifier, not just the new status | Do not trigger notifications and tasks again |
Retries, Delays, and the Dead-Letter Queue
Retries are needed for transient failures: a network timeout, brief service outage, or rate limit. Retrying immediately and indefinitely is dangerous. During an outage, many processes create additional load and prevent the system from recovering. Transient fault handling practice recommends limiting the number of attempts, increasing the intervals, respecting Retry-After, and adding random jitter so that clients do not retry simultaneously.
Permanent errors must be separated from transient ones. If an event lacks a required field or names an unknown owner, sending the same data again will not fix the situation. After a set number of attempts, the event is moved to a dead-letter queue (DLQ). A correctly configured DLQ preserves the message and the failure metadata supplied by the broker; the number of application attempts and tracing context must be explicitly included in the event headers or payload, and delivery to the DLQ itself must be verified. The queue does not recover the lead on its own.
- Set a timeout for every external call and determine which response codes allow a retry.
- Use limited, increasing intervals with random jitter instead of a short fixed interval.
- Do not retry validation errors and idempotency-key conflicts as transient failures.
- Expose the DLQ size, the age of the oldest event, and the causes of failure.
- Provide safe reprocessing after the cause has been fixed; it must use the original idempotency key.
- Assign an owner and response deadline; otherwise, the DLQ becomes a hidden archive of lost leads.
Data Reconciliation: How to Find Silent Losses
Logs answer the question of what happened inside a specific component. Reconciliation answers the more important question: does the business state match across systems? Even well-designed delivery can be affected by a manual change, a migration error, an expired key-retention period, or an unavailable external service. Critical integrations therefore supplement delivery with periodic data reconciliation.
For leads, reconciliation can compare event_ids accepted by the source with external identifiers in the CRM over a selected interval. Valid status transitions and required relationships are checked separately: a paid order has a payment event, an assigned lead has an owner or queue, and a completed operation has an audit-log entry. A detected discrepancy should not be corrected automatically without defined rules: it must first be classified so that a valid manual decision is not overwritten.
-
01
Define the source of truth
For every field and status, designate the owning system instead of declaring the entire CRM to be the only source.
-
02
Compare identifiers and states
Check not only the number of records, but also whether the event_id, external number, version, and status match.
-
03
Separate discrepancy types
A missing record, duplicate, content conflict, and delay require different actions and urgency levels.
-
04
Recover through the standard handler
The correction passes through the idempotent flow again rather than modifying CRM tables manually.
-
05
Record the reconciliation result
The log shows what was found, who approved the action, and which final state the systems reached.
How to Diagnose the Problem Without Rewriting the Entire Integration
Diagnosis begins with one real route, such as “website form — CRM — manager's Telegram.” The team selects several inquiries: one successful, one lost, and one duplicated. It then reconstructs every transition using timestamps and identifiers. If no shared identifier exists, that is already the first conclusion: observability must be fixed before complex optimization.
| Check | What You Need to See | Warning Sign |
|---|---|---|
| Source acceptance | Saved data and event_id before the external call | The form shows success merely because the request was sent |
| Delivery | Attempts, responses, delays, and a shared trace_id | Only the final error is available, with no history |
| Processing | An idempotent record and external CRM id | Every retry triggers create |
| Errors | Separation of transient and permanent causes | Every error is retried in the same way |
| Recovery | A dead-letter queue, reprocessing, and a reconciliation result | The correction is performed by manual copying |
- Count unique event_ids, not just HTTP requests or CRM rows.
- Verify that a retry after a timeout returns the same business result.
- Simulate the process stopping after writing to the CRM but before acknowledging the queue message.
- Send an invalid event and make sure it is not retried indefinitely.
- Reprocess one event from the DLQ and verify that it does not produce a second side effect.
- Reconcile accepted inquiries with the CRM over a control period and save the list of discrepancies.
How These Principles Apply in Agentix Labs Projects
In QR Oplata, a single request passes through reservation, queueing, assignment to a trader, a hold, a dispute, and the financial ledger. According to the project's verified description, concurrent assignment is protected by a Redis lock, while the final status is checked in PostgreSQL. Monetary changes pass through LedgerEntry and transactions, so control is built around a unified request state and a verifiable history rather than trust in a single interface request.
In VPN Service, repeated payment and traffic events affect the subscription and a separate LTE allowance. The project uses paid_event_id, idempotency_key, and a usage event identifier: a retry must not grant the LTE allowance a second time or deduct traffic again. This example shows why deduplication must operate at the business-operation level, not only within the queue or web server.
Checklist of Requirements for a Lead Integration
Not every company immediately needs a dedicated event platform. For a low-volume flow, a resilient receiver, local persistence, background delivery, and a clear error log are enough. But the core contracts should be defined in advance: the lead identifier, data owner, permitted retries, final state, and recovery method. The architecture can then grow with actual load without changing what the events mean.
- The lead is saved before the external CRM is called and receives a permanent event_id.
- Every retry passes the same idempotency key.
- The recipient atomically records the key and the business result.
- A retry with the same key but different content returns an explicit conflict.
- Retries are limited, use backoff with jitter, and account for the error type.
- After all attempts are exhausted, the event enters an observable DLQ.
- A replay does not create a second lead, payment, task, or notification.
- Logs are connected by a shared trace_id and do not expose unnecessary personal data.
- An owning system and valid transitions are defined for every status.
- Periodic reconciliation finds missing records, duplicates, and conflicting states.
- The team has a person responsible for the DLQ and a recovery procedure.
- Metrics show the number of accepted events, retries, errors, queue age, and reconciliation discrepancies.
Frequently asked questions
Can duplicates be eliminated with a unique phone number?
A phone number helps link contacts, but it does not define the uniqueness of a business intent. One person may submit two different inquiries, and a company may share one number. An event or command identifier is needed to prevent repeated processing; contact-merging rules are defined separately.
What if the CRM does not support idempotency keys?
Place an integration layer in front of the CRM that stores the external event_id, processing state, and resulting CRM id. On a retry, the layer looks for the completed operation and does not call create again. If the CRM can search a custom external field, that field is also used for reconciliation.
Are automatic retries enough?
No. A retry without idempotency increases the risk of duplicates, while indefinitely retrying a permanent error creates load and conceals the problem. Error classification, a limit on attempts, increasing intervals, a dead-letter queue, and safe reprocessing are all required.
How is a DLQ different from an error log?
A log supports investigation, while a DLQ stores the unprocessed event as a unit of future work. A DLQ needs a way to inspect the cause, apply a fix, reprocess the event with the original key, and confirm the final state.
Is reconciliation necessary if the queue promises exactly-once delivery?
Yes, if the business process involves multiple systems or external side effects. A broker guarantee applies within defined boundaries and does not protect against manual changes, schema errors, incorrect entity matching, or a separate external API failure. Reconciliation verifies the final business state.
Sources
- AWS Builders' Library: Making retries safe with idempotent APIsChecked 30 July 2026
- Microsoft Azure Architecture Center: Transient fault handlingChecked 30 July 2026
- Google Cloud Pub/Sub: Exactly-once deliveryChecked 30 July 2026
- RabbitMQ: Dead Letter ExchangesChecked 30 July 2026