Commerce

How to Add COD + Shiprocket Fulfillment to Medusa v2 for India

Medusa v2 has no built-in COD payment method or Shiprocket fulfillment module. This guide builds both from scratch — the COD payment provider, Shiprocket API integration, RTO handling, and the webhook pipeline that keeps order status in sync.

MManojAugust 4, 202614 min read
#medusa#commerce#fulfillment#india
Share

You have Medusa v2 running with Razorpay payments. Prepaid orders flow through. Then your first customer from Tier 2 India asks: "Do you have cash on delivery?" In India, COD is not a legacy payment method — it is the default. Depending on the city tier and product category, 25–65% of e-commerce orders are COD. Medusa v2 has no built-in COD payment provider and no Shiprocket fulfillment module. The community plugin Hemann55/medusa-fulfillment-shiprocket exists for v1 but was never ported. This guide builds both from scratch: a COD payment provider that creates orders without collecting payment, a Shiprocket fulfillment module that pushes orders to Shiprocket's API, and the webhook pipeline that keeps delivery status and RTO handling in sync.

Need Razorpay for prepaid orders?

This guide covers the COD side. For UPI, cards, and netbanking, see the Razorpay payment provider guide. For self-hosting Medusa on affordable infrastructure, see the Coolify + Hetzner deployment guide.

Cash on delivery creates a fundamentally different order lifecycle. There is no payment at checkout — the customer promises to pay when the package arrives. Your store ships product on trust, eats both forward and return shipping costs when the customer refuses delivery, and waits days for the courier to remit collected cash. Every Indian e-commerce operator knows this, but Medusa's payment architecture assumes payment happens before fulfillment. I am Manoj, commerce and ERP implementation lead at Mith Tech in Bengaluru, and wiring Indian fulfillment logistics into headless commerce stacks is what we do every week.

Why COD cannot be an afterthought

If you have built an Indian e-commerce store and skipped COD, your conversion rate is artificially suppressed. COD is not about customers who lack online payment access — UPI penetration is high. COD is about trust. A customer buying from a brand they have never heard of, on a website they found through Instagram ads, will choose COD to protect themselves from scams and mismatched product quality.

The industry data is consistent: COD accounts for roughly 60–65% of orders in fashion/apparel, 40–50% in electronics, and 25–35% in beauty and personal care. Tier 1 city rates are lower (25–35%) because repeat customers build trust. Tier 2 and Tier 3 rates are higher (45–65%) because brand awareness is lower.

The operational cost is also consistent: RTO rates for COD orders are 25–30% nationally, climbing to 40% in Tier 3 cities. Each RTO costs you both forward and return shipping — typically ₹60–160 per order depending on weight, zone, and courier. The RTO Cost Calculator below lets you model this for your order volume.

The COD order lifecycle — different from prepaid

A prepaid order follows a straightforward path: customer pays → store fulfils → courier delivers. COD inverts this. The store fulfils on trust, the courier collects cash, the courier remits cash to Shiprocket, and Shiprocket remits to your bank account. Your payment is "captured" days after the customer receives the product.

InteractiveCOD Order Flow Simulatorlink

Click through each stage to see what happens on the Medusa side and the Shiprocket side — from checkout through delivery, cash collection, and the RTO path when a customer refuses.

Checkout (COD selected)

Customer selects COD at checkout. No payment is collected. Medusa creates the order with payment_status = 'awaiting'.

Medusa side

completeCartWorkflow runs with manual/system payment provider. Payment session status = 'pending'. Order created with amount_due = total.

Shiprocket side

Not involved yet.

The simulator above shows the key divergence: after "delivered", the payment in Medusa is still awaiting. It moves to captured only after Shiprocket's settlement cycle (T+2 to T+8 depending on the courier). Your reconciliation process needs to account for this gap — you have shipped product but not received payment, and the settlement is not instantaneous.

The cost of RTO

Every COD order that gets refused at the doorstep costs you forward shipping, return shipping, product handling, and opportunity cost (inventory was locked while the order was in transit). The RTO Cost Calculator models this for your business:

CalculatorRTO Cost Calculatorlink

Enter your monthly COD order volume, average order value, and expected RTO rate to see the financial impact of COD returns on your margins.

RTO orders

125

25% of 500

RTO shipping cost

₹12,500

Forward + return

Total shipping cost

₹31,250

10.4% of revenue

Delivered revenue

₹300,000

375 orders

If the calculator shows shipping costs eating more than 8–10% of your revenue, consider these mitigations:

  1. OTP-verified COD. Send an OTP to the customer's phone before confirming a COD order. This reduces fake orders by 30–40%.
  2. COD with advance. Collect ₹50–100 via UPI at checkout, refundable against the COD amount. Filters out low-intent orders.
  3. Pincode-level COD blocking. Track RTO rates by pincode. Disable COD for pincodes with >40% RTO rate.
  4. COD to prepaid conversion. After order confirmation, send a WhatsApp message with a prepaid payment link offering a ₹50 discount. 15–25% of customers convert.

