Type: Developer Guide / Operations · Stage: Scale · Topic: Orders / Async GraphQL / ERP Integration
Quick answer
A successful orderCancel API response is not proof that Shopify finished cancelling the order. Cancellation is asynchronous. Starting with GraphQL Admin API 2026-10, the mutation exposes jobResult, a cancellation-specific object with id, done, status, errors, and the affected order.
Save the job-result ID, poll it with node(id:) until done is true, and only treat the cancellation as complete when the result reports SUCCEEDED. Keep the order out of an irreversible ERP or warehouse handoff while its cancellation is unresolved.
On this page
Why an accepted cancellation can still fail
An order-management integration often has two separate systems moving at once: Shopify receives a cancellation request while an ERP or 3PL is deciding whether to pick, ship, refund, or restock. If your worker marks the order cancelled immediately after the mutation returns, it can tell the warehouse to stop even though Shopify later rejects the cancellation. The reverse is worse: it can release a shipment while a cancellation is still pending.
On October 1, 2026, Shopify added OrderCancelJobResult to the mutation response. The existing generic job field still works, so this is an improvement you can adopt without breaking older integration logic. Unlike the generic job, the structured result reports a cancellation-specific status and errors.
Shopify can refuse cancellation when an order has already been cancelled, has active returns, has certain pending payment or fulfillment conditions, or cannot restock to a deactivated location. These are operational outcomes, not necessarily GraphQL transport errors.
Request cancellation and retain its tracking ID
This example deliberately disables automatic refunding and restocking; choose those settings from the merchant’s actual policy. Never copy them blindly into production.
mutation CancelOrder($orderId: ID!) {
orderCancel(
orderId: $orderId
reason: CUSTOMER
restock: false
refundMethod: { originalPaymentMethodsRefund: false }
notifyCustomer: false
) {
jobResult {
id
done
status
errors { field message code }
order { id cancelledAt }
}
orderCancelUserErrors { field message code }
}
}
Use a real order GID for $orderId. The mutation requires the applicable order-writing scope and permissions; cancellation is irreversible. Inspect top-level GraphQL errors and orderCancelUserErrors immediately. If the mutation returns a job result, persist its id with the original order ID and the cancellation request’s internal correlation ID.
Do not interpret done: false as a failed cancellation. It means the work is not finished. Equally, do not treat an empty immediate orderCancelUserErrors array as the final result.
Poll the structured result instead of resending the mutation
Query the job-result node by its returned ID:
query CancellationResult($id: ID!) {
node(id: $id) {
... on OrderCancelJobResult {
id
done
status
errors { field message code }
order { id cancelledAt }
}
}
}
The documented statuses are STARTED, SUCCEEDED, and FAILED. Poll with a bounded delay and backoff, respecting GraphQL cost limits. Once done is true, inspect status, the error list, and the order’s final state. A missing result, transport failure, or timeout should become an unknown/pending operational state, not an automatic retry of orderCancel.
| Result | Integration action |
|---|---|
STARTED or done: false |
Keep the cancellation pending; poll again. |
SUCCEEDED and done: true |
Re-read the order, then reconcile the ERP, warehouse, refund, and stock records. |
FAILED and done: true |
Record structured errors; alert the operator; do not claim cancellation succeeded. |
| No response or ambiguous network failure | Read current order state and stored job before deciding whether any new mutation is safe. |
Design the handoff as a state machine
Use explicit internal states such as cancellation requested, Shopify processing, Shopify cancelled, Shopify cancellation failed, and warehouse action required. Those states should be driven by confirmed facts, not by a tag or by whether the API request returned HTTP 200.
- Check whether the order is already cancelled or has a fulfillment that cannot be stopped.
- Record a cancellation intent and, if supported, place a temporary hold in the warehouse system before calling Shopify.
- Submit
orderCancelonce, saving the returned result ID. - Poll the result; preserve a retry budget and an escalation path for ambiguous outcomes.
- After
SUCCEEDED, verifycancelledAt, financial treatment, inventory behavior, and fulfillment state before closing the ERP ticket. - If the warehouse already shipped the order, use a return or exception process instead of pretending cancellation reversed shipment.
For partial line-item changes, consider the supported order-editing or refund workflow rather than cancelling the entire order. A buyer’s request to edit an order is also not the same thing as a completed cancellation; see Shopify Dude’s buyer-requested order-edit guide.
Common misunderstanding
“The mutation returned 200, so the order is cancelled.” GraphQL transport success and cancellation completion are different. “The new field replaces job and breaks old clients.” Shopify retained job. “Retrying orderCancel is the safest way to check.” No: poll the job-result node and re-read the order first. Repeated mutation attempts can create noisy, ambiguous error handling around an irreversible action.
How to test this
- In a development store, create an eligible unfulfilled test order and record its payment and inventory state.
- Submit cancellation with an explicit refund/restock policy; verify that the mutation returns
jobResult.idand no immediate user errors. - Poll the node until
doneand verifystatus: SUCCEEDEDplus the order’scancelledAtvalue. - Confirm the ERP and 3PL change state only after the confirmed outcome.
- Try cancelling an already cancelled order and an order with an ineligible fulfillment or return condition; verify the error path.
- Simulate a timeout after the initial mutation; confirm the worker reconciles the existing order/job rather than blindly resubmitting.
- Repeat with refunding and restocking enabled in a controlled test and inspect the financial and inventory side effects.
Sources and further reading
Documentation checked October 24, 2026.

