Skip to main content

Migrate to return processing

In API version 2025-07, Shopify introduced returnProcess to enhance the lifecycle management of returns. Processing a return means confirming the return items, exchange items, and return fees on the return, thereby updating a merchant's financial reports. This step will also result in exchange items being confirmed (if applicable).

This document outlines these updates in detail and offers migration best practices.


  1. Merchant reporting - Previously, a merchant's financial report would include a return when the return was created or approved. Moving forward, a return will only be recorded as a Sale when it has been processed.
  2. Confirming exchange items - Previously, creating or approving a return would immediately confirm an exchangeLineItem on the return and create a FulfillmentOrder. This will now only happen when the exchange line item has been processed.
  3. Holding exchange fulfillment orders - Previously, all fulfillment orders created for an exchange would automatically be placed on hold. Moving forward only exchanges with a balance owed by the buyer will be automatically placed on hold. An even or refundable exchange will not be placed on hold.

Note: In addition to new mutations, the way you obtain suggested refund and financial outcomes has changed. See Migrating from suggestedRefund to suggestedFinancialOutcome for details on updating your integration.


Anchor to Who needs to take action?Who needs to take action?

  1. Partners that create returns with an exchangeLineItem
  2. Partners that create returns with a ReturnShippingFee or RestockingFee
  3. Partners that assume a merchant's financial report will include returns when the return is created

Although issuing refunds to the original payment method on a return will continue to work with returnRefund or refundCreate, returnRefund will be a legacy API, and using refundCreate for returns causes undesired effects for certain cases (example: risk of refunding the wrong item when there are multiple quantities of the same item on an order). The best practice is to integrate with returnProcess for this use case as well.


Anchor to Key changes with return processingKey changes with return processing

The following table outlines the key differences introduced with return processing:

ActionBefore return processingAfter return processing
Return items are recorded as a SalereturnCreate or returnApproveRequestreturnProcess or returnRefund or refundCreate
Exchange items are confirmed and fulfillment orders are createdreturnCreate or returnApproveRequestreturnProcess
Return fees are recorded as a SalereturnCreate or returnApproveRequestreturnProcess

Follow these steps to migrate your app from the legacy returns API to the return processing API:

  1. Update APIs for refunds on returns with and without return fees: Replace calls to the legacy refund mutations with the return processing mutation.
  2. Adapt your return workflow for exchanges: Call the return processing mutations on returns with exchange line items.
  3. Update webhook handlers: Subscribe to the new return/process webhooks (if applicable).
  4. Test your integration: Ensure your app works correctly with the new API.

Anchor to Step 1: Update APIs for refundsStep 1: Update APIs for refunds

Anchor to Migrating from the legacy ,[object Object], mutation to the new ,[object Object], mutationMigrating from the legacy returnRefund mutation to the new returnProcess mutation

Before: Using returnRefund

mutation ReturnRefund {
returnRefund(
returnRefundInput: {
# The ID of the return to refund
returnId: "gid://shopify/Return/3769532472",

# Return line items to refund
returnRefundLineItems: [
{
returnLineItemId: "gid://shopify/ReturnLineItem/5392564280",
quantity: 1
}
],

# Refund shipping costs
refundShipping: {
shippingRefundAmount: {
amount: 20.00,
currencyCode: USD
}
},

# Payment transaction information from the suggestedTransactions
orderTransactions: [
{
# Parent transaction ID
parentId: "gid://shopify/OrderTransaction/15011711647800",

# Amount to refund
transactionAmount: {
amount: 124.64,
currencyCode: USD
}
}
],

# Optional: Notify the customer about the refund
notifyCustomer: true
}
) {
refund {
id
createdAt
note
totalRefundedSet {
shopMoney {
amount
currencyCode
}
presentmentMoney {
amount
currencyCode
}
}
# Return associated with this refund
return {
id
status
}
}
userErrors {
field
message
code
}
}
}

After: Using returnProcess

