1. Callback Events
  2. RMA Events

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

callback_url
string

Your registered webhook endpoint URL

message_type
string

Always rmaEvent for this event type

message_id
string

Unique identifier for this event. Use for idempotency checks.

data
array

Array of RMA status updates. Each record contains:

​
RMA Status Values

1

tendered

Return package tendered to carrier by customer

2

inTransit

Return package in transit to warehouse

3

delivered

Return package delivered to warehouse dock

4

received

Return items inspected and processed into inventory

5

undeliverable

Return package undeliverable (wrong address, refused, etc.)

​
Return Types

​
Return Reason Codes

CodeDescriptionTypical Disposition
tooBigItem too largeAvailable for resale
tooSmallItem too smallAvailable for resale
wrongItemWrong item receivedAvailable for resale
defectiveItem defectiveQuality control/damaged
notAsDescribedNot as describedQuality control
arrivedLateArrived too lateAvailable for resale
noLongerNeededCustomer changed mindAvailable for resale
damagedInShippingDamaged during shippingDamaged/disposed

​
Inventory Status After Return

​
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

Python
@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
JavaScript
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

​
Best Practices

Never process refunds before receiving the received status. Items may be undeliverable, damaged, or not match the RMA.

​
Refund Decision Matrix

Inventory StatusRefund PolicyTypical Action
availableFull refundProcess immediately
quality-controlPending inspectionHold refund until QC complete
damagedPartial/no refundCustomer damaged - may deny refund
disposedNo refundItem unsellable due to customer

​
Response Requirements

Your webhook endpoint must:

  1. Respond within 30 seconds - Return HTTP 200 to acknowledge receipt
  2. Handle all statuses - Support all RMA status transitions
  3. Process conditionally - Only restock/refund based on inventory status
  4. Track discrepancies - Monitor expected vs received quantities