← Back to KPIs

Return to Origin Rate

Also known as: RTO Rate, Return to Sender Rate, Undelivered Return Rate

OperationskpiE-commerce: Post-Purchase Logistics & FulfillmentE-commerce

Share of shipments dispatched in the period that the carrier sent back to the seller undelivered, after failed attempts or refusal. Each RTO costs forward and reverse freight with no sale.

Business Use Case

1️⃣ Logistics team: one carrier's RTO above target in a region → move that postcode range to the better-performing carrier. 2️⃣ Finance: COD RTO rising → require partial prepayment or turn off COD for high-risk postcodes. 3️⃣ Customer Service: refusal-driven RTO up → add a pre-dispatch confirmation message for COD orders. 4️⃣ Operations: address-related RTO up → enforce address validation at add_shipping_info before dispatch.

Business Criticality

High

Journey / Navigation Area

Generic

Owning Function

Operations

Scope

Global

Formula

(Shipments Returned to Origin / Shipments Dispatched) × 100

Calculation Notes

1️⃣ Numerator: shipments whose carrier status reaches RTO initiated; count each once, at the first RTO scan, even if RTO delivered follows. 2️⃣ Denominator: every shipment with a carrier pickup scan in the dispatch cohort, delivered or not. 3️⃣ Cohort by dispatch date and report after a 21-day maturity window, so shipments still in transit do not understate the rate. 4️⃣ Exclude customer returns after delivery (Return Initiated) and orders cancelled before dispatch; neither is RTO. 5️⃣ Count at shipment (tracking number) level: an order with one of two parcels sent back is one RTO out of two shipments. 6️⃣ Failed attempts that end in delivery are not RTO (see Failed Delivery Rate); shipments lost or damaged in transit are excluded. 7️⃣ Report cash-on-delivery and prepaid orders separately; refusals and RTO concentrate in COD.

Technical Details

Measure TypePercentage
Aggregation WindowWeekly, Monthly

Events

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Dispatch cohort ends 21 days ago so every shipment in it has matured
DECLARE end_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 21 DAY);
DECLARE start_date DATE DEFAULT DATE_SUB(end_date, INTERVAL 90 DAY);

-- One row per shipment (tracking number): first pickup scan, first RTO scan, lost/damaged flag
WITH shipments AS (
  SELECT
    tracking_number,
    ANY_VALUE(payment_method) AS payment_method,
    MIN(IF(scan_status = 'picked_up', scan_timestamp, NULL)) AS dispatch_ts,
    MIN(IF(scan_status = 'rto_initiated', scan_timestamp, NULL)) AS first_rto_ts,
    LOGICAL_OR(scan_status IN ('lost', 'damaged')) AS is_lost_or_damaged
  FROM `your-project.erp.shipment_scans`
  WHERE scan_timestamp >= TIMESTAMP(start_date)
  GROUP BY tracking_number
)

-- RTO shipments over all dispatched shipments, COD and prepaid reported separately
-- (a pickup scan is required, so orders cancelled before dispatch never enter; customer returns after delivery carry no RTO scan)
SELECT
  IF(payment_method = 'cod', 'COD', 'Prepaid') AS payment_group,
  COUNT(*) AS shipments_dispatched,
  COUNTIF(first_rto_ts IS NOT NULL) AS shipments_returned_to_origin,
  ROUND(SAFE_DIVIDE(COUNTIF(first_rto_ts IS NOT NULL), COUNT(*)) * 100, 2) AS rto_rate_pct
FROM shipments
WHERE DATE(dispatch_ts) BETWEEN start_date AND end_date
  AND NOT is_lost_or_damaged
GROUP BY payment_group
ORDER BY payment_group;
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Dispatch cohort ends 21 days ago so every shipment in it has matured
WITH params AS (
  SELECT
    date_sub(current_date(), 21) AS end_date,
    date_sub(current_date(), 111) AS start_date
),

-- One row per shipment (tracking number): first pickup scan, first RTO scan, lost/damaged flag
shipments AS (
  SELECT
    s.tracking_number,
    MAX(s.payment_method) AS payment_method,
    MIN(CASE WHEN s.scan_status = 'picked_up' THEN s.scan_timestamp END) AS dispatch_ts,
    MIN(CASE WHEN s.scan_status = 'rto_initiated' THEN s.scan_timestamp END) AS first_rto_ts,
    MAX(CASE WHEN s.scan_status IN ('lost', 'damaged') THEN 1 ELSE 0 END) AS is_lost_or_damaged
  FROM aep_backend_dataset.shipment_scans s
  CROSS JOIN params p
  WHERE s.scan_timestamp >= CAST(p.start_date AS TIMESTAMP)
  GROUP BY s.tracking_number
)

-- RTO shipments over all dispatched shipments, COD and prepaid reported separately
-- (a pickup scan is required, so orders cancelled before dispatch never enter; customer returns after delivery carry no RTO scan)
SELECT
  CASE WHEN sh.payment_method = 'cod' THEN 'COD' ELSE 'Prepaid' END AS payment_group,
  COUNT(*) AS shipments_dispatched,
  SUM(CASE WHEN sh.first_rto_ts IS NOT NULL THEN 1 ELSE 0 END) AS shipments_returned_to_origin,
  ROUND(100.0 * SUM(CASE WHEN sh.first_rto_ts IS NOT NULL THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0), 2) AS rto_rate_pct
FROM shipments sh
CROSS JOIN params p
WHERE CAST(sh.dispatch_ts AS DATE) BETWEEN p.start_date AND p.end_date
  AND sh.is_lost_or_damaged = 0
GROUP BY CASE WHEN sh.payment_method = 'cod' THEN 'COD' ELSE 'Prepaid' END
ORDER BY payment_group;

Source Data

ERP

Dependencies

Prerequisite: List the dependencies required for this KPI

Metrics:

shipment-volume

Dimensions:

carriershipment-statuspayment-methoddelivery-region

Report Attributes

carrier delivery-region payment-method shipment-mode delivery-exception-type

Recommended Reporting Views

Views
Last-Mile Delivery PerformanceCarrier ScorecardCOD Risk & Logistics Cost

Segment Eligibility

true

Governance

Data SensitivityInternal

Governance

Created bydevyendarm
Created on9/27/2026
Last modified bydevyendarm
Last modified on9/27/2026
Version1.0.0
Validation stateqa passed
StatusPUBLISHED

GitHub

View related Pull Requestcatalog/kpis/return-to-origin-rate.yml

Community Discussion