mutation ReturnProcess {
returnProcess(
input: {
# The ID of the return to process
returnId: "gid://shopify/Return/3774119992",

# Return line items with disposition decisions
returnLineItems: [
{
id: "gid://shopify/ReturnLineItem/5398200376",
quantity: 1,
dispositions: [
{
reverseFulfillmentOrderLineItemId: "gid://shopify/ReverseFulfillmentOrderLineItem/3367010360",
quantity: 1,
locationId: "gid://shopify/Location/67829006392", # Required for RESTOCKED
dispositionType: RESTOCKED
}
]
}
],

# Financial transfer information (refunds)
financialTransfer: {
issueRefund: {
orderTransactions: [
{
# Parent transaction ID from the response
parentId: "gid://shopify/OrderTransaction/15012547592248",

# Amount to refund from the response
transactionAmount: {
amount: 329.25,
currencyCode: USD
}
}
]
}
},

# Optional: Notify the customer about the refund
notifyCustomer: true
}
) {
return {
id
status
refunds(first: 10) {
edges {
node {
id
createdAt
totalRefundedSet {
shopMoney { amount currencyCode }
presentmentMoney { amount currencyCode }
}
}
}
}
}
# Any user-level errors
userErrors {
field
message
code
}
}
}

Key differences between returnRefund and returnProcess

  1. Combined process: returnProcess handles both disposition decisions (restock/not restock) and financial processing in a single call, while returnRefund only handled the financial aspect.

  2. Structure changes: Financial information is now under the financialTransfer object instead of being directly at the root level of the input.

Tip: When preparing inputs for the new returnProcess mutation, use the suggestedFinancialOutcome field on the return object to generate recommended values for refunds, exchanges, and fees. This replaces the legacy suggestedRefund field.

Anchor to Migrating from the ,[object Object], mutation to the new ,[object Object], mutationMigrating from the refundCreate mutation to the new returnProcess mutation

Before: Using refundCreate

mutation CreateRefund {
refundCreate(
input: {
# The ID of the order to refund
orderId: "gid://shopify/Order/13788583067704"

# Optional: Add a note about why the refund is being issued
note: "Full refund for order #1856"

# Optional: Whether to notify the customer about the refund
notify: true

# Line items to refund with restock information
refundLineItems: [
{
lineItemId: "gid://shopify/LineItem/39756510527544",
quantity: 1,
restockType: RETURN,
locationId: "gid://shopify/Location/67829006392"
},
{
lineItemId: "gid://shopify/LineItem/39756510560312",
quantity: 1,
restockType: RETURN,
locationId: "gid://shopify/Location/67829006392"
}
],

# Transaction details for the refund
transactions: [
{
amount: "1426.75",
gateway: "manual",
kind: REFUND,
orderId: "gid://shopify/Order/13788583067704",
parentId: "gid://shopify/OrderTransaction/15012617093176"
}
]
}
) {
refund {
id
note
totalRefundedSet {
shopMoney {
amount
currencyCode
}
}
}
userErrors {
field
message
}
}
}

After: Using returnProcess

mutation ProcessReturn {
returnProcess(
input: {
# The ID of the return to process
returnId: "gid://shopify/Return/3774119992",

# Return line items with disposition decisions
returnLineItems: [
{
id: "gid://shopify/ReturnLineItem/5398200376",
quantity: 1,
dispositions: [
{
reverseFulfillmentOrderLineItemId: "gid://shopify/ReverseFulfillmentOrderLineItem/3367010360",
quantity: 1,
locationId: "gid://shopify/Location/67829006392",
dispositionType: RESTOCKED
}
]
},
{
id: "gid://shopify/ReturnLineItem/5398200377",
quantity: 1,
dispositions: [
{
reverseFulfillmentOrderLineItemId: "gid://shopify/ReverseFulfillmentOrderLineItem/3367010361",
quantity: 1,
locationId: "gid://shopify/Location/67829006392",
dispositionType: RESTOCKED
}
]
}
],

# Financial transfer information (refunds)
financialTransfer: {
issueRefund: {
orderTransactions: [
{
parentId: "gid://shopify/OrderTransaction/15012617093176",
transactionAmount: {
amount: "1426.75",
currencyCode: USD
}
}
]
}
},

# Optional: Notify the customer about the refund
notifyCustomer: true
}
) {
return {
id
status
refunds(first: 5) {
edges {
node {
id
totalRefundedSet {
shopMoney { amount currencyCode }
}
}
}
}
}
userErrors {
field
message
}
}
}

