Skip to content

Extracting Order Numbers Referenced Inside a Support Ticket

9 min read · updated August 11, 2026

An order number is not an entity that exists in text. It is a key in your database that a customer has attempted to transcribe. That distinction decides the whole design: you are not trying to recognise order numbers, you are trying to generate candidates and let your own data tell you which one is real.

The shapes a customer sends

The same order arrives written half a dozen ways, because the customer is copying from wherever they can find it.

  • Bare. 1002345. No context, sometimes on its own line, sometimes mid-sentence.
  • Prefixed. #1002345, ORD-1002345, Order 1002345. The prefix may be yours or may be the customer’s own invention.
  • Punctuated. Dashes or spaces inserted where the confirmation email had them — or where it did not, because people group digits to read them back.
  • Inside a URL. Pasted from the order status page, so the number is a path segment or a query parameter, possibly alongside an opaque token that is not the order number and that looks far more like an identifier.
  • Quoted from a confirmation email. The whole original email is pasted below the question, which means the ticket now contains the order number, an invoice number, a customer number and a tracking number, all correctly formatted and all plausible.
  • In an image. A screenshot of the confirmation, with the number only in pixels. That is a vision-model path and it carries the character-confusion problem that handwritten order forms have in a milder form.
  • Not at all. “the blue chair I ordered last Tuesday” is a description, and the correct extraction is no candidate plus a flag, not a number.

What else is a six-digit number

A pattern like six-or-more consecutive digits has excellent recall and terrible precision, because a support ticket is full of numbers. Phone numbers, postcodes in some countries, tracking numbers, invoice numbers, VAT registration numbers, a date written as eight digits, the last four of a card with two digits of something else beside it, an amount in minor units, a Unix timestamp in a pasted log line, and a version string with the dots removed.

Some of these you can exclude cheaply and should. Carrier tracking numbers have documented formats and several carry check digits, so a candidate that validates as a tracking number is probably one. A candidate that parses as a plausible date in the file’s convention deserves suspicion. A candidate immediately preceded by a currency symbol is an amount.

But do not build a taxonomy of everything an order number is not. That list is unbounded and it will still be wrong. Generate candidates generously, attach the surrounding context to each, and resolve them against reality.

The lookup is the extractor

The design that works is three stages, and the middle one is not a model.

  1. Generate candidates. Run a permissive set of patterns over the stripped ticket text: digit runs, your known prefixes, URL path segments and query values, and any token matching your order-number shape. Keep the character offsets and about forty characters of context on each side. This stage is deterministic and should over-produce.
  2. Resolve against your order table. For each candidate, normalise — strip prefixes, dashes and spaces — and look it up, scoped to the requesting customer where you can identify them. This stage is a query, and it is what converts a plausible number into a known one. Nothing a model can say is worth as much as a row existing.
  3. Use a model only for what is left. Two cases: no candidate resolved and the customer described the order in prose, or several candidates resolved and you need to know which one the sentence is actually about. Both are language questions. Finding digit strings is not, and using a model for it makes a deterministic step intermittent.

The lookup also gives you an error-tolerant path that pure pattern matching cannot. If a candidate does not resolve exactly and it came from an image, retry the lookup over a bounded set of variants generated from the known confusion pairs — zero and the letter O, one and seven, five and S, eight and B. Accept a variant only if exactly one resolves and it belongs to the right customer; two matches means you have no answer, not a choice to make.

Keep the candidate list and the resolution outcome, not just the winner. When somebody asks why the agent-assist attached the wrong order, the answer is in the candidates you discarded, and without them the decision is unexplainable.

Order numbers are not globally unique

On several commerce platforms the customer-facing order identifier is a per-store sequence that starts low and is not unique across stores, while a separate internal identifier is the real primary key. If you operate more than one storefront — different brands, different countries, a marketplace — then the number in the ticket is meaningless without a store scope, and the same digits identify a different order in each one.

Worse, the customer-facing identifier is often configurable: a merchant can change the prefix, the suffix and the starting number, and orders created before the change keep the old shape. So a regex derived from today’s orders will miss last year’s, and a format assumption baked into a prompt will go stale silently.

Platform-specific identifier conventions change between API versions, and the distinction between a display name and an internal id is exactly the kind of detail that gets revised. Confirm the current behaviour in the platform’s own API reference rather than from a description of it, including which field a confirmation email actually prints.

Derive your patterns from your data instead of from documentation: query the distinct shapes of order identifiers actually present in your table, and generate the candidate patterns from that. It is a few lines, it is correct by construction, and it updates itself.

When it matches somebody else’s order

This is the case that turns an extraction problem into an authorisation problem, and it is the reason the resolution stage must be scoped rather than global.

A customer mistypes a digit and the resulting number is a valid order — belonging to a different person. An unscoped lookup finds it. An agent-assist panel that automatically surfaces the matched order then displays another customer’s name, address, items and total to whoever is handling the ticket, and in a self-service flow it can display them to the person who typed the number. That is a personal data disclosure caused by a helpful feature, and it will not appear in any accuracy metric you are tracking because the extraction was, in a narrow sense, correct.

  • Scope every resolution to the identified requester by default, and treat an out-of-scope match as no_match for display purposes while still recording it internally.
  • Make matched_other_account an explicit outcome value rather than folding it into no-match. It is a useful signal: a burst of them from one requester is worth looking at, and a steady trickle usually means your confirmation email prints a number that is easy to mistype.
  • Never auto-resolve a match on identity alone. Where a genuine cross-account case exists — a gift order, a company account, an order placed by a partner — it needs a human and a verification step, and the pipeline’s job is to surface the question rather than to answer it.
  • Record the outcome per candidate, not per ticket. One ticket routinely contains a matched order, an invoice number that matched nothing, and a tracking number, and the per-candidate record is what makes that legible later.

The general lesson generalises past order numbers: where an extracted value is a key into data you hold about other people, the extraction confidence and the authorisation decision are different questions, and a confidence score answers only the first one.