Building the COD + Shiprocket stack

The implementation has four files: the COD payment provider module, the Shiprocket fulfillment module, the tracking webhook route, and the module registration in medusa-config.ts.

Code recipeCOD + Shiprocket Recipelink

Four-tab code recipe covering the COD payment provider, Shiprocket fulfillment service, tracking webhook handler, and medusa-config.ts registration. Copy each file into your Medusa v2 project.

// src/modules/cod/service.ts
import {
  AbstractPaymentProvider,
  PaymentSessionStatus,
} from "@medusajs/framework/utils"

class CodPaymentProvider extends AbstractPaymentProvider {
  static identifier = "cod"

  async initiatePayment(input: {
    amount: number
    currency_code: string
    context: Record<string, unknown>
  }) {
    // COD: no payment is collected at checkout
    // Create a session with status "pending"
    return {
      id: `cod_${Date.now()}`,
      data: {
        method: "cash_on_delivery",
        amount: input.amount,
        currency: input.currency_code,
      },
    }
  }

  async getPaymentStatus(): Promise<PaymentSessionStatus> {
    // COD payments are always "pending" until
    // delivery agent collects cash
    return PaymentSessionStatus.AUTHORIZED
  }

  async authorizePayment() {
    // Auto-authorize — the "payment" is the promise to pay
    return {
      status: PaymentSessionStatus.AUTHORIZED,
      data: { authorized_at: new Date().toISOString() },
    }
  }

  async capturePayment(paymentData: Record<string, unknown>) {
    // Called when COD cash is remitted to your bank
    return {
      ...paymentData,
      captured_at: new Date().toISOString(),
    }
  }

  async refundPayment() {
    // COD refunds are manual (bank transfer to customer)
    return { status: "refunded" }
  }

  async cancelPayment() {
    return { status: "cancelled" }
  }
}

export default CodPaymentProvider

Create the COD payment provider module

Create src/modules/cod/service.ts extending AbstractPaymentProvider. The key difference from a standard payment provider: initiatePayment creates a session without collecting any payment. authorizePayment auto-authorises because COD "authorisation" is the customer promising to pay. capturePayment is called manually (or via a scheduled job) when Shiprocket remits the COD amount.

Create the Shiprocket fulfillment module

Create src/modules/shiprocket/service.ts extending AbstractFulfillmentProviderService. The module authenticates with Shiprocket's API using email/password credentials, creates orders with payment_method: "COD", and returns the AWB number for tracking. Store the awb_code and courier_name on the fulfillment metadata.

Register both modules

Add the COD and Shiprocket modules to medusa-config.ts. The COD module takes no options (it does not connect to any external service). The Shiprocket module takes email, password, and pickup_location from environment variables.

Build the tracking webhook

