Migrate from legacy to 2025-07
The following guide will help you migrate the API and webhook from the legacy version to version 2025-07.
1. General API Changes
1.1 Endpoint Change
What's the difference?
| Change | Before | After |
|---|---|---|
| Base URL | https://api.aftership.com/returnscenter/v2 | https://api.aftership.com/returns/2025-07 |
| as-api-version response header value | - | 2025-07 |
| as-webhook-version request header value | - | 2025-07 |
What do I need to do?
Make the following updates to your application's code:
- Replace the base URL from
https://api.aftership.com/returnscenter/v2tohttps://api.aftership.com/returns/2025-07. - When editing existing URLs or adding new ones, select the webhook version. Please review your webhook settings for verification.
- Parse the header to distinguish between different versions. Otherwise, no action is required.
2. API Endpoint Updates
2.1 Update Return Status (POST /returns/rma/{rma_number}/status-transitions)
What's the difference?
The endpoint has been split into three different endpoints in the 2025-07 version:
POST /returns/rma/{rma_number}/approvePOST /returns/rma/{rma_number}/resolvePOST /returns/rma/{rma_number}/reject
The original endpoint had 4 types of request bodies, each corresponding to a different case. The migration can be done for each of the use cases:
2.1.1 Simple status transitions
Legacy
preparing...
2025-07
In the new API version, you can call the status endpoint directly. There is no need to manually retrieve from_status as required in the legacy version.
preparing...
2.1.2 Rejection
Legacy
preparing...
2025-07
In the new API version, you can directly call the reject endpoint.
preparing...
2.1.3 Approval with label URL - Deprecated flow
Legacy
preparing...
2025-07
In the initial Returns system, merchants had to provide a label before approving a return, and the API was designed around that workflow. In the latest system, label submission and approval are two separate actions. As of the 2024-10 version, the approve endpoint no longer accepts a label URL. If you still need to provide a label, please use the attach shipment endpoint instead.
2.1.4 Approval and create a label
Legacy
preparing...
2025-07
In the new API version, you can use the approve endpoint with generate_label.
preparing...
What do I need to do?
- Replace the deprecated status-transitions endpoint with the corresponding new endpoint (
approve,resolve, orreject). - Remove the
from_statusandto_statusfields from the request payload. - For approvals that require providing a label URL, use the attach shipment endpoint separately after approval.
- Add the new optional field
notify_customerto control whether the customer receives an email notification.
2.2 Batch mark items as received (POST /returns/rma/{rma_number}/item-receive-batch)
What's the difference?
| Change | Before | After |
|---|---|---|
| Endpoint | POST /returns/rma/{rma_number}/item-receive-batch | POST /returns/rma/{rma_number}/receive-items |
| Field name | send_notification | notify_customer |
| Response payload | Legacy response structure | New Return Object |
Legacy
Request:
preparing...
2025-07
Request:
preparing...
What do I need to do?
- Update the endpoint from
item-receive-batchtoreceive-items. - Replace
send_notificationwithnotify_customerin the request body. - Update your code to parse the new Return Object structure in the response.
2.3 Update return items By RMA (PATCH /returns/rma/{rma_number}/items/{external_id})
What's the difference?
| Change | Before | After |
|---|---|---|
| Field name | merchant_tags | item_tags |
| Field name | merchant_images | merchant_uploaded_image_urls |
| Field type | merchant_images is an array of objects | merchant_uploaded_image_urls is an array of strings |
| Response payload | Legacy response structure | New ReturnItem Object |
Legacy
Request:
preparing...
OR
preparing...
2025-07
Request:
preparing...
OR
preparing...
What do I need to do?
- Replace
merchant_tagswithitem_tags. - Replace
merchant_imageswithmerchant_uploaded_image_urlsand change the structure from an array of objects to an array of strings. - Update your code to parse the new ReturnItem Object structure in the response.
2.4 Batch attach shipments to an RMA (POST /returns/rma/{rma_number}/shipment-batch)
What's the difference?
| Change | Before | After |
|---|---|---|
| Endpoint | POST /returns/rma/{rma_number}/shipment-batch | POST /returns/rma/{rma_number}/attach-shipments |
| Field name | shipments\[\*\].label.aftership_tracking_slug | shipments\[\*\].slug |
| Field type | shipments\[\*\].label.total_charge.amount is a number | shipments\[\*\].label.total_charge.amount is a string |
| Field name | send_notification | notify_customer |
| Response payload | Legacy response structure | New Return Object |
Legacy
Request:
preparing...
2025-07
Request:
preparing...
What do I need to do?
- Update the endpoint from
shipment-batchtoattach-shipments. - Move
aftership_tracking_slugout of thelabelto the top level of the shipment object and rename it toslug. - Change
label.total_charge.amountfrom a number to a string. - Replace
send_notificationwithnotify_customer. - Update your code to parse the new Return Object structure in the response.
2.5 List Return Item Tags (GET /merchant-tags)
What's the difference?
| Change | Before | After |
|---|---|---|
| Endpoint | GET /merchant-tags | GET /item-tags |
| Field name | merchant_tags | item_tags |
Legacy
Request:
preparing...
2025-07
Request:
preparing...
What do I need to do?
- Update the endpoint from
/merchant-tagsto/item-tags. - Update your code to parse
item_tagsinstead ofmerchant_tagsin the response.
3. Webhook Updates
3.1 Webhook Design Changes
To address several limitations in the Legacy webhook version, we introduced a redesigned webhook starting from the 2024-07 version. The main improvements are as follows:
- More detailed events
The legacy version offered only a small set of events with limited support for real-world scenarios. This made it difficult for merchants to automate their workflows effectively. - Redesigned the payload structure
Starting July 2024, we introduced two fields in the webhook:modifiedanddata.
-
In the legacy version, the webhook payload consisted solely of an event name and a full Return object. As a result, developers were unable to accurately determine what had changed at the time the event occurred. For example, when receiving a
return.shipment.createdevent, developers couldn't determine which specific shipment had been created based on the event payload. -
The new webhook introduces a
modifiedfield to indicate the specific piece of data that changed or was created in each particular event, aka delta data. For areturn.shipment.createdevent, themodifiedfield includes only the newly created tracking number, label PDF, and related details -
The
datafield continues to return the Return object as it exists at the time the event occurs.
3.2 Webhook Event Mapping
If you are using events from the legacy version, we expect you will find corresponding alternatives in the 2025-07 version:
| Legacy Events | Alternatives in 2025-07 |
|---|---|
| return.created | return.submitted |
| return.updated | The return.updated event in the legacy version was a coarse-grained event triggered by any update in the return would trigger this event. In the 2025-07 version, finding a corresponding alternative depends on how you previously used this event. For developers who used this event to receive primary status changes, you can use events such as return.approved, return.resolved, and others. |
| return.approved | return.approved |
| return.shipment.created | If the shipment isprovided by the merchant, use return.shipment.provided. If the shipment is provided by the shopper, use return.shipment.recorded instead. |
| return.shipments.created | If the shipment is provided by the merchant, use return.shipments.provided. Currently, multiple shipments from shoppers are not supported, so no alternative exists for this case. |
| return.shipment.updated | return.shipment.updated |
| return.dropoff.created | return.dropoff.created |
| return.dropoff.updated | return.dropoff.updated |
| return.dropoff.shipment.updated | return.dropoff.shipment.updated |
3.3 Return Object Field Mapping
The following table provides a comprehensive mapping of fields from the legacy Return object to the 2025-07 version. Use it as a reference to update your code when migrating from the legacy API to the 2025-07 version.
What's the difference?
The Return object structure has been significantly refactored in the 2025-07 version. Many fields have been renamed, restructured, or relocated within the object hierarchy. Some fields have been removed entirely, while new fields have been introduced to improve clarity and organization.
What do I need to do?
- Review the field mapping table below to identify changes for fields you currently use.
- Update your code to use the new field names and paths.
- Handle removed fields by using alternative fields or removing dependencies.
- Update your data models and parsing logic to match the new Return Object structure.
| legacy | 2025-07 | remark |
|---|---|---|
| status | approval_status | Field name changed. Enum values updated (removed shipped, partially_received, and received). The legacy version incorrectly mixed approval_status and shipping_status. |
internal_notes | merchant_note | Field renamed |
updated_at | - | Removed. Use other timestamp fields instead. |
tracking_number | - | Removed. Use shipments\[\*\].tracking_number instead. Previously existed because the legacy version was developed before multiple shipments were supported. |
order.customer_name | - | Removed. Contact support if you need this field in the new version. |
order.customer_email | order.customer.emails\[0\] | Structure changed from a single string to an array. |
order.country | order.country_region | Field renamed. Type changed from string to object. |
order.app.key | order.store.external_id | Path changed |
order.app.platform | order.store.platform | Path changed |
exchange_order.id | exchange.order.id | Path changed |
exchange_order.order_number | exchange.order.order_number | Path changed |
exchange_order.external_id | exchange.order.external_id | Path changed |
exchange_order.customer_name | - | Removed |
exchange_order.customer_email | exchange.order.customer.emails\[0\] | Path and structure changed |
exchange_order.country | exchange.order.country | Path changed |
exchange_order.app.key | exchange.order.store.external_id | Path changed |
exchange_order.app.platform | exchange.order.store.platform | Path changed |
return_items[*].external_id | return_items[*].external_order_item_id | Path changed |
return_items[*].title | - | Removed. We no longer provide the ambiguous title field. Instead, we provide product_title and variant_title for you to use separately or in combination |
return_items\[\*\].return_quantity | - | In the new model, we separated the intended_return_quantity (the quantity the shopper submitted in their request) from the return_quantity (the actual quantity that needs to be returned). For scenarios such as green return, keep item, or remove item, the return_quantity may be less than the intended_return_quantity. Please choose the appropriate field based on your use case. |
return_items[*].price | - | Removed. The meaning of this field was ambiguous in the legacy version—it could represent tax-included or tax-excluded prices, discounted or non-discounted amounts, and even different currencies depending on the e-commerce platform. Please refer to the 2025-07 version schema to find the appropriate amount field as a replacement. |
return_items\[\*\].image_url | return_items\[\*\].product_image_urls\[\*\] | Changed from a single URL to an array of URLs |
return_items[*].merchant_tags[*] | return_items[*].item_tags[*] | Path changed |
return_items[*].return_images[*].id | - | Removed. This id is typically not useful. |
return_items\[\*\].return_images\[\*\].url | return_items\[\*\].shopper_uploaded_image_urls\[\*\] | Structure changed from an array of objects to an array of URLs |
return_items[*].return_reason.description | return_items[*].return_reason | Path changed |
return_items[*].return_reason.additional_notes | return_items[*].return_reason_comment | Path changed |
return_items[*].subreason | return_items[*].return_subreason | Path changed |
return_items\[\*\].last_received_at | - | Removed. If you need this value, use the maximum timestamp from the receivings array |
return_items[*].exchange_variant.external_id | return_items[*].exchange_variant.external_variant_id | Path changed |
return_items\[\*\].bundled_items\[\*\].return_quantity | - | In the new model, we separated the intended_return_quantity (the quantity the shopper submitted in their request) from return_quantity (the actual quantity that needs to be returned). For scenarios such as green return, keep item, or remove item, the return_quantity may be less than the intended_return_quantity. Please choose the appropriate field based on your use case. |
return_items\[\*\].bundled_items\[\*\].last_received_at | - | Removed. If you need this value, use the maximum timestamp from the receivings array. |
return_items\[\*\].bundled_items\[\*\].parent_bundle_external_id | - | Removed. If you need this value, use the parent item's external_order_item_id. |
return_method.cost_of_return.amount | cost_of_return.value.amount | Path changed. Type changed from number to string |
return_method.cost_of_return.currency | cost_of_return.value.currency | Path changed |
return_method.label.url | shipments[*].label.url | Path changed. |
return_method.label.total_charge.amount | shipments[*].label.total_charge.amount | Path changed. Type changed from number to string |
return_method.label.total_charge.currency | shipments[*].label.total_charge.currency | Path changed |
return_method.label.aftership_tracking_slug | shipments[*].slug | Path changed |
return_method.label.postmen_courier_slug | shipments[*].label.aftership_shipping_slug | Path changed |
resolution.type | - | Removed. We have refactored resolution-related content in recent years because refunds and charges can also occur when a resolution is exchange. We recommend using return.outcomes as a replacement. |
resolution.exchange_notes | - | Removed. |
refunded_total.amount | refunded_total.amount | Type changed from number to string |
refund_store_credit_reference | refunds[*].store_credit_reference | Path changed |
shipments[*].items[*].external_id | shipments[*].items[*].external_order_item_id | Path changed |
shipments[*].label.total_charge.amount | shipments[*].label.total_charge.amount | Type changed from number to string |
shipments[*].label.aftership_tracking_slug | shipments[*].label.slug | Path changed |
shipments[*].label.postmen_courier_slug | shipments[*].label.aftership_shipping_slug | Path changed |
dropoffs[*].slug | dropoffs[*].service_provider | Path changed |
dropoffs[*].qr_code | dropoffs[*].qr_code_url | Path changed |
dropoffs[*].items[*].external_id | dropoffs[*].items[*].external_order_item_id | Path changed |
dropoffs[*].shipments[*].items[*].external_id | dropoffs[*].shipments[*].items[*].external_order_item_id | Path changed |
dropoffs[*].shipments[*].courier_slug | dropoffs[*].shipments[*].service_provider_carrier_code | Path changed |
4. What if I don't migrate?
After June 30, 2026, any requests made to the legacy version will result in a 404 Not Found error. Therefore, we encourage you to complete the migration at your earliest convenience.