Voice Agent Post-Call Write-Back Testing: Record vs Call

On this page
The call ended well. The caller heard "you're all set." Three days later a technician shows up at the wrong hour, or a customer gets two confirmation texts, or a note in the CRM holds a full card number. The conversation was fine. The write-back was not.
If your team built its own voice agent inside a larger product, the call is the front door. The record is the thing your customers, your support team and your regulators actually see. Voice agent record accuracy is the metric most teams skip. Most test suites stop at the transcript or the tool call. This post on voice agent post call write back testing covers the last hop: the record that exists after hangup.
It does not re-teach tool-call checks. For those, see tool call accuracy for voice agents and voice agent tool-calling test cases. It also assumes you know the basics of silent tool failures. Here the question is narrower: does the stored record match the call?
Why the record can differ from the call
A voice agent CRM write back has five steps. The agent captures values from speech. Code normalises them into fields. A request goes to your CRM, scheduling table or ticket system. The system answers. A later job may add a summary or status.
Each step can change the value without raising an error. Speech recognition may hear "fifteen" for "fifty." A parser may read "3" as 3 AM. A retry may create a second row. A timeout may leave half the fields written. The agent still says "done," because it only saw a success code.
That is why transcript scoring cannot catch these bugs. The transcript can be perfect while the record is wrong. The reverse also happens. Evalgent's thesis is that judging from text alone misses audio-level failures. The record check is the other half: verify the outcome, not the wording.

