Back to blog
Article

Shipping rates at checkout that match the courier's monthly invoice

Shipping rates at checkout that match the courier's monthly invoice
S

StriveBit

4 min readE-commerce

Shipping rates at checkout that match the courier's monthly invoice

A merchant in Surat sells cotton blends — 1.2 kg packed, volumetric weight 2.5 kg. At checkout the customer pays ₹85 for shipping. At month-end the Delhivery invoice says ₹112 for that same consignment. Multiply by 3,000 orders a month and the merchant is bleeding ₹81,000 in absorbed shipping costs.

The gap comes from three places. The rate card at checkout is stale. The weight used for the quote is the product weight, not the packed weight. And the pincode zone mapping is a simplification that collapses six courier zones into three.

We ran into this for a home goods store in Jaipur last quarter. Their Shopify checkout used flat rates — ₹60 for zone A, ₹120 for zone B — and the courier billed on actuals. The reconciliation spreadsheet at month-end had 14 columns and no one owned it.

The fix is to quote at checkout using the same inputs the courier uses to bill. That means three things working together: a current rate card, the packed weight, and the courier's own zone mapping.

**Rate cards.** Most Indian couriers — Delhivery, Bluedart, Xpressbees — publish rate cards as Excel sheets, updated quarterly or when fuel surcharge changes. We store these as JSON in a config table, not hardcoded in the checkout logic. When the courier revises rates, someone updates the JSON. It is a five-minute job and it is the only maintenance this system needs.

{
  "courier": "delhivery",
  "effective_from": "2025-01-01",
  "fuel_surcharge": 0.21,
  "zones": {
    "A": { "base": 38, "per_kg": 12 },
    "B": { "base": 52, "per_kg": 18 },
    "C": { "base": 71, "per_kg": 25 }
  }
}

**Packed weight.** The quote at checkout should use the packed weight, not the catalog weight. A 900 g product in a corrugated box with bubble wrap weighs 1.1 kg and dimensions of 30×22×18 cm give a volumetric weight of 2.4 kg. The courier bills on the greater of actual and volumetric. We compute both at the cart level using the packaging dimensions for each product's default box size, stored as a field on the product record. This is a one-time data entry per SKU, and it holds until the packaging changes.

**Zone mapping.** Couriers maintain pincode-to-zone files. Delhivery's is a CSV with 27,000 rows, updated monthly. We load it into a Postgres table and join on the first six digits of the pincode at checkout. The lookup is a single query, cached in Redis for 24 hours per pincode.

SELECT zone FROM courier_pincode_zones
WHERE courier = 'delhivery'
  AND pincode_prefix = LEFT($1, 6)
  AND valid_from <= CURRENT_DATE
ORDER BY valid_from DESC LIMIT 1;

The checkout flow becomes: cart weight and dimensions from the product records, zone from the pincode lookup, rate from the JSON config. The customer sees a shipping charge that is within ₹2 of what the courier will bill. The remaining ₹2 is rounding and the occasional remote-area surcharge, which we absorb as a cost of doing business rather than chasing the last decimal.

What we do not do is call the courier's rate API at checkout. Delhivery's API takes 400-800 ms to return a quote. On a mobile checkout over a flaky connection, that is a conversion killer. The local lookup with cached rate cards returns in under 10 ms and matches the API 96% of the time in our testing. The 4% mismatch is usually a pincode the courier reclassified between monthly file updates. We accept that drift.

For merchants using multiple couriers, we run the same logic against each courier's rate card and pick the cheapest at checkout. The pincode-zone table holds entries for all couriers, keyed by courier name. The merchant can also pin specific pincodes to a preferred courier — useful when a courier has a better delivery SLA in a region the merchant cares about.

The reconciliation at month-end shrinks to a diff between what was charged and what was billed, grouped by courier and zone. When the diff exceeds 2% for a courier, the rate card JSON needs updating. That is the trigger, not a quarterly calendar reminder.

We shipped this for the Jaipur store in February. Their March invoice showed a ₹1,200 gap across 2,800 orders, down from ₹74,000 the previous March. The remaining gap was a fuel surcharge revision that took effect mid-month, and we updated the JSON the day the invoice arrived.

Back to all articles

Ready to build something great?

We help ambitious teams build software that lasts. If you're interested in working with us or want to discuss your project, let's connect.

Get in touch