After 12 years of developer frustration, Shopify finally solved the Carrier Service API’s biggest limitation. On November 4, 2025, they added discount data and customer information to shipping rate callbacks and features developers have requested since 2013.

What Changed? The Game-Changing Fields

Shopify’s Carrier Service API now sends three critical pieces of data that were previously missing:

1. Order Totals (The Discount Solution)

"order_totals": {
  "subtotal_price": 12000,    // $120.00 before discounts
  "total_price": 9600,         // $96.00 after 20% discount
  "total_discounts": 2400      // $24.00 discount applied
}

Why this matters: Before November 2025, carrier services only saw pre-discount prices. A customer with a $120 cart applying a 20% coupon would still trigger shipping calculations based on $120 breaking “free shipping over $100” thresholds and causing merchants to lose money.

2. Customer Data (Segment-Based Rates)

"customer": {
  "id": 5234789456,
  "tags": ["VIP", "Wholesale", "Canada"]
}

The opportunity: You can now offer VIP free shipping, wholesale discount rates, or region-specific pricing based on customer tags which was impossible with the old API.

How to Implement Discount-Aware Shipping (Code Example)

Here’s a practical free shipping threshold that actually works with discounts:

function calculateShipping(payload) {
  const cartTotal = payload.rate.order_totals.total_price; // in cents
  const customerTags = payload.rate.customer?.tags || [];

  // Free shipping for VIP customers over $50
  if (customerTags.includes('VIP') && cartTotal >= 5000) {
    return {
      service_name: "VIP Free Shipping",
      service_code: "VIP_FREE",
      total_price: 0,
      currency: "USD"
    };
  }

  // Standard free shipping over $100 (post-discount)
  if (cartTotal >= 10000) {
    return {
      service_name: "Free Standard Shipping",
      service_code: "FREE",
      total_price: 0,
      currency: "USD"
    };
  }

  // Tiered wholesale rates
  if (customerTags.includes('Wholesale')) {
    return {
      service_name: "Wholesale Shipping",
      service_code: "WHOLESALE",
      total_price: cartTotal >= 20000 ? 500 : 1000, // $5 or $10
      currency: "USD"
    };
  }

  // Standard rate
  return {
    service_name: "Standard Shipping",
    service_code: "STANDARD",
    total_price: 1500, // $15.00
    currency: "USD"
  };
}

Critical Implementation Details

Use total_price, Not Item Prices

Wrong approach:

const total = payload.rate.items.reduce((sum, item) => 
  sum + (item.price * item.quantity), 0
);

Right approach:

const total = payload.rate.order_totals.total_price;

Why? The items array only includes physical products. Digital goods, gift cards, and items with different shipping profiles affect order_totals.subtotal_price but won’t appear in the items array.

Handle Missing Customer Data

The customer object can be null for guest checkouts or due to caching:

const isVIP = payload.rate.customer?.tags?.includes('VIP') ?? false;

Pro tip: If customer data seems missing after login, it’s likely a caching issue. The workaround is updating cart quantity to force a fresh rate request.

Real-World Use Cases Now Possible

1. True Free Shipping Thresholds

“Free shipping over $100” now works correctly with discount codes and no more accidentally giving free shipping on $80 orders.

2. Customer Segment Pricing

  • VIP customers: Free expedited shipping
  • Wholesale accounts: Flat $10 regardless of order size
  • Regional tags: Canada gets free shipping, US pays standard rates

3. Discount-Aware Rate Calculations

Calculate percentage-based shipping (5% of order total) that accounts for coupon codes.

What’s Still Missing?

Despite the breakthrough, limitations remain:

  • No discount code strings: You can’t see that “SAVE20” was applied, only the total discount amount
  • No per-item discount breakdown: Can’t identify which products are discounted
  • Items still show original prices: The price field in items array hasn’t changed to use order_totals instead
  • Caching issues: 15-minute cache duration means discount changes may not immediately update rates

Testing Your Implementation

Test with these scenarios:

  1. Percentage discount codes (20% off entire order)
  2. Fixed amount discounts ($10 off orders over $50)
  3. Automatic discounts (BOGO, volume pricing)
  4. Stacked discounts (product discount + cart discount)
  5. Customer segments (logged-in VIP vs. guest checkout)
  6. Tax-inclusive vs. tax-exclusive stores (impacts total_price)

Performance Requirements

Shopify enforces strict timeout limits based on request volume:

  • Under 1,500 RPM: 10-second timeout
  • 1,500-3,000 RPM: 5-second timeout
  • Above 3,000 RPM: 3-second timeout

No retry mechanism exists and your response must succeed on the first attempt or Shopify shows backup rates.

Migration Strategy

For Existing Carrier Services

  1. Check for field presence to maintain backward compatibility:const total = payload.rate.order_totals?.total_price ?? payload.rate.items.reduce((sum, item) => sum + (item.price * item.quantity), 0 );
  2. Update documentation explaining the improved accuracy
  3. Notify merchants to reconfigure shipping rules if needed

For New Implementations

Start with order_totals and customer fields from day one and they’re available on 100% of Shopify stores as of November 2025.

The Future: Shopify Functions

While this update makes Carrier Service API viable for discount-aware shipping, Shopify’s strategic direction is clear: Functions are the future.

The Discount Function API (Shopify Plus required for custom functions) provides:

  • Full cart context including metafields
  • Real-time calculation without caching issues
  • Access to all discount details, not just totals
  • Better performance and reliability

Bottom line: Use the updated Carrier Service API for current projects, but plan migration to Functions for long-term implementations.

Conclusion

The November 2025 update transforms Shopify’s Carrier Service API from fundamentally broken to functionally viable. Free shipping thresholds work correctly, customer segment pricing is possible, and discount-aware rate calculations finally make sense.

After 12 years, developers can stop building elaborate workarounds and focus on creating sophisticated shipping experiences their merchants actually need.

Ready to implement? Start by updating your carrier service to read order_totals.total_price and customer.tags , your merchants will thank you when their shipping rules start working as intended.


Quick Reference: New API Fields

FieldTypeDescription
order_totals.subtotal_priceinteger (cents)Total before discounts
order_totals.total_priceinteger (cents)Final total after discounts + taxes
order_totals.total_discountsinteger (cents)Total discount amount
customer.idintegerCustomer identifier (null for guests)
customer.tagsarrayCustomer tags like [“VIP”, “Wholesale”]

Availability: All Shopify stores with Carrier Service access (Advanced plan or higher, or with $20/month addon)

Documentation: Shopify Developer Changelog – November 4, 2025