Tickets

What a ticket is, how its orders are linked and verified, and when to refresh it.

View as Markdown

A ticket is one support conversation together with its customer and the orders linked to it. You read tickets in OneInbox, through MCP, or with the Context API.

A conversation has a ticket when the support classifier judged its latest customer message a support request. See List tickets for the details.

Stored, not live

Reading a ticket returns what EcomSquad has already stored. It never contacts the store. A separate refresh asks the store for current order information, stores the result, and returns the ticket with any newly matched orders.

So a connected store does not by itself mean a ticket has current order information. An order’s details is null until the orders sync or a store lookup has stored it, and its items stay empty until a store lookup or the shipping read has read the order.

Linked orders

Each order on a ticket says how it was linked in matchedBy:

  • email_lookup: the store returned the order for a lookup by the customer’s email address.
  • order_email: the order’s own synced record carries the customer’s email address, compared exactly: ASCII only, trimmed and lowercased, with no folding of dots or +tags. No lookup is needed.
  • second_check: the conversation names the order, and the customer wrote the order’s email address in their own reply, and it matched. This verifies that one order for reading only, never for a refund, cancellation or change.
  • order_mention: the conversation names the order number.
  • agent_link: an agent linked the order.

The first three verify an order. An order_mention also verifies when the customer quotes the number and their full first and last name matches the store’s customer record, after normalizing case, accents and whitespace. The name comes from their From display name or their own message or signature. A number alone stays unverified. verificationMethod records email or order_number_and_name, or null when unverified; verified says whether the bar was met. An unverified order keeps its name, dates, statuses and items, but has no tracking, no estimated delivery and no shipping updates: an empty tracking or events there means withheld, not absent.

A verified shipment on its way carries inTransitSince, when it went in transit. Some carriers report only milestones, such as in transit and delivered, so a parcel can go days without a new update. Only after 14 days with none does the ticket list a no_carrier_update unknown, whose detail is the time of the newest update.

Email verification means the address matched, not that the sender controls it. The sender’s address comes from the email and is never proven. Neither a mention nor an email match verifies who sent a message. An order read by name or ID alone, as Get an order does, names no customer, so it is always unverified.

Refresh a ticket

A refresh asks the Shopify store again for the ticket’s orders and its customer. It needs a key with Support context → Refresh (context:refresh) and access to both the mailbox and the store. Read the ticket first, and refresh only when the answer depends on order information it does not have yet:

What the ticket showsRefresh?
No orders, and the answer needs the customer’s ordersYes. The lookup by the customer’s email may find them.
An order whose details is nullYes. Nothing about that order has been stored.
An order whose details.items is emptyYes, if the answer needs its items.
Orders with details, items and fulfillmentsNo. Answer from the stored ticket, checking checkedAt.
Terminal
curl --fail-with-body --silent --show-error --request POST \
  --header "x-api-key: ${ECOMSQUAD_API_KEY:?Set ECOMSQUAD_API_KEY first}" \
  "${ECOMSQUAD_API_BASE_URL}/tickets/${ECOMSQUAD_TICKET_ID}/refresh"

Allow up to about 75 seconds. Keep routine reads on the GET endpoints. Answer from the returned ticket.orders, as after a plain read. If refresh.skipped is above zero, refresh again to continue. Refreshing twice in a row is safe: answers from the last five minutes are reused, so a retry after a timeout does not repeat store calls. Every field is in Refresh a ticket.

Refresh states

A refresh reports one refresh.state for the lookups it answered, with refresh.checkedAt saying when the newest ran.

StateMeaningWhat to do
noneThis refresh answered no lookup: the ticket gave it nothing to look up, or it skipped every lookup.If refresh.skipped is above zero, refresh again. Otherwise ask for an order number.
freshThe lookups deciding the answer succeeded.Answer from the ticket, checking the timestamps.
not_foundA lookup matched no order.Read refresh.attempts to see which. If the ticket names no order, ask for an order number.
outside_historyThe store did not return the order’s history.Ask the merchant to locate it.
access_missingNo usable store is connected, or the store has not granted the access needed.Ask your administrator to check the connection.
provider_unavailableThe store failed, rate-limited, or timed out.Wait, then refresh. Recent attempts are reused for five minutes.
not_permittedYour credentials can read the mailbox but not the store.Ask for access to the store connection as well.

refresh.attempts has one entry for each order name the ticket’s conversation mentions or an agent linked. refresh.customer is the lookup by the customer’s email address. It is shared by every conversation with that customer, so it may have run from another one. When the conversation names an order, the order lookups decide the state and the customer lookup is extra context. When it names none, the customer lookup decides.

An order the customer wrote that a store answered outside_history appears in the ticket’s unknowns as mentioned_order_outside_history, with the order name as its detail: the store did not return that order’s history. It does not say the order exists, so look it up in Shopify rather than tell the customer no store has it. A number no store has stays mentioned_order_not_found.

When a store’s customer record counts more orders for the customer’s email than that store’s email lookup returned and the synced orders hold, the ticket’s unknowns carry customer_orders_outside_history, with how many more orders the store counts as its detail, such as 2. The customer has orders: look them up in Shopify rather than tell the customer they have none. It takes the place of no_order_for_email.

A missing order can be a valid result. Explain what is missing rather than guessing. See Troubleshooting when order information is missing.