Create src/api/hooks/shiprocket/route.ts. Shiprocket sends status updates via webhook with status codes: 6 (shipped/out for delivery), 7 (delivered), 8 (cancelled), 9 (RTO initiated), 10 (RTO delivered). Map each code to the corresponding Medusa fulfillment and order state. Note that order.completed never fires reliably in some courier integrations (GitHub #10611) — use the tracking webhook as the source of truth.

Handle the RTO path

When status 9 or 10 arrives, cancel the order in Medusa, restock the inventory, and log the shipping cost. The inventory restock is critical — without it, your available stock decreases permanently for every RTO. Also verify the stock location: Medusa can deduct from the wrong location (GitHub #10658), so always specify location_id explicitly.

Test the full lifecycle

Create a test COD order. Verify it appears in Shiprocket with payment_method: COD. Simulate delivery by calling your webhook endpoint with status 7. Confirm the fulfillment transitions to "delivered" and the payment remains "awaiting" (cash collected but not yet remitted). Then simulate COD remittance by manually capturing the payment. Finally, test the RTO path: webhook with status 10, verify inventory is restocked.

What to know before you commit

Shiprocket API authentication uses email/password. There is no OAuth or API key — you store your Shiprocket account credentials in environment variables. The token expires after 24 hours, so your fulfillment module must handle re-authentication. The code recipe handles this with a lazy token fetch.

Draft orders bypass stock checks in some configurations (GitHub #14106). If you use draft orders for COD (creating the order before the customer confirms), verify that inventory is decremented at order creation, not at fulfillment.

COD amount handling. Shiprocket needs the order total in rupees, not paise. Medusa stores amounts in the smallest currency unit (paise for INR). Divide by 100 when sending to Shiprocket, or your COD collection will be 100× too high.

Courier selection matters. Shiprocket's auto-assign picks the cheapest courier by default. For COD, reliability matters more than cost — a failed delivery attempt on a COD order is a near-certain RTO. Override the auto-selection for high-value COD orders to use premium couriers (Delhivery, Blue Dart) with better first-attempt delivery rates.

+How do I add COD (cash on delivery) to Medusa v2?

Build a custom payment provider module extending AbstractPaymentProvider. The initiatePayment method creates a session without collecting payment. authorizePayment auto-authorises. capturePayment is called when the courier remits the collected cash. Register the module in medusa-config.ts. There is no official COD module for Medusa v2.

+How do I integrate Shiprocket with Medusa v2?

Build a fulfillment provider module extending AbstractFulfillmentProviderService. Authenticate with Shiprocket's API (POST /v1/external/auth/login with email/password), create orders via POST /v1/external/orders/create/adhoc, and handle delivery status via webhook callbacks. The community plugin Hemann55/medusa-fulfillment-shiprocket exists for v1 but was never ported to v2.

+What is the RTO rate for COD orders in India?

The national average RTO rate for COD orders is 25–30%. Tier 1 cities (Mumbai, Delhi, Bengaluru) are lower at 15–20%. Tier 2 cities (Jaipur, Lucknow, Indore) run 25–35%. Tier 3 cities and rural areas reach 35–40%. Fashion and apparel have the highest RTO rates; electronics and essentials have the lowest.

+How long does Shiprocket COD settlement take?

Shiprocket remits COD amounts to your bank account on a T+2 to T+8 cycle depending on the courier partner. Delhivery and Blue Dart typically settle in T+2 to T+3. Ecom Express and DTDC may take T+5 to T+8. You can opt for early COD remittance (Shiprocket charges 1.5–2% for this) to improve cash flow.

+Does Shiprocket charge for RTO shipments?

Yes. You pay both forward and return shipping for RTO orders. Forward shipping is ₹30–80 depending on weight and zone. Return shipping is ₹30–80 (same rate tier). So each RTO costs ₹60–160 in shipping alone, plus packaging and product handling costs. This is why reducing RTO rate is critical for COD profitability.

+How do I reduce COD RTO rates?

Four proven methods: (1) OTP verification before confirming COD orders (reduces fake orders by 30–40%), (2) COD with advance payment of ₹50–100 via UPI (filters low-intent buyers), (3) pincode-level COD blocking for high-RTO areas, (4) post-order prepaid conversion via WhatsApp/SMS payment link with a ₹50 discount (15–25% convert). Implement these in your Medusa order workflow, not in the payment provider.

+Can I use Medusa's built-in fulfillment for Indian shipping?

Medusa's built-in fulfillment provider (manual) only marks items as shipped — it does not connect to any courier API. For India, you need a fulfillment module that integrates with Shiprocket, Delhivery, or a similar logistics aggregator. Shiprocket is the most common choice because it aggregates 17+ courier partners and provides a single API for order creation, tracking, and COD management.

+What Medusa version does this guide apply to?

This guide targets Medusa v2.18.0 (latest stable as of August 2026). The AbstractPaymentProvider and AbstractFulfillmentProviderService interfaces have been stable since v2.0. The COD and Shiprocket module patterns work across v2.x releases. Webhook handling may need adjustment if Medusa changes its API route body parsing.

COD is operationally harder than prepaid — the payment comes after shipping, RTO eats margins, and reconciliation is a weekly chore. But in India, skipping COD means losing more than half your addressable market. The order flow simulator and RTO calculator above show the exact lifecycle and cost structure. If the numbers work for your margins, the code recipes give you a working COD + Shiprocket stack for Medusa v2 in an afternoon.

Wiring COD + Shiprocket into your Medusa store?

We build the full India fulfilment stack for headless commerce — COD, Shiprocket, RTO mitigation, and COD reconciliation. If you are stuck on the Shiprocket webhook pipeline or need to reduce RTO rates, we will scope it honestly.

Free · By email

Get practical ERPNext & automation guides

New implementation guides, cost breakdowns and open-source tips for Indian businesses — occasionally, straight to your inbox. No spam.

M

Written by

Manoj

Founder of Mith Tech, an open-source ERP & automation studio. Hands-on ERPNext/Frappe implementation across multi-branch, multi-warehouse Indian operations — GST/TDS/PT compliance, branch-level permissions, and custom Frappe apps that give management real-time visibility.

Keep reading

See what this looks like for your business

Book a free 30-minute audit. We'll map your workflows, find where time and money leak, and design an open-source stack you actually own — no per-user licence fees.

Book a consultation
0 0
Published on 4 August 2026

Manoj

Comments

No comments yet. Start a new discussion.

Ctrl+Enter to add comment