Pizza Where's My Order AI Calls: POS Lookup Failures

On this page
A caller asks, "Where's my order?" Every pizza where's my order AI faces this call, and the answer sits in a point-of-sale system your team did not build. The status field in that system was written for a kitchen, not for a caller. That gap is where an in-house voice agent goes wrong.
This post is for teams that built an AI phone agent for pizza shops into POS software, a delivery platform or an ordering app. Your agent now answers status calls for many shops at once. We cover pizza order status AI end to end: how it finds the right order, what each POS state really proves, and how to test all of it before a change ships. For the ordering side, see our posts on half-and-half orders and delivery address and callback number errors.
Why a status call is harder than it looks
For a pizza where's my order AI, status looks like the easy intent. No cart, no modifiers, no payment. The agent reads one field back.
It is not easy, for four reasons.
1. The caller is a stranger. Nobody typed an order number. The agent has a voice, maybe a caller ID, and a name said over background noise.
2. The status is a proxy. A POS status records what staff tapped, not where the pizza is.
3. The caller is already upset. People call because the pizza is late. They interrupt, repeat and ask for money back.
4. Every error sounds confident. "Your order is out for delivery" is a clean sentence even when it is false.
A wrong read-back also moves things downstream. The caller may cancel and reorder. A shop may remake a pizza that is already at the door. A refund may post twice. The call is the front door to a workflow: identify, look up, speak, and sometimes write back a note or a ticket. Each step can fail silently.
We covered the same pattern for car shops in status calls for auto repair and for phone repair ticket lookups. Pizza differs in one way. The order lives for about an hour, and its real state changes every few minutes.
A failure taxonomy for where's my order calls
Name the failures before you test them. Use this taxonomy to build scenarios. Each class has a different fix.
| Class | What happens | Example | Who owns the fix |
|---|---|---|---|
| Wrong match | Agent reads someone else's order | Two open orders for "Chris" | Matching logic |
| Stale status | POS lags reality | Shows "Active," pizza already left | POS data and wording |
| Status over-claim | Agent says more than the state proves | "On its way" from a READY state | Prompt and policy |
| Invented ETA | Agent produces a time with no source | "About ten minutes" | Prompt and tool design |
| Scope breach | Agent promises a remake or refund | "I've sent you a free pizza" | Policy and handoff |
The first two are data problems. The last three are behaviour problems. Test both kinds, because a perfect prompt cannot fix a stale field.
Matching the caller to the right open order
Before the agent says anything about status, it must pick one order. Treat matching as a ladder with a stop at every rung.