Key differences between refundCreate and returnProcess

  1. Return-based vs Order-based: returnProcess works with return objects rather than directly with orders, providing better lifecycle management of returns.

  2. Line item precision: With refundCreate, which references order LineItems directly, there's no guarantee that you are refunding the item you intended to refund when multiple quantities of the same product exist on an order. returnProcess uses ReturnLineItems that are specifically associated with the items being returned, ensuring the exact returned items are properly processed.

  3. Comprehensive return handling: returnProcess can handle all return types including exchanges, whereas refundCreate only works for net refundable returns. This means returnProcess provides a complete solution for the entire returns lifecycle, including exchange fulfillment orders.

  4. Return fee support: returnProcess can record return fees (such as restocking or shipping fees) on the sales ledger, providing accurate financial reporting for merchants. refundCreate lacks this capability, potentially leading to inconsistencies in financial reporting for return-related fees.

Anchor to Migrating from ,[object Object], to ,[object Object]Migrating from suggestedRefund to suggestedFinancialOutcome

With the introduction of the new return processing API, Shopify has also modernized how apps can obtain suggested financial transactions for a return. Previously, the suggestedRefund field on the return object provided a recommended refund structure, typically used as input to the legacy refundCreate mutation. Now, the suggestedFinancialOutcome field provides a more comprehensive and flexible suggestion, designed to be used with the new returnProcess mutation.

This section explains the differences between these two fields and how to migrate your integration.

AspectsuggestedRefund (Legacy)suggestedFinancialOutcome (New)
PurposeSuggests refund amounts for a return, to be used with refundCreate or returnRefundSuggests a complete financial outcome (refunds, exchanges, fees, allocations) for a return, to be used with returnProcess
Inputs- Return refund line items
- Shipping refund
- Duties
- Additional fees
- (Optionally) apply deductions
- Return line items
- Exchange line items
- Shipping refund
- Duties
- Additional fees
- Refund method allocation (e.g., original payment method, store credit)
CoverageFocused on refunds onlyHandles refunds, exchanges, fees, and supports allocation of refund methods
Output StructureRefund amounts and related fieldsDetailed breakdown of all financial components, including refund methods, transactions, deductions, and more
Mutation UsagerefundCreate or returnRefundreturnProcess
ExtensibilityLimitedDesigned for future extensibility and more complex return scenarios

Before: Using suggestedRefund

query {
return(id: "gid://shopify/Return/123") {
suggestedRefund(
returnRefundLineItems: [
{ returnLineItemId: "gid://shopify/ReturnLineItem/456", quantity: 1 }
],
refundShipping: {
shippingRefundAmount: { amount: "5.00", currencyCode: USD }
},
refundDuties: [
{ dutyId: "gid://shopify/Duty/789", refundType: FULL }
],
refundAdditionalFees: [
{ additionalFeeId: "gid://shopify/AdditionalFee/101" }
]
) {
amount {
shopMoney { amount currencyCode }
}
# ...other fields
}
}
}

The result would be used as input to the refundCreate or returnRefund mutation.

After: Using suggestedFinancialOutcome

query {
return(id: "gid://shopify/Return/123") {
suggestedFinancialOutcome(
returnLineItems: [
{ id: "gid://shopify/ReturnLineItem/456", quantity: 1 }
],
exchangeLineItems: [
{ id: "gid://shopify/ExchangeLineItem/789", quantity: 1 }
],
refundShipping: {
shippingRefundAmount: { amount: "5.00", currencyCode: USD }
},
refundDuties: [
{ dutyId: "gid://shopify/Duty/789", refundType: FULL }
],
refundAdditionalFees: [
{ additionalFeeId: "gid://shopify/AdditionalFee/101" }
],
refundMethodAllocation: ORIGINAL_PAYMENT_METHODS
) {
totalReturnAmount { shopMoney { amount currencyCode } }
financialTransfer {
suggestedTransactions {
amountSet { shopMoney { amount currencyCode } }
# ...other fields
}
suggestedRefundMethods {
amount { shopMoney { amount currencyCode } }
# ...other fields
}
# ...other fields
}
selectedDeductions {
returnShippingFeesSubtotal { shopMoney { amount currencyCode } }
restockingFeesSubtotal { shopMoney { amount currencyCode } }
# ...other fields
}
# ...other fields
}
}
}

