vicigeeksimple guides
Browse
All guides

Running your system · Duplicate-safe operations

Design a duplicate-safe callback migration

Resolve the intended lead, identify each scheduled callback separately, then check whether the supported write path can express that exact move before planning it.

Reader setup

Before you evaluate

  1. Export an approved synthetic or minimized callback input and keep an immutable copy outside the working directory.
  2. Obtain a documented old-agent to new-agent mapping plus a read-only database or supported report path.
  3. Use an inactive test campaign first and obtain separate approval before any supported API write.
Expected result
You will prepare a row-by-row ledger that combines an existence check with independent identity and duplicate checks before any mutation.
Before making changes
The examples only normalize and read. They do not move callbacks; production changes must use the installed supported API and an approved rollback ledger.

Build one normalized identity set

Fast answer: callbacks are usually tied to lead records, while imported files are often keyed by phone number. Normalize country code and punctuation once, then resolve each phone to the intended lead before planning a write.

A phone number that looks identical to a human can still fail to match: a leading country code, a stray extension digit, or punctuation left over from a spreadsheet export all change the literal string VICIdial's own search compares against. Decide and document one normalized shape for every input before the first lookup, and apply it identically to both the migration input and whatever VICIdial's own phone_number field actually stores, rather than normalizing only one side.

Ambiguous numbers belong in a review queue. Never let a last-row-wins rule silently choose between duplicate leads.

See VICIdial terminology for complete beginners: users, phones, campaigns and leads for the rest of this glossary.

Before you use it
Run through the approved read-only database account. Replace <PHONE_A> and <PHONE_B> with normalized synthetic input values only; keep the parentheses and quotes, and never remove the WHERE clause for a first pass.
Read-only duplicate check
SELECT phone_number, COUNT(*) AS lead_rowsFROM vicidial_listWHERE phone_number IN ('<PHONE_A>','<PHONE_B>')GROUP BY phone_numberHAVING COUNT(*) > 1;
Reader-run example · no captured result

No output was captured for this example. Read its preparation, expected result and stop conditions before running the command on your own system.

Success looks like
No rows means neither exact input has more than one match; either may have no match at all. Confirm existence separately. Returned rows identify duplicates that must be resolved before a write.
Stop if
Stop if the account can write, the population scope is unclear, returned data is not synthetic/authorized, or a number maps to more than one lead.
  • Country code, spacing and punctuation are normalized identically on both the input file and the field VICIdial's own search compares against.
  • Every ambiguous phone number is quarantined in a review queue, not resolved by a default rule.
  • The normalized shape you chose is written down once, so a second engineer applying the same migration months later reproduces it exactly.
Trace path · read left to right
01Input numbers02no_update dry run03Verified callbacks
Reference screenshots 3 images

Reference capture

Reference screens from the demo

Captured on an isolated VICIdial demo: Administration screens on September 24, 2026, and the idle Agent screen on August 11, 2026. Each caption states its own capture time, and every sanitized image helps you recognize a related screen; none proves that this article's call, command, or result occurred.
Step 1 · Learn shared statuses

Read system status definitions

Open to enlarge
Sanitized VICIdial System Statuses page showing status names, categories, and selectable flags
Captured August 11, 2026 at 16:21:13 UTC on the authorized isolated demo.
Capture detailsThis is shared status configuration with no customer or call rows; it does not prove that any status was used.
Step 2 · Check campaign eligibility

Compare campaign dial statuses

Open to enlarge
Sanitized VICIdial Custom Campaign Statuses listing for the fixture campaign, with no other campaigns shown
Captured September 24, 2026 at 21:47:33 UTC on the authorized isolated demo.
Capture detailsThis is the custom campaign-statuses listing for one fixture campaign, not the full dial-status selection area; it does not show call outcomes or report results.
Step 3 · Recognize the Agent status area

See where an agent works with status

Open to enlarge
Sanitized logged-in VICIdial Agent screen in an idle no-live-call state with blank customer fields
Captured August 11, 2026 at 16:25:04 UTC on the authorized isolated demo.
Capture detailsThis is a real logged-in idle Agent screen with session and system identifiers redacted. Customer fields are blank, and it does not prove a placed, answered, recorded, transferred, or completed call.

Prepare a tiny input before a large migration

Start with three synthetic cases: one unique callback needing a move, one already owned by the target agent and one deliberately ambiguous phone. This small set proves the reject and no-op branches as well as the happy path.

A fourth case is worth adding once the first three pass: the same phone number, but represented differently between your input file and VICIdial's own stored value — an extra leading digit, or punctuation the export tool left in. That case exercises your normalization step specifically, separately from the identity-resolution logic the other three already cover, and it is the case most migrations skip until it causes a real mismatch.

Use opaque fixture labels in the public worksheet. The example values explain the columns; they are not real agent IDs, lead IDs or phone numbers.

Before you use it
Create this ledger offline with synthetic identifiers, then substitute only authorized input references in the protected working copy. unique, ambiguous, update, no-op and reject are decision labels, not VICIdial status values.
Beginner dry-run ledger example
input_ref,normalized_phone,lead_match,current_owner,proposed_owner,planfixture-01,PHONE_A,unique,AGENT_OLD,AGENT_NEW,updatefixture-02,PHONE_B,unique,AGENT_NEW,AGENT_NEW,no-opfixture-03,PHONE_C,ambiguous,unknown,AGENT_NEW,reject
Not executed · worksheet or reference text

This sample is a template or reading aid, not a terminal command. There is no output to show.