The ladder has four rungs.
1. Candidate set. Pull open orders for this shop, from the last few hours. Add orders matched by the calling number, but never use that number alone.
2. Count. Zero, one or many candidates. Each count has its own path.
3. Confirm a detail the caller supplies. Ask for something they know: the first item, the street, or the time they ordered. Do not read the details out and ask "is that right?"
4. Read the status. Only after a confirmed match.
The confirm step matters most. A caller hears "I found your order for Chris, a large pepperoni." They say "yes" to be helpful. If that order belongs to another Chris, you just disclosed it. Ask first: "What did you order?" Compare the answer in code.
Why names are weaker than they look
Take a peak hour with 25 open orders. Assume first names fall evenly across a pool of 120 names. This is an illustrative simplification. Real names cluster, so real odds are worse.
The chance that a given caller shares a first name with another open order is 1 - (1 - 1/120)^24. That is about 18%. With 12 open orders it is about 9%. At 25 open orders, the odds that some pair of orders shares a first name are about 93%.
So "same name, different order" is not a rare edge case on a busy night. It is a Friday evening regular. Your test set needs it as a standard scenario, not a corner case.
Phone number reuse and shared lines
Caller ID is a clue. It is not an identity.
- A household has one number and three orders in a month.
- A person orders from the office line and calls from a mobile.
- A number changes hands. The FCC's reassigned numbers announcement says millions of numbers are reassigned each year. An old order record may point at a stranger's phone.
- A third-party app places the order under its own relay number.
So the agent should treat a number match as a candidate filter. It then asks the caller to confirm one detail from the order.
Here is a simplified matcher. The logic is the point, not the exact fields.
from dataclasses import dataclass
from difflib import SequenceMatcher
@dataclass
class Order:
id: str
first_name: str
phone: str
items: list[str]
placed_minutes_ago: int
def similar(a: str, b: str) -> float:
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
def match_order(open_orders, caller_phone, said_item, said_name):
"""Return (order, reason). reason is one of: match, ask_more, handoff."""
pool = [o for o in open_orders if o.placed_minutes_ago <= 180]
by_phone = [o for o in pool if o.phone == caller_phone]
candidates = by_phone or pool # phone narrows, never decides
scored = []
for o in candidates:
item_hit = any(similar(said_item, i) >= 0.8 for i in o.items)
name_hit = similar(said_name, o.first_name) >= 0.8
scored.append((item_hit, name_hit, o))
both = [o for item, name, o in scored if item and name]
if len(both) == 1:
return both[0], "match"
if len(both) > 1:
return None, "ask_more" # ask time or street, never guess
if any(item or name for item, name, _ in scored):
return None, "ask_more"
return None, "handoff" # no match: do not disclose anythingThree details matter here. Two matches never resolve to the first one. A single weak match is not a match. And a miss ends in a handoff, not a guess.
What the POS status actually proves
Every POS order lookup ends in a status field from a POS or ordering API. That field has a meaning defined by the vendor, set by a person or a device, at a time that may differ from the real event. We checked three public references to show the range. These are examples, not a claim about any shop's setup.
Toast. Toast's guest order fulfillment status webhook lists RECEIVED, IN_PREPARATION, READY_FOR_PICKUP, CLOSED and VOIDED. READY_FOR_PICKUP is set by staff marking the order ready, or by the kitchen display system when it fulfills the order. The docs also say the webhook sends no message when an order is updated, such as an added drink. And updates go only to the third-party channel where the order was placed. An agent built on that webhook alone would never see an edit.
Toast's Orders Hub status breakdown adds more. Staff must tap Order Ready manually unless an expediter display is configured to do it. Complete is only available after the guest has paid. So "ready" can mean a person tapped a button, and "completed" can mean the money cleared, not that food arrived.
A Toast developer-community answer on when an order is done makes the same point. Item READY status appears only if the restaurant uses a Toast KDS. And `deliveryState` is available only if the restaurant uses its own drivers or an integration partner updates it. That is a community reply, not a spec, so check it against your own sandbox.
Square. Square's Orders API fulfillments guide defines six fulfillment states: PROPOSED, RESERVED, PREPARED, COMPLETED, CANCELED and FAILED. COMPLETED means shipped, picked up, delivered or handed over. The order state is separate from the fulfillment state. An order can be COMPLETED when no fulfillments are pending and payments are complete. The two fields answer different questions.
Delivery dispatch. Third-party delivery data, the kind behind pizza delivery status calls, adds another layer. DoorDash's Drive webhook reference lists events such as DASHER_CONFIRMED, DASHER_PICKED_UP, DASHER_DROPPED_OFF and DELIVERY_CANCELLED. It also has return events when a Dasher cannot deliver. The docs say events are sent as soon as they happen and give no guaranteed order, and retries can repeat a webhook. Your code has to handle repeats and gaps. Toast's delivery services status page lists statuses such as DRIVING TO DROP OFF, UNDELIVERABLE and DELIVERY CANCELLED, with a Redispatch option for staff.
None of these vocabularies has a state called "your driver is not answering." That is a call-time situation, and the agent has to recognise it from the caller, not the POS.
A trust table: state to allowed sentence
Map every POS state to a sentence the agent may say, and one it may not.

