medusa-shiprocket-fulfillment-sbl
v0.0.30
Published
Shiprocket Fulfillment Provider Plugin for MedusaJS 2
Maintainers
Readme
medusa-shiprocket-fulfillment-sbl
Medusa v2 Shiprocket fulfillment provider plugin with shipping rate calculation, shipment creation, serviceability API, webhook automation, and optional tracking-event persistence.
Plugin Overview
medusa-shiprocket-fulfillment-sbl integrates Shiprocket logistics into Medusa:
- Registers a fulfillment provider (
identifier = shiprocket) for rates and fulfillment lifecycle. - Calculates rates from pickup/delivery pincodes and order item weights.
- Creates Shiprocket orders, assigns AWB, and attaches label/manifest/invoice URLs.
- Exposes storefront serviceability endpoint.
- Exposes carrier tracking webhook endpoint to auto-handle shipped/delivered updates.
- Adds admin utilities for shipment documents and tracking events.
- Includes optional
shipment_trackingmodule for append-only tracking scan history with deduplication. - Includes a scheduled token-refresh job.
Built for Medusa v2 (package uses Medusa framework/module APIs, workflows/core-flows integration, and admin-sdk widget extension).
Installation & Setup
1) Install package
npm install medusa-shiprocket-fulfillment-sbl
# or
yarn add medusa-shiprocket-fulfillment-sbl2) Register fulfillment provider and plugin
Recommended (single credential source): set email / password only on the fulfillment provider; the plugin reads them automatically.
import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig({
modules: [
{
resolve: "@medusajs/medusa/fulfillment",
options: {
providers: [
{
resolve: "medusa-shiprocket-fulfillment-sbl",
id: "shiprocket",
options: {
email: "[email protected]",
password: "your_password",
pickup_location: "Primary",
pickup_pincode: "110001", // optional explicit pickup override
cod: "false",
apiTimeoutMs: 30000,
},
},
],
},
},
],
plugins: [
{
resolve: "medusa-shiprocket-fulfillment-sbl",
options: {
// shiprocket.email/password optional when set on fulfillment provider above
webhookSecret: "change-me",
apiTimeoutMs: 30000,
},
},
],
})Before (duplicated credentials — avoid):
plugins: [
{
resolve: "medusa-shiprocket-fulfillment-sbl",
options: {
shiprocket: { email: "...", password: "..." }, // duplicated
webhookSecret: "...",
},
},
],
modules: [/* same email/password repeated in provider options */]3) Optional module registration (shipment tracking events)
{
resolve: "medusa-shiprocket-fulfillment-sbl/modules/shipment-tracking"
}4) Run migrations
npx medusa db:migrateRequired migrations from this plugin:
shipment_tracking_event(only if optional tracking module is registered)
Configuration (config.ts / plugin options)
This plugin resolves options via src/utils/resolve-options.ts and type definitions in src/types/plugin-options.ts.
Plugin options (plugins[].options)
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| shiprocket.email | string | No* | — | Shiprocket account email. Required on plugin or fulfillment provider. |
| shiprocket.password | string | No | — | Shiprocket account password. Required on plugin or fulfillment provider. |
| shiprocket.extra_estimated_delivery_days | number \| string | No | resolved to 1 if all sources unset | Extra days added to summarized multi-courier serviceability estimated_delivery_days. |
| shiprocket.estimated_delivery_days | number \| string | No | same as above | Alias for extra-days buffer. |
| extra_estimated_delivery_days | number \| string | No | same as above | Top-level alias for same extra-days buffer. |
| webhookSecret | string | No | — | Secret used by /carrier-tracking/webhook. Required if webhook endpoint should be functional. |
| publishableKey | string | No | — | Optional config field available to webhook handler. |
| apiTimeoutMs | number | No | 30000 | HTTP timeout for Shiprocket API requests (milliseconds). |
Fulfillment provider options (modules -> providers[].options)
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| email | string | Yes* | — | Shiprocket email. Can be omitted on plugin when set here. |
| password | string | Yes | — | Shiprocket password. *Can be omitted on plugin when set here. |
| pickup_location | string | No | provider-side default usage | Pickup location nickname used in order creation / serviceability fallback. |
| pickup_pincode | string | No | — | Explicit pickup postal code override when inventory/stock location cannot be resolved. |
| cod | 0 \| 1 \| "true" \| "false" | No | prepaid-like behavior | Influences provider rate calculation COD flag. |
| apiTimeoutMs | number | No | 30000 | HTTP timeout for Shiprocket API requests (milliseconds). |
| extra_estimated_delivery_days | number \| string | No | fallback source | Considered as fallback source for extra-days merge if plugin option is absent/non-positive. |
| estimated_delivery_days | number \| string | No | fallback source | Alias fallback source for extra-days merge. |
Extra delivery-days merge behavior
Resolved in resolve-options.ts:
- Plugin-level extra days take precedence when parsed value > 0.
- Otherwise provider-level fallback is used.
- Final
resolveShiprocketOptionsdefault is1when not specified (undefined/empty). - Explicit
0is preserved when provided directly toresolveShiprocketOptions.
Complete example config
{
modules: [
{
resolve: "@medusajs/medusa/fulfillment",
options: {
providers: [
{
resolve: "medusa-shiprocket-fulfillment-sbl",
id: "shiprocket",
options: {
email: "[email protected]",
password: "secret",
pickup_location: "Primary",
cod: 0,
estimated_delivery_days: 1,
},
},
],
},
},
{
resolve: "medusa-shiprocket-fulfillment-sbl/modules/shipment-tracking",
},
],
plugins: [
{
resolve: "medusa-shiprocket-fulfillment-sbl",
options: {
shiprocket: {
email: "[email protected]",
password: "secret",
extra_estimated_delivery_days: 2,
},
webhookSecret: "shiprocket-webhook-secret",
publishableKey: "pk_...",
},
},
],
}Environment Variables
No runtime process.env.* reads were found in this plugin’s src code.
| Variable | Used directly in src runtime code? | Notes |
|---|---|---|
| None | No | Values are expected to be passed through Medusa config options. |
REST APIs / Routes
Store routes
GET /store/shiprocket/serviceability
- Auth requirement: Store route context (typically publishable key based on host app middleware)
- Query parameters:
| Param | Type | Required | Description |
|---|---|---|---|
| pincode | string | Yes | Delivery pincode. |
| variant_id | string | Yes | Variant id with required shipping dimensions. |
| pickup_pincode | string | No | Override pickup pincode; skips pickup resolution from inventory/stock location. |
| cod | string/int | No | 0 or 1, default 0. |
| declared_value | integer-like string | No | Non-negative integer. |
| order_id | integer-like string | No | Positive integer (Shiprocket order id). |
| express_delivery | boolean-like string | No | true, 1, or yes to include a nested express summary alongside standard fields. Default: single summary object. |
Core behavior:
- Loads variant dimensions (
weight,length,width,height) via query graph. - Weight converted from grams to kg for Shiprocket request.
- Resolves pickup from variant inventory location levels and stock location address postal codes when
pickup_pincodeabsent. - Calls Shiprocket serviceability API through
ShiprocketClient.
- Loads variant dimensions (
Response behavior:
- Default (
express_deliveryabsent or false): single summary object (HTTP 200), never the raw Shiprocket payload. express_delivery=true: nested object — top-level fields are the standard tier (surface couriers and couriers with missingis_surface, max rate/days);expressis the air tier (explicitis_surface: false, fastest/min-days) ornullwhen no air couriers exist.- Breaking change: storefronts that parsed the full Shiprocket JSON for zero/one courier results must migrate to this schema.
- Default (
| Field | Description |
|---|---|
| rate | Courier rate as a string. Single courier: that row's rate. Multiple couriers: maximum rate. No couriers: "0". |
| estimated_delivery_days | Delivery days as a string (before ETD formatting). Single courier: that row's days. Multiple couriers: maximum days. No couriers: "0". Includes configured extra buffer (extra_estimated_delivery_days, default +1 day). |
| etd | Estimated delivery date, formatted like Shiprocket (e.g. "June 28, 2026"). Computed from today in Asia/Kolkata plus estimated_delivery_days. For zero couriers, this is today's date in Asia/Kolkata. |
| cod_available | Whether COD is available for this route. Derived from Shiprocket response/courier COD fields in the same API call. Fallback: true when the request used cod=1 and at least one courier is returned. |
When express_delivery=true, nested express uses the same field names:
| Field | Description |
|---|---|
| express | null when no air couriers; otherwise { rate, estimated_delivery_days, etd, cod_available } for the fastest air option. |
| express.rate | Rate from the fastest air courier (minimum delivery days; tie → lowest rate). |
| express.estimated_delivery_days | Delivery days for the fastest air courier, plus extra buffer. |
| express.etd | ETD computed from express.estimated_delivery_days. |
| express.cod_available | COD availability inferred from air couriers only. |
Example (default — serviceable):
{
"rate": "89",
"estimated_delivery_days": "6",
"etd": "June 28, 2026",
"cod_available": true
}Example (express_delivery=true):
{
"rate": "89",
"estimated_delivery_days": "6",
"etd": "June 28, 2026",
"cod_available": true,
"express": {
"rate": "120",
"estimated_delivery_days": "2",
"etd": "June 24, 2026",
"cod_available": true
}
}Example (express_delivery=true, no air couriers):
{
"rate": "89",
"estimated_delivery_days": "6",
"etd": "June 28, 2026",
"cod_available": true,
"express": null
}Example (not serviceable — zero couriers):
{
"rate": "0",
"estimated_delivery_days": "0",
"etd": "June 22, 2026",
"cod_available": false
}GET /store/plugin
- Health route, returns HTTP
200.
Carrier webhook route
POST /carrier-tracking/webhook
Auth requirement: shared secret validation against configured
webhookSecretAccepted secret sources:
Authorization: Bearer <secret>x-shiprocket-secret: <secret>x-api-key: <secret>- query
?secret=or?token=
Body validation:
- Requires at least one of:
order_id,shipment_id,awb,channel_order_id - Reads status from
status/shipment_status/current_status - Accepts passthrough payload fields (
.passthrough())
- Requires at least one of:
Workflow behavior:
- Resolves Medusa order/fulfillment using lookup utility (
awbprioritized). - Normalizes status (
shipped/deliveredcoarse states). - For shipped: ensures shipment exists (
createShipmentWorkflow) if needed. - For delivered: ensures shipment exists then marks delivered (
markOrderFulfillmentAsDeliveredWorkflow). - Persists webhook metadata into fulfillment metadata.
- If optional tracking module registered, ingests scan history and emits
shipment.tracking.updatedon latest milestone change.
- Resolves Medusa order/fulfillment using lookup utility (
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
| shiprocket_only | string | true | false allows broader fulfillment matching beyond Shiprocket provider id. |
Admin routes
POST /admin/orders/:id/fulfillments/:fulfillment_id/shiprocket-documents
- Auth requirement: Admin JWT (admin namespace route)
- Body: none
- Behavior:
- Resolves order and fulfillment via remote query.
- Ensures fulfillment belongs to Shiprocket provider and has
shipment_id. - Generates label via Shiprocket API.
- Success response:
{ label_url: string, manifest_url: string, invoice_url: string }— empty string for any document that failed to generate
- Persists non-empty URLs to
fulfillment.dataviaupdateFulfillment - Possible errors:
400,404,502depending on missing data/provider mismatch/generation failure.
GET /admin/orders/:id/shipment-tracking-events
- Auth requirement: Admin JWT
- Behavior:
- Validates order exists.
- If
shipment_trackingmodule registered: returns sorted raw event rows. - If not registered: returns
501with registration guidance.
GET /admin/plugin
- Health route, returns HTTP
200.
Important API examples
# Store serviceability
curl "http://localhost:9000/store/shiprocket/serviceability?pincode=110001&variant_id=variant_123" \
-H "x-publishable-api-key: <PUBLISHABLE_KEY>"# Shiprocket webhook
curl -X POST "http://localhost:9000/carrier-tracking/webhook" \
-H "Content-Type: application/json" \
-H "x-api-key: <WEBHOOK_SECRET>" \
-d '{
"order_id": "12345",
"shipment_id": "67890",
"status": "delivered",
"awb": "AWB123456789"
}'# Admin generate label
curl -X POST "http://localhost:9000/admin/orders/order_123/fulfillments/ful_123/shiprocket-documents" \
-H "Authorization: Bearer <ADMIN_JWT>"# Admin shipment tracking events
curl "http://localhost:9000/admin/orders/order_123/shipment-tracking-events" \
-H "Authorization: Bearer <ADMIN_JWT>"Services
ShipRocketFulfillmentProviderService
Provider class extending AbstractFulfillmentProviderService, identifier = shiprocket.
Key methods:
getFulfillmentOptions()- Returns
Standard Shipping,Express Shipping, andReturn Shipping.
- Returns
canCalculate(...)- Always true.
calculatePrice(...)- Computes package weight from context items, reads
delivery_tierfrom shipping option provider data (standard|express), and calls Shiprocket serviceability with unifiedselectCourierForTier()(standard = min rate among surface couriers; express = fastest air courier).
- Computes package weight from context items, reads
createFulfillment(...)- Resolves payment method, selects courier by
delivery_tier, creates Shiprocket order, assigns AWB with explicitcourier_id, generates documents.
- Resolves payment method, selects courier by
cancelFulfillment(data)- Cancels Shiprocket order by
order_id.
- Cancels Shiprocket order by
createReturnFulfillment(fulfillment)- Creates return order flow.
getFulfillmentDocuments(data)- Generates invoice.
getShipmentDocuments(data)- Generates label.
validateFulfillmentData(...)- Stores
delivery_tier(standard|express) and namespacedexternal_id(pending:fulfillment:<id>).
- Stores
validateOption(data)- Checks
external_idpresence.
- Checks
checkServiceability(...)- Wrapper around client serviceability call.
ShiprocketClient
Shiprocket API wrapper with token management and request methods:
calculate(data)— usesselectCourierForTier()(not cheapest-of-all)checkServiceability(data)create(fulfillment, items, order, paymentMethod)cancel(orderId)getTrackingInfo(trackingNumber)getPickupLocations()getPickupPincodeByName(locationName)getPickupLocationNames()createReturn(fulfillment)createDocuments(fulfillment)generateLabel({ shipment_id, order_id? })generateInvoice(fulfillment)dispose()
Includes axios interceptor for automatic token refresh on 401.
ShipmentTrackingModuleService (optional module)
Provides normalized tracking ingestion and timeline retrieval:
ingestShiprocketPayload(...)getLatestNormalizedForFulfillment(fulfillmentId)- Computes latest milestone.
deriveCoarseWorkflowFromNormalized(...)getTimelineForOrder(orderId, { audience })listRawEventsForOrder(orderId)maxNormalizedFromPayload(payload)
Utility services/helpers
resolveShiprocketPaymentMethod(...)- Determines
PrepaidvsCODusing provider data, order metadata, and payment graph fallback. - Logs
shiprocket.payment_method.fallback_prepaidwhen defaulting to Prepaid (check provider logs).
- Determines
resolveShiprocketPaymentMethodWithSource(...)- Same resolver; returns
{ method, source }for observability.
- Same resolver; returns
findOrderAndFulfillmentByShiprocketIds(...)- Robust fulfillment resolution from webhook identifiers.
shipment-tracking-normalize.ts- Status normalization, dedupe hashing, customer visibility filtering.
Workflows & Steps (Medusa v2)
No custom createWorkflow / createStep workflows are defined in this plugin source.
⚠️ Note: The webhook uses Medusa core flows (
createShipmentWorkflow,markOrderFulfillmentAsDeliveredWorkflow) rather than custom plugin workflows.
Subscribers / Event Hooks
order-placed-shiprocket-cod-metadata
- Event:
OrderWorkflowEvents.PLACED - Behavior:
- Detects COD from order payments via query graph.
- Persists order metadata hints:
is_cod: trueshiprocket_payment_method: "COD"
- Fails silently to avoid checkout/order-placement disruption.
Jobs
refresh-shiprocket-token
- Schedule:
0 0 */8 * * - Calls
ShiprocketClient.refreshToken()on the shared client to force a fresh login. - File:
src/jobs/refresh-shiprocket-token.ts
CI / branch protection
Coverage thresholds apply to src/utils/** and src/providers/shiprocket/** (excluding the large HTTP client implementation file, which is covered by dedicated client unit tests). CI runs npm test -- --coverage.
Enable Require status checks for Test medusa-shiprocket-fulfillment-sbl on your default branch.
Admin UI / Widgets
ShiprocketTrackingWidget
- Placement:
order.details.side.after - File:
src/admin/widgets/printables.tsx - Displays:
- active Shiprocket fulfillments for order
- shipment id / AWB / status
- label/manifest/invoice actions
- Interactions:
- “Download Label”:
- uses existing label URL, or
- calls admin document-generation route if missing
- opens manifest/invoice links when available
- “Download Label”:
Models & Entities
Optional model: shipment_tracking_event
Defined in src/modules/shipment-tracking/models/shipment-tracking-event.ts.
| Field | Type | Nullable |
|---|---|---|
| id | id | No |
| fulfillment_id | text | No |
| order_id | text | No |
| awb | text | Yes |
| courier_name | text | Yes |
| activity | text | No |
| normalized_status | text | No |
| location | text | Yes |
| event_time | text | No |
| raw_payload | text | Yes |
| provider | text | No (default shiprocket) |
| dedupe_hash | text | No |
Indexes/constraints:
- unique partial index on
dedupe_hash(deleted_at IS NULL) - index on
order_id - index on
fulfillment_id
Relationships:
- Stores
order_idandfulfillment_idreferences (no explicit FK constraints to Medusa core tables in migration).
Use Cases & Examples
Shiprocket calculated shipping at checkout
- Use provider in fulfillment module;
calculatePricereturns live shipping amount from Shiprocket serviceability.
- Use provider in fulfillment module;
Product-page pincode serviceability checks
- Call
GET /store/shiprocket/serviceabilitywithvariant_idand destination pincode for ETD/rate preview.
- Call
Automated shipment state sync from carrier events
- Configure Shiprocket webhook to
POST /carrier-tracking/webhookto auto-mark shipped/delivered.
- Configure Shiprocket webhook to
Admin label recovery for failed/missing document generation
- Use
POST /admin/orders/:id/fulfillments/:fulfillment_id/shiprocket-documentsto generate label URL on demand.
- Use
Carrier scan-history timeline storage and retrieval
- Register optional
shipment_trackingmodule and queryGET /admin/orders/:id/shipment-tracking-events.
- Register optional
Express delivery setup
Express uses the same Shiprocket provider with two shipping options (standard + express). Checkout price and fulfillment courier selection both use selectCourierForTier() — no silent fallback to standard when express is unavailable.
Where to configure (Medusa Admin)
Go to Settings → Locations & Shipping (or your stock location) → Create Shipping Option for the Shiprocket fulfillment provider.
Create two shipping options using the form fields below. Medusa Admin does not expose a “provider data” JSON editor on this screen — that is expected.
Standard shipping option
| Admin field | Value |
|---|---|
| Price type | Calculated (required — Fixed bypasses Shiprocket rate logic) |
| Name | e.g. Standard Shipping |
| Fulfillment provider | shiprocket (your plugin provider id) |
| Fulfillment option | Standard Shipping |
| Enable in store | On |
Express shipping option
| Admin field | Value |
|---|---|
| Price type | Calculated |
| Name | e.g. Express Shipping |
| Fulfillment provider | shiprocket |
| Fulfillment option | Express Shipping |
| Enable in store | On |
Also ensure pickup_location in plugin options matches a Shiprocket pickup with a valid pincode (used for courier selection at fulfill time).
Do you need { "delivery_tier": "express" } JSON?
Usually no. Selecting Fulfillment option → Express Shipping is enough.
When you pick a fulfillment option in Admin, Medusa stores that option on the shipping option’s data field (from getFulfillmentOptions()), typically:
{ "id": "Express Shipping", "name": "Express Shipping", "is_return": false }The plugin resolves tier from that automatically (id / name === "Express Shipping") or from explicit delivery_tier: "express".
| What | Do you set it manually? |
|---|---|
| Shipping option — pick Fulfillment option Express Shipping | Yes, in Admin (required) |
| Shipping option data — { "delivery_tier": "express" } | Optional (fallback if tier resolution fails) |
| Fulfillment data — delivery_tier, requested_courier_id, etc. | No — plugin writes these at checkout / fulfill |
You never paste JSON into fulfillment data in Admin.
Optional: explicit delivery_tier on the shipping option
Add this only if express orders still behave as standard (wrong rate/courier, or fulfillment.data.delivery_tier is not "express" after fulfill). Medusa Admin often has no JSON field — use the Admin API after creating the option:
curl -X POST "http://localhost:9000/admin/shipping-options/<EXPRESS_OPTION_ID>" \
-H "Authorization: Bearer <ADMIN_JWT>" \
-H "Content-Type: application/json" \
-d '{ "data": { "delivery_tier": "express" } }'Use the update route/body for your Medusa version if it differs.
Staging verification (express)
- Place a staging order and select Express Shipping at checkout (Calculated price should reflect air-tier rate, not standard).
- Create fulfillment from Medusa Admin.
- Confirm
fulfillment.dataincludes:delivery_tier:"express"requested_delivery_tier:"express"requested_courier_id,actual_courier_company_id,actual_courier_name
- In the Shiprocket panel, confirm an air courier was assigned (not surface-only).
- If
delivery_tieris missing or"standard", add{ "delivery_tier": "express" }to the shipping option via API (see above) and retry.
Behavior
| Tier | Courier filter | Selection rule |
|---|---|---|
| standard | Surface + missing is_surface | Minimum rate |
| express | Air only (is_surface: false) | Minimum delivery days; tie → minimum rate |
- Hard fail: If no couriers match the tier at checkout (
calculatePrice) or fulfillment (createFulfillment), the plugin throwsNOT_FOUND. It never falls back to the other tier. - COD + express: Only air couriers that support COD are considered when payment method is
COD. - Storefront serviceability API (
GET /store/shiprocket/serviceability?express_delivery=true) is for UI preview (ETD, COD flags). Cart/checkout price comes fromcalculatePriceon the Express shipping option.
Fulfillment data fields (after AWB)
| Field | Description |
|---|---|
| delivery_tier | standard or express |
| requested_delivery_tier | Tier used for courier selection |
| requested_courier_id | Courier ID sent to /courier/assign/awb |
| actual_courier_company_id | Courier ID from AWB response |
| actual_courier_name | Courier name from AWB response |
If requested_courier_id ≠ actual_courier_company_id, logs include shiprocket.courier.mismatch with tier, postcodes, weight, and COD flag.
Go-live staging validation
Before production traffic, run one manual reconciliation on staging Shiprocket:
- Place a Medusa order: 1× ₹999 GST-exclusive item (18% tax), ₹50 shipping, full fulfillment.
- Create Shiprocket fulfillment from Medusa Admin.
- Compare Medusa-computed values (unit test golden reference) with the Shiprocket order panel:
| Field | Expected value |
|---|---|
| sub_total | 1179 (Math.round(999 × 1.18)) |
| shipping_charges | 50 |
| order_items[0].selling_price | 1179 |
| order_items[0].tax | 18 (rate percent, not rupee amount) |
| payment_method | Prepaid or COD per order payment |
- COD orders: confirm Shiprocket shows
payment_method: CODand logs do not containshiprocket.payment_method.fallback_prepaid. - Prepaid orders:
payment_method: Prepaidis expected;fallback_prepaidwarn means no explicit COD signal was found (verify payment metadata if order should be COD).
Plugin unit tests encode the ₹999 + 18% GST golden payload in shiprocket-order-amounts.test.ts (buildShiprocketAdhocAmountPayload).
Fulfillment retry (wallet / AWB failures)
When Shiprocket creates a shipment but AWB assignment fails (for example low wallet balance), the plugin does not cancel the Shiprocket order anymore. The order stays active so you can recover without hitting "order is in cancelled state" on retry.
Retry from Medusa Admin (after topping up Shiprocket wallet):
- Open the order and create or retry fulfillment as usual.
- The provider first checks Medusa fulfillment
dataand Shiprocket List Orders for an active shipment tied to the same Medusa reference (display_idor internal order id). - If an active shipment exists without AWB, it calls
assign/awbonly (no duplicatecreate/adhoc). - If an active shipment already has AWB, it returns that tracking data (idempotent).
- If only cancelled Shiprocket rows exist (for example from older plugin versions), it creates a new adhoc order with a suffixed
order_id({baseRef}_{fulfillmentSuffix}), capped at 50 characters.
One active Shiprocket shipment per Medusa order is the target state. Suffixed ids are used only when a new Shiprocket order is required, not on every fulfillment call.
Ops notes:
- Cancelled bare ids (e.g. display id
250only) cannot be revived on Shiprocket; after deploy, retries use lookup + AWB assign or a new suffixed id. - Manual AWB in the Shiprocket panel is only needed for debugging; normal flow is wallet top-up + retry fulfillment in Medusa.
- Fulfillment
datastoresorder_id,shipment_id,channel_order_id,base_ref, and tracking fields for admin widgets and document routes. - Success logs include
resolve_mode:reuse_has_awb,reuse_no_awb, orcreate. If duplicate Shiprocket orders appear, check whether retries loggedresolve_mode: createinstead of reuse — verifychannel_order_idmatches the Medusa order reference and that List Orders search finds the active shipment. - A warn log
shiprocket.createFulfillment.resolve_mode_createindicates a fresh/orders/create/adhoccall (no reusable shipment found in fulfillment data or list search).
Troubleshooting
shiprocket.email is required / shiprocket.password is required
- Cause: plugin options missing or malformed.
- Fix: verify
plugins[].options.shiprocketinmedusa-config.
Serviceability fails with missing pickup postcode resolution
- Cause: no
pickup_pincodequery param and variant inventory location levels lack stock location postal code. - Fix: pass
pickup_pincodeor correct stock-location address + inventory linkage.
Serviceability returns dimension/weight validation error
- Cause: variant missing positive
weight,length,width,height. - Fix: set required variant shipping dimensions in Medusa Admin.
Webhook returns 501 not configured
- Cause:
webhookSecretnot set in plugin options. - Fix: set
plugins[].options.webhookSecret.
Webhook returns 401
- Cause: secret did not match accepted headers/query secret.
- Fix: ensure Shiprocket token/header matches configured
webhookSecret.
Admin tracking events route returns 501
- Cause: optional
shipment_trackingmodule not registered. - Fix: register
medusa-shiprocket-fulfillment-sbl/modules/shipment-trackingand migrate DB.
No fulfillment matched in webhook payload
- Cause: payload ids/AWB not mapping to Medusa fulfillment data.
- Fix: verify Shiprocket webhook fields (
order_id,shipment_id,awb,channel_order_id) and stored fulfillmentdata.
Fulfillment creation fails with missing dimensions
- Cause: provider
createrequires variant dimensions and weight for all fulfillment items. - Fix: update variant shipping fields.
order is in cancelled state on fulfillment retry
- Cause: a prior attempt created a Shiprocket order, AWB failed, and an older plugin version cancelled the order; retry reused the same bare
order_id. - Fix: upgrade to a version with fulfillment retry support, top up wallet, retry fulfillment from admin. For orders stuck before upgrade, use manual AWB on any still-active Shiprocket row or wait for suffixed create after upgrade.
