Repeated Telegram Webhooks: Avoiding a Second CRM Lead
Separate event retries from new enquiries, persist incoming updates, and handle an ambiguous CRM create timeout without blindly creating another lead.
Repeated Telegram Webhooks: Avoiding a Second CRM Lead
If the same Telegram event arrives twice, your integration should not create a second CRM lead. A stable event identifier and a processing record help prevent that. But checking whether an ID has been seen is not enough: if the CRM creates a lead and its response is lost, your handler cannot tell whether it is safe to send another create request.
This is a design guide for developers connecting Telegram Bot API → their own handler → a CRM. It does not describe a verified built-in feature of 24go.site. It assumes your bot already receives the relevant enquiries legitimately, and that you have a database and a test CRM. The design does not grant permission to collect other people's contact details or send unsolicited messages.
Give events, contacts, and deals different identities
These objects need different keys and rules for handling repeats.
| Object | What its key protects | Example key in your integration |
|---|---|---|
| Incoming event | Processing the same delivery twice | source + your tenant + bot + update_id |
| Contact | Linking multiple enquiries to one person | tenant + source + user identifier, when available |
| Deal | Separating a new enquiry from an existing conversation | internal enquiry ID under an agreed business rule |
update_id uniquely identifies an update. Telegram explicitly documents its use for ignoring repeated updates. The additional components above are a proposed isolation scheme for your system, not Telegram's key format.
Two different messages from one person are not duplicate deliveries of the same event. They might extend one deal or start different deals; agree on the rule with the process owner. Do not turn “one delivery, one processing action” into “one person, one deal forever.”
Receiver: persist first, acknowledge second
Idempotency means that repeating an operation does not produce an additional effect. Store incoming updates durably rather than relying only on process memory.
- Authenticate the request and validate its expected structure. Telegram's
setWebhooksupportssecret_token; when configured, Telegram sends it inX-Telegram-Bot-Api-Secret-Token. Check it before queuing work. Do not log the token's value. - Build the event key. Do not use request arrival time, which changes on redelivery.
- Store the event and set its state to
pendingin one transaction. Put a unique constraint on the composite key. If jobs live in a separate table, write both records in the same transaction to avoid losing work between writes. - Return a successful HTTP response after the commit succeeds. If the key already exists, acknowledge delivery without creating a new job. An existing
pendingrecord must remain available to the worker. - If durable storage fails, do not acknowledge success. Your handler should not claim to have accepted an event it has not stored.
Worker: a timeout does not mean creation failed
Before attempting creation, persist an operation_id that identifies the business operation. Keep it unchanged across worker retries. Prevent two workers from executing the same operation at once with atomic job claiming, an ownership deadline, and a recovery procedure for abandoned jobs.
Choose the next step based on capabilities verified in your CRM's own documentation:
- Idempotent creation is supported. Reuse the same operation key on each retry within its documented validity period. Do not assume every CRM accepts an
Idempotency-Keyheader. - A unique external ID and atomic upsert are supported. Map the operation to that ID. Here, upsert means creating or updating by key; an ordinary search followed by creation does not provide equivalent protection against a race.
- Neither mechanism is available. After an ambiguous timeout, move the job to
needs_reconciliation. Check the CRM using an external operation reference, request logs, or manual inspection. If you cannot establish the result, do not blindly submit another create request.
crm_id and mark the job done. If the process dies after creating the lead but before saving the result, recovery must use the same idempotency mechanism or reconciliation. A local event table alone does not guarantee exactly-once effects in a remote CRM.
Decide what repeated and new messages should do
| Situation | Action |
|---|---|
Same event key, pending record | Acknowledge delivery; continue the original job |
Same event key, done record | Do not repeat the CRM action |
Different update_id, same contact | Apply the rule for continuing or opening a deal |
| Timeout after sending a create request | Retry with a supported stable key or reconcile; do not generate a new random key |
| Late stage-change event | Check its version or whether the transition is valid rather than overwriting unconditionally |
Test failure paths before enabling the integration
Use anonymized test enquiries and check more than the happy path:
- Deliver the same key twice, then concurrently in two requests. Storage should contain one job, and the CRM effect should not be duplicated.
- Stop the worker after
pendingis persisted. The event should still be available for processing after restart. - Simulate a CRM record being created while its response is lost. Recovery should use the original key or move the job to
needs_reconciliation, not blindly create again. - Send a different message from the same test contact. Confirm that event deduplication does not discard it.
- Test authentication failure and database failure. A forged request should not enter the queue, and an unsaved event should not receive a successful acknowledgement.
For operation, monitor the age of pending jobs, errors, and the number of jobs requiring needs_reconciliation. Retain the mapping event_key → operation_id → crm_id. Avoid placing full conversations and tokens in technical logs unnecessarily. Align key retention with actual redelivery periods and data requirements: after a key is deleted, an old event can look new again.
Readiness is not just seeing a test lead appear once. Confirm that retries do not add another effect, unfinished work can recover, and an ambiguous response does not turn into a duplicate deal.