The write-back failure taxonomy
Use this grid to plan voice agent post call write back testing coverage. Each row is a class of bug, how it shows up, and the test that catches it.
| Failure class | What the record shows | Test that catches it |
|---|---|---|
| Wrong value | Field holds a near miss of what was said | Field-level diff against golden truth |
| Wrong object | Update lands on a different contact or job | Read back by record ID, check the owner |
| Silent success | 200 or 202 returned, nothing stored | Read-after-write with a timeout |
| Duplicate | Two rows for one call | Fault-injected retry, count rows per call |
| Partial write | Some fields set, others empty | Kill the connection mid-write |
| Race after hangup | Summary or status missing or stale | Hang up at each call stage |
| Time drift | Right clock time, wrong zone or day | Zone and DST cases |
| Overwrite | Old good data replaced by new guess | Seed existing values, then call |
| PII in notes | Card, SSN or health detail in free text | Pattern scan of every text field |
| Summary conflict | Note says one thing, fields say another | Compare summary claims to fields |
Trade posts show these classes with real data. The pharmacy refill line shows how a one-digit slip lands in a prescription record. The utility outage post shows an address that reads right on the call and wrong on the ticket. The pizza order status post shows a lookup that finds the wrong ticket. Capture errors upstream of the write are covered in alphanumeric capture and STT entity accuracy.
Silent success: a 200 with the wrong object
HTTP success tells you the server accepted a request. It does not tell you the right row changed. A lookup by phone number is the classic trap. Two contacts share a household line, the agent grabs the first match, and the update lands on the wrong person. Every status code in the chain is green.
The 202 code is worse. MDN describes `202 Accepted` as a request accepted for processing where "processing has not been completed or may not have started," and notes that "there is no way to later send an asynchronous HTTP response to indicate the outcome" (MDN, 202 Accepted). If your CRM returns 202, the agent cannot know the write worked. It has to poll or you have to reconcile.
Two rules follow.
1. Never let the agent say "done" on a status code alone. Confirm by reading the record back, by ID, and checking owner and key fields.
2. Test the lookup, not just the write. Seed near-duplicate contacts: same phone, same surname, same address with a different unit. Then check which row changed.
Retries and duplicates
Retries are where voice agent duplicate records come from. Networks drop responses, not just requests. The client sees a timeout, tries again, and the server now has two copies.
Method semantics matter here. An HTTP method is idempotent when "the intended effect on the server of making a single request is the same as the effect of making several identical requests," and POST is "not guaranteed to be idempotent" (MDN, Idempotent). Creating a record with POST can therefore duplicate on retry.
There is a standard fix in progress. The IETF HTTPAPI working group's `Idempotency-Key` draft (version 08, dated October 22, 2026, still an Internet-Draft as of November 2026) says a key "MUST be unique and MUST NOT be reused with another request with a different request payload" (IETF datatracker). It suggests a 409 if the first request is still running and a 422 for key reuse with a different payload. Because it is a draft, check whether your target system honours any such key before you rely on it.
Inbound events duplicate too. LiveKit's webhook docs say "there are no guarantees around delivery," that it retries on transient failure, and that each event carries a UUID `id` (LiveKit webhooks). Retries mean the same event can arrive more than once, and because delivery is not guaranteed it can also be missed, so your post-call job may run twice for one call or not at all. Dedupe on that event ID and reconcile for gaps.
The strongest fix lives in your own store. Make the call ID plus the operation a unique key, and let the database refuse the second write. PostgreSQL documents `ON CONFLICT DO NOTHING` as skipping the insert, and `DO UPDATE` as guaranteeing "an atomic INSERT or UPDATE outcome" even under high concurrency (PostgreSQL INSERT).
CREATE UNIQUE INDEX crm_records_call_op
ON crm_records (source_call_id, op);
INSERT INTO crm_records (source_call_id, op, payload)
VALUES ($1, 'create_appointment', $2)
ON CONFLICT (source_call_id, op) DO NOTHING;
The test is a fault injection. Let the first write commit, drop the response, and let the client retry. Then count rows for that call. The pass condition is exactly one.
Partial writes and overwrites
A record is rarely one field. An appointment has a time, a service, a contact, a location and a note. If the write is several calls, a failure in the middle leaves a record that looks valid but is incomplete. Worse, the downstream system may start acting on it.
Test this by failing the second request of a multi-step write. Then check what a human or downstream job would see. Is the half-written record visible? Is it marked incomplete? Does the agent tell the caller the truth?
Overwrites are the opposite problem. The agent writes a field that already held good data. A caller says a new phone number for this call only, and the profile's primary number changes for good. Seed each scenario with existing values and assert which ones must survive. Decide per field whether the agent may replace, append or must ask.
The race after hangup
Many teams run a post-call job that summarises the call and sets a status. It fires on a hangup or room-closed event. The final transcript and tool results may not be complete yet.
LiveKit lists separate events for `participant_left`, `room_finished` and `egress_ended`, and says it sequences queued events, delivering newer ones only after older ones are delivered or abandoned. Those are different moments. A job keyed to the first one can read a transcript that is still short. That behaviour depends on your stack, so treat this as a pattern to test, not a fact about yours.
Test it by hanging up at different points: mid-confirmation, right after the write, and during the read-back. Then check three things. Did the record exist? Was it complete? Was the summary written once, from the final transcript? A cheap guard: wait for a "finalised" marker, and make the write safe to repeat. The deeper logging checklist is in what to log in voice agent calls.
Time, format and normalisation
Speech gives you "Thursday at three." Your record needs an absolute timestamp with a zone. Every step in between is a chance to be wrong by hours.
RFC 3339 defines a widely used internet timestamp profile: a date, a `T`, a time and a mandatory offset that is either `Z` or a numeric value. Offsets are "local time minus UTC," so `18:50:00-04:00` equals `22:50:00Z` (RFC 3339). The same standard notes that `-00:00` means the local offset is unknown, which is different from `Z`. A timestamp stored without an offset is a latent bug.
Daylight saving adds a second trap. Python's `zoneinfo` docs show that an ambiguous local time, such as 1:00 AM on the November fall-back date, takes the earlier offset when `fold=0` and the later one when `fold=1` (Python zoneinfo). The docs cover ambiguous times only, so test skipped spring-forward times in your own code.
Build these cases:
- The caller is in a different zone from the business.
- "Tomorrow" said at 11:50 PM.
- "Next Friday" said on a Wednesday and on a Friday.
- A booking on the DST change weekend.
- Phone numbers said with and without a country code.
- Names with apostrophes, hyphens and suffixes.
Store the zone as an IANA key, not a bare offset, wherever the future date matters. The appointment moves with the clock; a fixed offset does not.
Free-text notes and summaries
Free text is where PII leaks and where facts drift. NIST SP 800-122 says PII "should be protected from inappropriate access, use, and disclosure" and asks organisations to identify it by context (NIST SP 800-122). A call note that copies a card number or a date of birth is PII in the wrong place. Scan every text field for patterns after every test call. See voice agent PII leakage detection for the detection side.
Summaries create a second record. A generated note can say "customer asked to cancel" while the structured status says "rescheduled." Pick one source of truth. Compare every claim in the summary against the fields, and flag any disagreement.
How to run a golden-call and record-diff test
A golden call is a scripted call with a known expected record. The method has six steps.
1. Write the expected record. For each scenario, list every field and its correct value, as the caller meant it. Include fields that must stay unchanged.
2. Seed the system. Create the contacts, jobs and existing values the scenario needs. Use a fixed test tenant so you never touch live data.
3. Place the call. Use a synthetic caller with a fixed script, accent, pace and noise level. Record the audio and the call ID.
4. Wait for settle. Poll until the record reaches its final state or a timeout hits. A timeout is a failure, not a skip.
5. Read the record back. Use the same read path your customers' tools use. Fetch by ID, then fetch by the key a user would search.
6. Diff and score. Compare field by field with per-field rules. Log missing, wrong, extra, duplicate and overwritten values separately.

