- Callback Events
- RMA Events
Callback Events
RMA Events
Webhook event sent when return merchandise authorization (RMA) status changes or items are received
RMA Events provide real-time updates on customer returns throughout the return lifecycle - from package tendering through final item processing. Track return shipments, receive notifications when returns arrive, and monitor restocking status.
Returned items may be restocked as available, sent to quality control, or marked as damaged depending on their condition upon receipt.
​Event Payload
{
"callback_url": "https://client.com/api/rmaEvent",
"message_type": "rmaEvent",
"message_id": "0896116f-e54b-4756-9d3e-1b0c4a25d821",
"data": [
{
"id": "vf79fyn5-ec61-7892-ae7b-57a173b68133",
"customer_identifier": "rma123",
"package_id": "1z324897234nferg45",
"return_type": "rma_submitted_by_customer",
"status": "received",
"received_at": "2018-08-06T11:18:46Z",
"notes": "All items received in good condition",
"line_items": [
{
"customer_order_id": "54321",
"customer_sku_id": "shirts872340",
"quantity": 2,
"quantity_received": 2,
"return_reason_code": "tooBig",
"return_inventory_status": "available",
"not_on_original_rma": false
}
]
}
]
}
​Payload Fields
Your registered webhook endpoint URL
Always rmaEvent for this event type
Unique identifier for this event. Use for idempotency checks.
Array of RMA status updates. Each record contains:
​RMA Status Values
tendered
Return package tendered to carrier by customer
inTransit
Return package in transit to warehouse
delivered
Return package delivered to warehouse dock
received
Return items inspected and processed into inventory
undeliverable
Return package undeliverable (wrong address, refused, etc.)
​Return Types
Customer Submitted
rma_submitted_by_customer - Customer initiated return via your system
Client Submitted
rma_submitted_by_client - You initiated return on customer’s behalf
Unidentified
unidentified_return - Package arrived without prior RMA
​Return Reason Codes
| Code | Description | Typical Disposition |
|---|---|---|
tooBig | Item too large | Available for resale |
tooSmall | Item too small | Available for resale |
wrongItem | Wrong item received | Available for resale |
defective | Item defective | Quality control/damaged |
notAsDescribed | Not as described | Quality control |
arrivedLate | Arrived too late | Available for resale |
noLongerNeeded | Customer changed mind | Available for resale |
damagedInShipping | Damaged during shipping | Damaged/disposed |
​Inventory Status After Return
Available
Item in sellable condition - added to available inventory
Quality Control
Item needs inspection before resale decision
Damaged
Item damaged - not available for sale
Disposed
Item disposed due to condition or regulations
​Example Scenarios
​Full Return - All Items Restocked
{
"status": "received",
"line_items": [
{
"customer_order_id": "54321",
"customer_sku_id": "shirts872340",
"quantity": 2,
"quantity_received": 2,
"return_reason_code": "tooBig",
"return_inventory_status": "available",
"not_on_original_rma": false
}
]
}
​Partial Return - Some Items Damaged
{
"status": "received",
"line_items": [
{
"customer_order_id": "54321",
"customer_sku_id": "shirts872340",
"quantity": 2,
"quantity_received": 2,
"return_reason_code": "defective",
"return_inventory_status": "damaged",
"not_on_original_rma": false
}
],
"notes": "Both items have visible stains, cannot be resold"
}
​Unexpected Item in Return
{
"status": "received",
"line_items": [
{
"customer_order_id": "54321",
"customer_sku_id": "shirts872340",
"quantity": 1,
"quantity_received": 1,
"return_reason_code": "wrongItem",
"return_inventory_status": "available",
"not_on_original_rma": false
},
{
"customer_order_id": "54321",
"customer_sku_id": "pants9876",
"quantity": 0,
"quantity_received": 1,
"return_reason_code": "noLongerNeeded",
"return_inventory_status": "quality-control",
"not_on_original_rma": true
}
]
}
​Implementation Example
@app.route('/api/rmaEvent', methods=['POST'])
def handle_rma_event():
try:
payload = request.get_json()
message_id = payload['message_id']
# Check if already processed
if is_message_processed(message_id):
return 'OK', 200
# Process each RMA update
for rma in payload['data']:
rma_id = rma['customer_identifier']
status = rma['status']
tracking = rma.get('package_id')
# Update RMA status
update_rma_status(rma_id, status)
if status == 'tendered':
logger.info(f"RMA {rma_id} tendered to carrier")
elif status == 'inTransit':
logger.info(f"RMA {rma_id} in transit")
notify_customer_return_in_transit(rma_id)
elif status == 'delivered':
logger.info(f"RMA {rma_id} delivered to warehouse")
elif status == 'received':
# Process returned items
line_items = rma.get('line_items', [])
notes = rma.get('notes', '')
for item in line_items:
order_id = item['customer_order_id']
sku = item['customer_sku_id']
expected = item['quantity']
received = item['quantity_received']
reason = item['return_reason_code']
inv_status = item['return_inventory_status']
unexpected = item['not_on_original_rma']
# Update inventory based on status
if inv_status == 'available':
# Add to sellable inventory
restock_inventory(sku, received)
logger.info(f"Restocked {received} units of {sku}")
elif inv_status == 'damaged':
# Mark as damaged, don't add to available
mark_inventory_damaged(sku, received)
logger.warning(f"{received} units of {sku} marked damaged")
elif inv_status == 'quality-control':
# Hold for QC decision
hold_for_quality_control(sku, received)
# Process refund
if inv_status in ['available', 'quality-control']:
process_refund(order_id, sku, received)
# Check for discrepancies
if received < expected:
log_rma_shortage(rma_id, sku, expected - received)
if unexpected:
log_unexpected_return(rma_id, sku, received)
# Notify customer
notify_customer_return_processed(rma_id)
logger.info(f"RMA {rma_id} processing complete")
elif status == 'undeliverable':
logger.error(f"RMA {rma_id} undeliverable")
notify_customer_return_failed(rma_id)
mark_message_processed(message_id)
return 'OK', 200
except Exception as e:
logger.error(f"RMA event webhook error: {e}")
return 'Error', 500
app.post('/api/rmaEvent', async (req, res) => {
try {
const payload = req.body;
const messageId = payload.message_id;
// Check if already processed
if (await isMessageProcessed(messageId)) {
return res.status(200).send('OK');
}
// Process each RMA update
for (const rma of payload.data) {
const rmaId = rma.customer_identifier;
const status = rma.status;
// Update RMA status
await updateRMAStatus(rmaId, status);
if (status === 'tendered') {
console.log(`RMA ${rmaId} tendered to carrier`);
} else if (status === 'inTransit') {
console.log(`RMA ${rmaId} in transit`);
await notifyCustomerReturnInTransit(rmaId);
} else if (status === 'delivered') {
console.log(`RMA ${rmaId} delivered to warehouse`);
} else if (status === 'received') {
// Process returned items
const lineItems = rma.line_items || [];
for (const item of lineItems) {
const orderId = item.customer_order_id;
const sku = item.customer_sku_id;
const received = item.quantity_received;
const invStatus = item.return_inventory_status;
// Update inventory based on status
if (invStatus === 'available') {
await restockInventory(sku, received);
console.log(`Restocked ${received} units of ${sku}`);
} else if (invStatus === 'damaged') {
await markInventoryDamaged(sku, received);
console.warn(`${received} units of ${sku} marked damaged`);
} else if (invStatus === 'quality-control') {
await holdForQualityControl(sku, received);
}
// Process refund
if (['available', 'quality-control'].includes(invStatus)) {
await processRefund(orderId, sku, received);
}
// Check for unexpected items
if (item.not_on_original_rma) {
await logUnexpectedReturn(rmaId, sku, received);
}
}
// Notify customer
await notifyCustomerReturnProcessed(rmaId);
console.log(`RMA ${rmaId} processing complete`);
} else if (status === 'undeliverable') {
console.error(`RMA ${rmaId} undeliverable`);
await notifyCustomerReturnFailed(rmaId);
}
}
await markMessageProcessed(messageId);
res.status(200).send('OK');
} catch (error) {
console.error('RMA event webhook error:', error);
res.status(500).send('Error');
}
});
​Use Cases
Refund Processing
Trigger refunds only after items are received and inspected
Inventory Management
Automatically restock sellable returns to available inventory
Customer Communication
Keep customers informed of return status and refund timeline
Quality Tracking
Monitor return reasons and damage rates to identify product issues
​Best Practices
Conditional Refunds
Only process refunds after items are received and pass inspection
Inventory Accuracy
Update inventory based on actual return_inventory_status, not just receipt
Customer Updates
Proactively notify customers at each status milestone
Return Analytics
Track return reasons to identify product or description issues
Never process refunds before receiving the received status. Items may be undeliverable, damaged, or not match the RMA.
​Refund Decision Matrix
| Inventory Status | Refund Policy | Typical Action |
|---|---|---|
available | Full refund | Process immediately |
quality-control | Pending inspection | Hold refund until QC complete |
damaged | Partial/no refund | Customer damaged - may deny refund |
disposed | No refund | Item unsellable due to customer |
​Response Requirements
Your webhook endpoint must:
- Respond within 30 seconds - Return HTTP 200 to acknowledge receipt
- Handle all statuses - Support all RMA status transitions
- Process conditionally - Only restock/refund based on inventory status
- Track discrepancies - Monitor expected vs received quantities
​Related Events
- Order Events - Original order that’s being returned
- SKU Inventory Change Event - Inventory updates when returns are restocked
- Order Cancel Resolutions - Failed cancellations often lead to RMAs