Success looks like
Every input has exactly one plan, ambiguous data is rejected, and an already-correct callback is recorded as a no-op rather than written again.
Stop if
Stop if an input lacks an owner mapping, resolves to multiple leads, contains unapproved personal data, or cannot be assigned one deterministic plan.
Fictional same-lead callbacks must remain separateScroll sideways to compare columns
InputLeadCallback identity / scheduleOwnerPlan
fixtureAlead900001CB501 /10:00OLD_AMove only if the supported operation targets this exact callback.
fixtureBlead900001CB502 /11:00OLD_BLeave unchanged; sharing the lead does not make it the same callback.
fixtureCambiguousunknownunknownReject before any write.

Run VICIdial's own dry run before building anything custom

update_lead no_update=Y checks whether the lead search finds a match. It does not verify the owner, schedule or identity of a callback, and it does not provide a conditional per-callback update contract.

The documented callback_type and callback_user fields describe callback effects associated with a selected lead. Before using them, inspect the installed implementation’s update-versus-add behavior and whether it can target the intended scheduled record without affecting another. A lead ID alone is insufficient for the CB501/CB502 case above.

If the supported interface cannot select the exact callback identity and preserve the other schedule, mark this input UNSUPPORTED and hand the owner the two record identities, expected before/after state and missing API capability. Do not fall back to direct SQL writes, delete/recreate callbacks or assume that a second request is idempotent.

Before you use it
This is the request shape, not a runnable call — update_lead is a write function even when no_update=Y asks for a dry run only. Illustrate it here, then place the real call through /etc/vicidial-api/writer.cfg only after independent review.
The no_update dry-run request shape
function=update_lead  &phone_number=<PHONE_NUMBER>  &search_method=PHONE_NUMBER  &no_update=Y  &callback_type=USERONLY  &callback_user=<AGENT_USER>
Not executed · worksheet or reference text

This sample is a template or reading aid, not a terminal command. There is no output to show.

Success looks like
A match establishes lead existence only. The separately read callback identity, owner and schedule must still agree, and the supported writer must be capable of targeting only that intended record.
Stop if
Stop if the existence check finds no match, the duplicate check finds multiple matches, or the intended identity remains uncertain.

You should seeThe existence check is one planning input. A callback move remains blocked until the supported write’s exact target and effects are independently established.

Read every intended callback and the untouched sibling

Use the independent read-only client before planning a write and again after any separately approved supported operation. Read the intended callback identity and the same-lead sibling, not only campaign totals.

For the fictional CB501 move, the accepted result changes only CB501’s assigned user while retaining its schedule/state and leaving CB502 byte-for-byte equivalent in the reviewed fields. A campaign status/recipient total could stay identical after a wrong-owner write; it is a secondary reconciliation only.

Before you use it
Use the verified read-only SQL client; replace all fictional IDs and confirm the installed columns. Keep row-level results private.
Read exact synthetic callback identities and expected fields
SHOW COLUMNS FROM vicidial_callbacks;SELECT callback_id,lead_id,list_id,campaign_id,user,recipient,callback_time,status,lead_statusFROM vicidial_callbacksWHERE campaign_id='LAB01' AND lead_id=900001 AND callback_id IN (501,502)ORDER BY callback_id;
Reader-run example · not executed for this guide

This example has no recorded result here. Follow its preparation, expected result and stop conditions before using it.

Success looks like
Each expected callback identity is present with its separately recorded owner/schedule/state. The intended changed row and unchanged sibling reconcile to the approved ledger.
Stop if
Missing identities, changed schedules, unexpected siblings or an unsupported writer block the move. Do not reinterpret aggregate counts as success.

You should seeThe plan ends with a targetable supported operation plus exact independent verification, or a precise unsupported-operation handoff.

  • Input=changed+no-op+rejected/unsupported, with one deterministic plan per callback identity.
  • Every accepted changed row matches its exact intended after-state; every untouched sibling retains its recorded state.
  • Before reversal, compare current owner/schedule/status with the accepted after-state; a concurrent change blocks automatic rollback.

Stop cleanly and reverse only proved changes

Stop the worker on authentication errors, ambiguous responses, count mismatch or a duplicate discovered after a write. Do not continue from the next row until an independent read has classified the prior operation.

Rollback from the immutable ledger in reverse operation order using the same supported, idempotent path. Re-read each callback after reversal and retain only sanitized reconciliation totals in the incident record.

Rollback is itself a write. An existence-only no_update check cannot reveal later changes: independently re-read the current owner, schedule and status against the ledger before reversing a row. Stop when they differ or cannot be verified.

  • Changed plus no-op plus rejected must equal the input count.
  • Every reversed item must match its recorded original owner and schedule state.
  • Unknown outcomes block dependent work until independently resolved.

Checks and sources

What was checked

  • The official Non-Agent API documents lead-search no_update behavior and callback-related fields. It does not make lead uniqueness equivalent to a unique scheduled callback.
  • The same-lead/two-callback example is fictional; no callback migration, API write, deletion or reversal was executed for this repair.
  • The protected per-record read is a prospective template. Its actual target/column mapping and supported-write capabilities must be checked on the installation.

Primary references

Sources

  1. VICIdial Non-Agent APIVICIdial · accessed September 23, 2026
  2. VICIdial status and disposition reference (VICIDIAL_statuses.txt)VICIdial · accessed September 23, 2026
  3. Non-Agent API callback scopes and update_lead contractVICIdial Group · accessed November 7, 2026

Follow without guesswork

Get the next article

RSS is live now. Email delivery below is an explicit local preview and sends nothing.Open the RSS feed
Email preview only. The address stays in this browser and is never transmitted.