Here is a simplified record diff in Python. It normalises before it compares, so it flags real errors, not formatting noise.
from datetime import datetime
from zoneinfo import ZoneInfo
def same_instant(a, b):
return datetime.fromisoformat(a) == datetime.fromisoformat(b)
RULES = {
"start_at": same_instant,
"phone": lambda a, b: digits(a) == digits(b),
"service": lambda a, b: a.strip().lower() == b.strip().lower(),
}
def diff(expected, stored, preserve):
out = {"wrong": [], "missing": [], "overwritten": []}
for f, want in expected.items():
got = stored.get(f)
if got is None:
out["missing"].append(f)
elif not RULES.get(f, lambda a, b: a == b)(want, got):
out["wrong"].append((f, want, got))
for f, old in preserve.items():
if stored.get(f) != old:
out["overwritten"].append(f)
return outThe `digits` helper strips everything but digits. Keep rules strict. A loose comparison hides the bugs you came to find.
Scenario list
Keep scenarios as data, so any engineer can add one after an incident.
- id: wb-001-wrong-contact
seed: [{contact: A, phone: "+15550100"}, {contact: B, phone: "+15550100"}]
caller: {says_name: B, pace: fast}
expect: {updated_contact: B, untouched: [A]}
- id: wb-002-retry-duplicate
fault: {drop_response_after_commit: true}
expect: {rows_for_call: 1}
- id: wb-003-partial-write
fault: {fail_request: 2}
expect: {record_status: incomplete, agent_says_failed: true}
- id: wb-004-hangup-before-summary
hangup_at: after_write
expect: {record_exists: true, summary_count: 1}
- id: wb-005-timezone
caller: {zone: America/Los_Angeles, says: "tomorrow at 9"}
business_zone: America/New_York
expect: {start_at: "2026-11-06T09:00:00-08:00"}
- id: wb-006-overwrite
seed: [{contact: A, email: "a@example.com"}]
caller: {gives_second_email_for_this_call: true}
expect: {preserve: {email: "a@example.com"}}
- id: wb-007-pii-in-notes
caller: {reads_card_number: true}
expect: {note_matches_card_pattern: false}The scorecard
Score each run on counters, not one pass rate. This is the voice agent post call write back testing scorecard. The thresholds below are illustrative starting points, not published benchmarks. Tune them to what a wrong record costs you.
| Metric | Definition | Illustrative gate |
|---|---|---|
| Field exactness | Fields matching expected / fields checked | 99% on critical fields |
| Wrong-object rate | Writes to the wrong record / writes | 0 in the golden set |
| Duplicate rate | Calls with more than one row / calls | 0 |
| Missing-write rate | Success calls with no record / success calls | 0 |
| Overwrite count | Preserved fields changed | 0 |
| Settle time p95 | Hangup to final record state | Set from your own data |
| PII-in-notes hits | Pattern matches in free text | 0 |
Critical fields cause a wrong action: time, address, contact, amount and status. Weight them above cosmetic ones. For gating, see voice agent metric thresholds.
How many golden calls you need
Newman's 1995 JAMA letter, building on Hanley and Lippman-Hand, gives a quick rule: with zero events in N trials, the 95% upper bound on the event rate is about 3/N. His examples are 6% for 50 patients and 0.3% for 1,000 (JAMA).
Here is a worked example for write-back. If 100 golden calls produce zero wrong records, the true error rate could still be near 3%. Run 300 and the bound is about 1%. For a 0.3% ceiling you need about 1,000 runs. These are arithmetic from the rule, not data from any system. The rule assumes independent, identically distributed trials, so treat it as a floor for scripted calls. Use the rule to size your set, and to explain to a customer why "we tested 20 calls" is not proof.
Reconciliation in production
Voice agent reconciliation is the daily check that every call has exactly one correct record. Post-call data sync jobs and idempotency for a voice agent both depend on it. Golden calls prove the code path, and voice agent post call write back testing should not stop there. Reconciliation proves what happened to real callers. Run each reconciliation query daily and alert on any rows. They assume you store a call ID on every record and keep what the agent captured.
-- 1. Successful calls with no record
SELECT c.call_id FROM calls c
LEFT JOIN crm_records r ON r.source_call_id = c.call_id
WHERE c.outcome = 'booked'
AND c.ended_at < now() - interval '15 minutes'
AND r.id IS NULL;
-- 2. Duplicates
SELECT source_call_id, count(*) FROM crm_records
GROUP BY 1 HAVING count(*) > 1;
-- 3. Stored value differs from what the agent captured
SELECT c.call_id, c.captured->>'start_at' AS captured, r.start_at AS stored
FROM calls c JOIN crm_records r ON r.source_call_id = c.call_id
WHERE (c.captured->>'start_at')::timestamptz IS DISTINCT FROM r.start_at;Query 3 has a limit. It compares the agent's captured value with the record. It cannot tell you the capture itself was wrong. For that you need the audio, so sample calls for human review. Evalgent's production monitoring takes pushed call recordings, transcribes them, maps each to a scenario and judges it, and it flags tool-call failures and policy failures. That covers the call side of the join. Your reconciliation queries cover the record side. See monitoring voice agents in production for the wider setup.
Mapping status codes to agent behaviour
Decide in advance what the agent says for each outcome, and test each path.
| Write outcome | Agent should | Record should |
|---|---|---|
| 2xx, read-back matches | Confirm to the caller | Final state set |
| 2xx, read-back differs | Not confirm; retry or hand off | Flagged for review |
| 202 accepted | Say it is being processed | Pending until confirmed |
| 409 or duplicate key | Treat as already done, verify | Single row |
| 4xx validation | Re-ask the field or hand off | Nothing written |
| 5xx or timeout | Retry safely, then hand off | No partial row |
Honesty to the caller is part of the test. An agent that says "you're booked" over a failed write has made a promise your system does not keep. See also why voice agents fail in production.
Evalgent can generate scenarios from an agent's prompt and flows, covering states, branches and hard constraints, and runs simulated callers with chosen accents, pace and background noise. The record-diff step above is the part your team wires to its own system, since only you know the read path.
Frequently asked questions
What is post-call write-back in a voice agent?
Post-call write-back is the step where a voice agent, or a job after the call, stores results in a CRM, scheduler or ticket system. It covers fields, status and notes. Testing it means checking that the stored record matches what the caller said and meant, not just that the call sounded right.
How do you test that a voice agent wrote the right record?
Run a scripted golden call against a seeded test tenant, then read the record back by ID. Compare each field with the expected value using per-field rules. Also check for duplicates, missing writes and overwritten data. A pass needs every critical field to match and no extra rows.
Why do voice agents create duplicate CRM records?
Retries cause most duplicates. A write commits, the response is lost, and the client sends it again. HTTP POST is not guaranteed to be idempotent, so the server may create two rows. Use an idempotency key or a unique index on call ID plus operation, and test with a dropped response.
What is a silent failure in write-back?
A silent failure is a write that returns success but stores the wrong thing or nothing at all. Examples are a 200 on the wrong contact and a 202 that never completes. The agent tells the caller it worked. Only a read-back or reconciliation job reveals it.
How do you handle time zones when a voice agent books a time?
Resolve the caller's words into an absolute timestamp with an explicit offset, and store an IANA zone key when the date is in the future. Test cross-zone callers, late-night "tomorrow," and DST change weekends. A timestamp without an offset is a bug waiting to surface.
Should the agent write the call summary or should a separate job?
Either works if the summary never contradicts the structured fields. A separate job must wait for the final transcript and must be safe to run twice. Treat the structured fields as the source of truth, and test that every claim in the summary matches them.
How many test calls prove the write-back is reliable?
No count proves it. With zero failures in N runs, the 95% upper bound on the error rate is roughly 3/N, so 100 clean runs still allow about 3%. Use 300 for about 1% and 1,000 for about 0.3%. Keep sampling live calls after launch.
How do you reconcile call records with the CRM in production?
Store a call ID on every record, then run daily queries for calls with no record, calls with several records, and fields that differ from what the agent captured. Add human review of audio samples, because queries cannot tell whether the capture itself was wrong.
The bottom line
The record, not the recording, is what your customers and downstream systems act on, so test it as its own product. Replay golden calls, diff every field, inject retries and hangups, and reconcile live calls against records every day.
Related Articles

How to automate voice agent testing: synthetic callers vs manual QA
Learn how ai test automation replaces manual QA for voice agents. Compare synthetic callers vs human testers, with a 5-step framework to scale without hiring.
Read more
AI Agent Testing vs Voice Agent Testing: What General Tools Miss for Voice
AI agent testing measures text outputs. Voice agent testing measures behaviour through an acoustic pipeline. Five failure categories general tools miss.
Read more