
Featured Article
Implementation
How do you stop a voice AI workflow from calling the same lead twice?
Use a shared record of call attempts before dialing, not just a deduplicated CSV. Give each intended attempt a stable identifier, let only one worker reserve it, and resolve uncertain provider responses before starting another call. Check contact eligibility again when the job is about to run, because a lead may already have been contacted by another campaign.
This guide is published by ConversAI Labs. It proposes an application design based on primary-source documentation, not comparative testing. It does not claim that ConversAI supplies automatic cross-campaign deduplication or provider-level idempotent call creation.
Why does removing duplicate CSV rows not solve duplicate calls?
A marketing team imports the same lead into two campaigns. Each file contains the number once, so both imports pass their duplicate checks. Two workers then see an eligible record and start separate calls.
Another failure needs no duplicate lead: a call-creation request times out after the provider accepts it. The worker assumes failure and sends a new request. The customer may receive two calls even though the CRM contains one contact.
Separate these problems before choosing a fix:
| Problem | Control to design |
|---|---|
| Duplicate rows in one import | Normalize and review matching contact records |
| The same contact enters two campaigns | Shared eligibility and contact reservation across campaigns |
| Two workers process the same job | Atomic attempt reservation |
| A request times out after submission | An uncertain state and provider reconciliation |
| A legitimate follow-up is due | A new, explicitly authorized attempt |
| A result webhook is delivered twice | Idempotent result processing |
The last row is covered in our webhook retries guide. Deduplicating results after a call cannot undo a duplicate call already placed.
Which identifiers should you keep?
Keep contact identity, outreach intent and provider call identity separate.
- Contact ID: an opaque internal identifier within the correct workspace. Normalize numbers with country context, but do not assume a shared phone number always identifies one person.
- Outreach ID: the business reason for contacting that lead, such as one requested appointment callback.
- Attempt ID: one intended dialing attempt. Retries of that same operation retain this identifier; a later approved follow-up gets a new one.
- Provider call ID: the identifier returned when the provider accepts the request. Store it against the attempt as soon as it is available.
Scope these identifiers to the tenant or workspace. Avoid placing phone numbers or email addresses in idempotency keys or general-purpose logs. Keep only the operational fields needed for reconciliation, with appropriate access controls.
How do you prevent two workers from dialing at once?
Use an atomic database operation to reserve an attempt before the network request. An application sequence that reads “not called” and later writes “calling” leaves a gap in which another worker can make the same decision.
PostgreSQL documents unique constraints over one or several columns. An application can use that primitive to enforce a unique workspace-and-attempt identifier; the business rules for contact eligibility still need to be implemented separately. PostgreSQL constraint documentation
A useful record might contain an attempt ID, contact ID, outreach ID, state, owner, reservation time and provider call ID. The exact schema depends on the system. A unique attempt row does not stop two different attempts from targeting the same contact: a separate shared contact reservation or transactional eligibility rule must cover the campaigns that should coordinate.
Recheck eligibility inside that coordination step. If the contact is already reserved, recently contacted under your chosen policy, or no longer eligible, hold or cancel the queued work. Do not let the model's generated text decide whether a duplicate is permitted.
What should happen when call creation times out?
A timeout means the application did not receive a conclusive response. It does not establish whether the provider created the call.
AWS describes caller-supplied request identifiers as a way for an API to recognize repeated intent, with atomic handling of the token and the resulting operation. That is a property of an API contract, not something obtained merely by adding an arbitrary header. AWS guidance on idempotent APIs
If your provider documents idempotent call creation, follow its exact scope, retention and parameter rules. Otherwise, do not assume resubmitting the request is safe. Keep the attempt in an uncertain state and use supported provider records, correlation data and authenticated callbacks to reconcile it. A missing callback alone is not proof that no call exists. Where the evidence cannot resolve the ambiguity, hold for operator review rather than blindly redialing.
| Attempt state | Recommended next step |
|---|---|
| Reserved, not submitted | Submit only if the reservation and eligibility remain valid |
| Submitted, acceptance unknown | Reconcile; block another attempt for the same unresolved intent |
| Accepted, provider ID known | Track that call rather than creating a replacement |
| Terminal status known | Evaluate the outcome and follow-up policy |
| Follow-up approved | Create a distinct attempt with a traceable reason |
Do not automatically redial just because a worker lease expires. The worker may have submitted a call before crashing. Transfer ownership of the reconciliation work, not permission to create another call.
Does a completed call mean the lead was successfully contacted?
Not necessarily. Twilio documents that a completed call can connect to a person, an IVR or voicemail. Treat provider lifecycle status separately from the business outcome. Twilio Call resource
For a requested callback, record whether the intended person was reached and what action was agreed. A voicemail or uncertain outcome should follow an explicit retry policy; it should not silently become either a successful qualification or permission for unlimited attempts. See our voicemail detection guide.
What should you test before enabling a campaign?
Use controlled recipients and isolated downstream systems. Test the same job arriving twice, two campaigns selecting the same contact, a provider timeout after acceptance, a worker crash, a late callback and a lead becoming ineligible while queued. Verify the number of provider calls created, not just the number of CRM tasks.
Also test the opposite mistake: an approved follow-up must not be permanently suppressed by reusing an old attempt ID. Record suppression reasons so the operations team can distinguish duplicate prevention from genuinely missed outreach.
Start with the ConversAI Labs documentation and a controlled test call before connecting campaigns. Connected voice calls, including connected test calls, use 4 credits per minute (₹4/minute); ₹1 = 1 credit. Workflows are free for now. The 100 free credits are subject to the allowance terms shown in your account. Check the platform and provider capabilities before adopting this proposed design; this article is not a claim of built-in duplicate-call prevention.
About ConversAI Labs Team
The ConversAI Labs team writes about building and operating voice AI workflows.