The result is then used as input to the returnProcess mutation, which can process refunds, exchanges, and fees in a single call.

  1. Update your query: Replace any usage of suggestedRefund with suggestedFinancialOutcome in your GraphQL queries. Adjust the input arguments to include both return and exchange line items, as well as any new fields relevant to your workflow (such as refundMethodAllocation).
  2. Review the output: The structure of the response is more detailed. Update your code to extract the relevant suggested transactions, refund methods, and deductions as needed.
  3. Prepare the mutation input: Use the output from suggestedFinancialOutcome to construct the input for the returnProcess mutation. You may modify the suggested values as needed to fit your business logic.
  4. Test thoroughly: Ensure that your integration correctly handles all return scenarios, including refunds, exchanges, and fees.

  • Use suggestions as a starting point: The values returned by suggestedFinancialOutcome are recommendations. You can modify them before passing to returnProcess if your workflow requires it.
  • Handle errors gracefully: Both fields may return user errors if the input is invalid (e.g., mismatched currencies, invalid IDs). Ensure your integration checks for and handles these errors.
  • Support new features: The new API supports more complex scenarios, such as allocating refunds to store credit or handling multiple exchange line items. Take advantage of these features to provide a better merchant and buyer experience.

Anchor to Step 2: Adapt your return workflow for exchangesStep 2: Adapt your return workflow for exchanges

Another key change with return processing is the workflow for exchanges:

Before: Partners only needed to call returnCreate or returnAppproveRequest to create a return with exchange line items. This automatically confirmed exchange items and created fulfillment orders.

Now: Partners must follow a two-step process:

  1. Call returnCreate or returnAppproveRequest to create the return with exchange line items
  2. Call returnProcess to confirm the return and exchange line items

The returnProcess call is now required to:

  1. Commit those items to the sales ledger
  2. Create fulfillment orders for the exchange items

Additionally, whereas previously all fulfillment orders driven by exchanges would be placed on hold, now only fulfillment orders created for exchanges that have a net payable balance due by the buyer will be placed on automatic hold.

Anchor to Example: Processing a net even exchangeExample: Processing a net even exchange

mutation ReturnProcess {
returnProcess(
input: {
# The ID of the return to process
returnId: "gid://shopify/Return/3774152760",

# Return line items with disposition decisions
returnLineItems: [
{
id: "gid://shopify/ReturnLineItem/5398233144",
quantity: 1,
dispositions: [
{
reverseFulfillmentOrderLineItemId: "gid://shopify/ReverseFulfillmentOrderLineItem/3367043128",
quantity: 1,
locationId: "gid://shopify/Location/67829006392", # Required for RESTOCKED
dispositionType: RESTOCKED
}
]
}
],
# Exchange line items to process
exchangeLineItems: [
{
id: "gid://shopify/ExchangeLineItem/120586296",
quantity: 1
}
],
# Optional: Notify the customer about the exchange
notifyCustomer: true
}
) {
return {
id
status
refunds(first: 10) {
edges {
node {
id
createdAt
totalRefundedSet {
shopMoney { amount currencyCode }
presentmentMoney { amount currencyCode }
}
}
}
}
}
# Any user-level errors
userErrors {
field
message
code
}
}
}

In this example, the mutation processes both the return line items (with restock decisions) and the exchange line items. Since this is a net even exchange (no additional payment required from the buyer), the created fulfillment orders will not be placed on hold and can be fulfilled immediately.


Anchor to Step 3: Update webhook handlersStep 3: Update webhook handlers

Subscribe to the new returns/process webhook. This webhook will be triggered when a return has been fully or partially processed. The return's status is CLOSED when all items have been processed and a restock decision has been made.


Anchor to Step 4: Test your integrationStep 4: Test your integration

Before deploying to production, thoroughly test your updated integration:

  1. Create test orders and initiate returns
  2. Process returns using the returnProcess mutation
  3. Verify webhook handling for all return events
  4. Confirm that exchanges are properly created and managed
  5. Validate that refunds are accurately issued

Return processing is available starting in API version 2025-07. Existing return apps that use returnRefund or refundCreate must migrate to returnProcess to ensure consistent behavior and avoid disruptions.



  • Update all refund and return processing flows to use returnProcess instead of refundCreate/returnRefund
  • Replace all queries to suggestedRefund with suggestedFinancialOutcome
  • Update your code to handle the new output structure from suggestedFinancialOutcome
  • Test all return, refund, and exchange scenarios end-to-end
  • Update documentation and team knowledge to reflect these changes

Was this page helpful?