The rule is simple. The agent speaks to the state, not past it. "The kitchen has your order marked in preparation" is true when the state is IN_PREPARATION. "It's in the oven" is a guess. "It will be there in ten minutes" is an invention.
Write the table into code, not only into the prompt:
ALLOWED = {
"in_preparation": "The kitchen has it marked as being prepared.",
"ready": "It's marked ready at the shop.",
"out_for_delivery": "It's marked as handed to a driver.",
"delivered": "It's marked delivered.",
"cancelled": None, # handoff: never explain a cancel
"unknown": None, # handoff
}
def speak_status(state: str, minutes_since_change: int, stale_after: int = 20):
line = ALLOWED.get(state)
if line is None:
return "handoff", None
if minutes_since_change > stale_after:
line += " I can't confirm anything newer than that."
return "say", lineThe `stale_after` guard matters. A status that has not changed for a while is the one you should hedge. Pick the window per shop, because a thin-crust shop and a deep-dish shop bake at different speeds.
Stale status: the gap between the tap and the truth
Statuses lag because they depend on people. A cook taps Ready when the pizza leaves the oven. A driver never taps anything. The courier app updates when the driver remembers. The POS has the order as "Active" for ten minutes after it left.

The timeline shows an illustrative order, not measured data. Three gaps appear: the kitchen taps Ready late, dispatch records the driver late, and no one records a delay. A caller hits each gap at a different moment. A call in the first gap gets "in preparation" for a pizza that is boxed. A call in the second gets "ready" for one already on a road.
The agent cannot close these gaps. It can do two things.
- Say the source. "The shop's system shows..." shifts the claim from the pizza to the record.
- Offer the human path. If the status looks old, offer a staff callback or transfer.
Our post on evaluating an order status voice agent vendor covers how to probe staleness when you are buying. If you built the agent, the same probes apply to your own build.
ETAs and driver problems: what the agent must not say
An ETA is a promise. If the POS does not hold one, the agent should not make one.
Some systems do expose an estimate. DoorDash's webhook reference, for example, includes `pickup_time_estimated` and `dropoff_time_estimated` fields. If your data has a courier estimate, you can read it back, labelled as the courier's estimate. If it only holds a quote time from order placement, that time is stale the moment the kitchen runs behind.
Three rules cover most cases.
1. No time without a source. The agent may state a time only if a field supplies it, and must name whose estimate it is.
2. No blame. "The driver is lost" and "the kitchen is slammed" are guesses. Say what the record shows.
3. Unreachable driver means a human. If the caller says the driver is calling from the wrong street, or the status says UNDELIVERABLE or cancelled, hand off. The shop can call the driver. The agent cannot.
The address side of this lives in our post on pizza delivery address and callback number errors. If the stored address is wrong, a correct status read-back is still the wrong answer.
Remakes and refunds: route, do not decide
Many status calls turn into complaints within a minute. "It's been an hour. I want my money back." Or "It arrived cold. Send another."
The agent should not decide either. Money and food cost are shop decisions, and policies differ by shop. The right move is a short, calm handoff with a written note, so the caller does not repeat the story.
A good handoff record holds:
- the confirmed order ID, or "unmatched" if the match failed;
- the caller's stated complaint in one line;
- the status the agent read, with its timestamp;
- what the caller asked for: remake, refund, or call back.
Do not let the agent say "I've issued a refund" or "a new pizza is on its way" unless a tool actually did it and returned success. A spoken promise with no write-back is the worst outcome. The caller believes it, and no one acts on it.
Two cousins of this problem show up in other posts. Drive-thru upselling shows how a sales prompt can override a policy. And catering inquiries show the same handoff problem with larger tickets.
How to test a pizza order status agent before it takes live calls
Here is a protocol to run. It needs a staging copy of the POS data or a replay of order states, and a way to place calls.
1. Seed the order book. Create 20 to 30 open orders with controlled traps. Include same first names, one shared household phone, an order edited after it was sent, a cancelled order, and a courier return.
2. Write the scenario list from the agent's prompt. For each branch in your prompt, build at least one call. Add the cases the prompt forgot: caller on a different phone, order under a relay number, caller who has the wrong shop.
3. Vary the caller. Change accent, pace, interruption level, response delay and background noise. A kitchen-adjacent caller in a loud car is not a studio caller.
4. Freeze the state, then move it. Run each scenario at a fixed POS state. Then flip the state mid-call to see whether the agent re-reads or repeats the old value.
5. Score from the audio. Check what was actually said, not only the transcript. A name heard as "Kris" and written as "Chris" can produce a different match.
6. Check the write-back. Confirm that every handoff left a note with the fields listed above.
7. Rerun after every change. A new menu, a new prompt line or a new POS version can alter the output. Keep the suite and run it before each release.
Evalgent generates test scenarios from an agent's prompt and flows, including branches, policies and coverage gaps, and runs caller profiles over phone, SIP or WebRTC. It judges calls from the audio with evidence, and teams can review the verdicts. That fits steps 2 to 5, where manual testing runs out of hours.
Pass criteria
A pizza where's my order AI should pass every row. These thresholds are suggested starting points, not industry standards. Tune them to your risk.
| Check | Suggested threshold | Why |
|---|---|---|
| Wrong-order disclosure | 0 in the full suite | One leak is a privacy incident |
| Same-name scenarios resolved correctly | 100% | The agent must ask for more detail |
| Status sentence matches the allowed table | 100% | It is a lookup, not a judgment |
| Invented ETA | 0 | No source, no time |
| Refund or remake promised without a tool call | 0 | The caller will rely on it |
| Handoff note complete | at least 95% | Staff should not re-ask |
| Stale-state hedge when over the window | 100% | It prevents confident wrong answers |
Run each scenario more than once. Voice calls vary with noise and phrasing, so a single pass proves little. Ten repeats per high-risk scenario is a reasonable start, and any failure in a zero-tolerance row blocks the release.
Monitoring after launch
A suite covers what you thought of. Live calls cover the rest. Evalgent's production monitoring takes call recordings that you push, transcribes them, maps them to scenarios and judges them. It flags tool-call failures, policy failures, latency spikes and PII leakage, with fix recommendations. For this agent, the useful flags are a status read that conflicts with the lookup result, a number said that no field supplied, and a handoff with no note.
Review a sample of unmatched calls every week. They show you what callers say that your matcher never saw.
Frequently asked questions
How does an AI phone agent find a caller's pizza order?
It pulls open orders for the shop and narrows them by calling number, name and an item or street the caller supplies. The phone number only filters candidates. The agent should confirm one detail from the caller before it reads any status. If it cannot find a single match, it should hand off.
Can an AI agent give an ETA for a pizza delivery?
Only if a data field supplies one, and it should say whose estimate it is. A courier estimate can be read back as the courier's. A quote time from checkout is not a live estimate. Without a source, the agent should say what the record shows and offer a staff callback.
What should the AI say when the driver cannot be reached?
It should not guess. The agent should say that it cannot confirm the driver's location, note the complaint and transfer or schedule a callback from staff. The shop can phone the driver or redispatch. A status like UNDELIVERABLE or a courier cancellation should trigger the same handoff.
Why does the POS show the wrong status for a pizza order?
Statuses depend on people and devices. Staff must tap Order Ready unless a display does it, and couriers update when they can. Some systems skip edits or show states only with certain hardware. The status records a tap, not the pizza's location, so it can trail reality.
Should AI handle pizza refund and remake requests?
No. It should log the complaint, the order, the status it read and the request, then route to staff. Refund and remake rules are shop decisions. The agent must never say a refund or remake is done unless a tool completed it and returned success.
What if two open orders have the same name?
Treat it as normal on busy nights. The agent should ask for something only the caller knows, such as an item, the street or the order time. If two orders still match, it should hand off rather than pick one. Never read out the order details first.
How do you test an order status agent for a pizza shop?
Seed a staging order book with traps: same names, shared phones, edited orders, cancels and courier returns. Run scripted calls with varied accents, noise and interruptions. Score what the agent said from the audio, check handoff notes, and rerun after every prompt or POS change.
Does caller ID reliably match the pizza order?
No. Households share numbers, people call from other phones, numbers get reassigned and apps use relay numbers. Use caller ID to shortlist orders, then confirm a detail from the caller. Never read an order back on a number match alone.
The bottom line
A pizza where's my order AI call is a lookup that fails in quiet ways: the wrong order, an old status, an invented time. Build the match ladder, map each POS state to an allowed sentence, and test every change before it reaches live callers.
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