Hubbazzar Shipping API
Everything you can do in the Shipping Panel — book shipments with Delhivery, Shadowfax, Amazon Shipping and Nimbus Post, print labels, track parcels, handle NDRs, cancel with automatic refunds, read your wallet and COD remittances, and receive real-time webhooks — over one JSON REST API.
https://hubbazzar.com/api/v1/shippingAuthorization: Bearer <token>600 requests / minuteYour first shipment in 5 steps
- 1. Get a token. Panel → API Settings → generate token (or
POST /auth/login). Export it:export TOKEN=… - 2. Add a pickup warehouse.
POST /warehouses→ note itspick_address_id. (Nimbus Post users: also verify the OTP once.) - 3. Pick a courier.
GET /couriers?express_type=surface&weight=0.5&payment_mode=COD&order_amount=1499→ choose acourier_code. - 4. Create the shipment.
POST /shipmentswith the consignee,pick_address_idandcourier_code→ you get the AWB (waybill) instantly. - 5. Print & track.
GET /shipments/{id}/label→ PDF. Track via your webhook (order.status_updated) orGET /shipments/{id}/track?sync=1.
Authentication
Send a Bearer token on every request (except /auth/login) and ask for JSON:
- Static token — generate in Panel → API Settings. Best for a backend service. Regenerating it invalidates the old one.
- Login token —
POST /auth/login. Revocable with/auth/logout, replaceable with/auth/token/rotate. - Keep tokens server-side. Never ship them in a browser or mobile app.
- Every call only ever sees your own warehouses, shipments, wallet and NDRs.
Integration workflow
How a typical store/ERP uses the API from checkout to settlement:
| Stage | What you do | Endpoint(s) |
|---|---|---|
| Setup (once) | Store pickup addresses; verify Nimbus Post OTP if used; set webhook | POST /warehouses, /warehouses/{id}/nimbuspost/*, POST /webhook |
| Checkout | Check delivery is possible & quote price | GET /pincode/{pin}, GET /couriers, POST /calculate-rate |
| Fulfilment | Book, print label, hand over | POST /shipments (or /bulk), GET /shipments/{id}/label, POST /shipments/labels |
| In transit | Keep customer informed | Webhook order.status_updated, GET /shipments/{id}/track |
| Exceptions | Failed delivery? choose action. Changed mind? cancel. | GET /ndr, POST /ndr/{id}/action, POST /shipments/{id}/cancel |
| Money | Watch balance; reconcile COD | GET /wallet, /wallet/transactions, /cod-remittances |
402 if wallet_balance + approved credit is below the charge. Cancelling refunds the freight to the wallet. Recharge from the panel (Wallet → Add Money).
Couriers guide
One API, four couriers. You never call the courier yourself — pick a courier_code from GET /couriers and the API handles the courier-specific payloads, labels, tracking, cancellation and NDR calls.
| Courier | Before you ship | Notes | Label |
|---|---|---|---|
| Delhivery | Warehouse is registered automatically on create/sync. | Bulk tracking, NDR actions, pincode serviceability (this API's pincode endpoints use Delhivery data). | Label generated from your label template. |
| Shadowfax | Uses your warehouse pick_address_id as the pickup code; phone is sanitised to digits. | Weight is sent in grams automatically. NDR actions supported. | Label generated from your label template. |
| Amazon Shipping | Addresses are split into ≤60-char lines automatically; add consignee_email. | Cancellation uses Amazon's shipment id (stored for you). NDR actions supported. | Amazon's own label PDF is stored at booking and returned as-is. |
| Nimbus Post | Warehouse must be OTP-verified once (see Warehouses → Nimbus Post). | Cancellation by AWB. NDR actions supported. | Label generated from your label template. |
courier_codevalues are specific to your account and chosenexpress_type— always read them fromGET /couriersrather than hard-coding.- A courier is offered only when your rate card has a slab for exactly the weight you ask about; use a weight that appears in
GET /rate-card(weight_slabs). - If the courier does not return an AWB the order is not created and nothing is charged.
- Tracking, cancellation, labels and NDR actions are routed automatically from the shipment's courier — the same endpoints work for all four.
Errors, limits & pagination
Response envelope
| HTTP | Meaning | What to do |
|---|---|---|
200/201 | OK / created | — |
401 | Missing or invalid token | Check the Authorization header; regenerate if needed. |
402 | Insufficient wallet balance | Recharge, then retry. |
403 | Account inactive / not allowed | Contact support. |
404 | Not found (or not yours) | Check the id. |
409 | Duplicate client_order_id | Treat as "already booked"; use the returned shipment. |
422 | Validation or courier rejection | Fix the input — message/errors say what. |
429 | Rate limit (600/min) | Back off using Retry-After. |
5xx | Server or courier outage | Retry with the same client_order_id — duplicates are safe. |
Pagination
List endpoints take per_page (max 100, default 20) and page, and return:
Idempotency
Always send your own client_order_id on POST /shipments. If a network error leaves you unsure whether the order was booked, repeat the call: you'll get 409 with the existing shipment instead of a second booking and a second charge.
Endpoints
Authentication
/auth/login
Login (get token)
Exchange your panel email + password for a Bearer token. Use this for server-to-server integrations that prefer revocable tokens; otherwise copy the static token from Panel → API Settings.
No token required.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
email |
body | string | required | Panel login email. e.g. user@example.com |
password |
body | string | required | Panel password. e.g. secret |
device_name |
body | string | optional | Label for this token. Logging in again with the same name replaces the old token. e.g. my-erp |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/auth/login" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "secret",
"device_name": "my-erp"
}'
Response 200
{
"success": true,
"message": "Login successful.",
"data": {
"token": "12|AbCdEf…",
"token_type": "Bearer",
"user": {
"id": 42,
"name": "Acme Store",
"email": "user@example.com",
"wallet_balance": 1500,
"wallet_credit": 0,
"status": "active"
}
}
}
Errors
401— Invalid credentials.403— Account inactive.422— Validation failed.
/auth/profile
Get profile
Returns the account the token belongs to. A cheap way to verify a token works.
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/auth/profile" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"id": 42,
"name": "Acme Store",
"email": "user@example.com",
"phone": "9811122233",
"wallet_balance": 1500,
"wallet_credit": 0,
"status": "active",
"created_at": "2026-01-10 09:00:00"
}
}
/auth/token/rotate
Rotate token
Revokes the token used for this call and returns a new one. Store the new token immediately — the old one stops working at once.
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/auth/token/rotate" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"message": "Token rotated successfully.",
"data": {
"token": "13|XyZ…",
"token_type": "Bearer"
}
}
/auth/logout
Logout (revoke token)
Revokes the token used for this call.
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/auth/logout" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"message": "Logged out successfully."
}
Warehouses (pickup addresses)
/warehouses
List warehouses
All your pickup/return addresses. pick_address_id is what you pass when creating shipments.
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/warehouses" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": [
{
"id": 7,
"address_title": "Main Warehouse",
"sender_name": "Acme Store",
"full_address": "Plot 4, Industrial Area Phase 2",
"phone": "9811122233",
"email": "ops@acme.com",
"pincode": "110020",
"city": "New Delhi",
"state": "Delhi",
"pick_address_id": "Main Warehouse",
"delhivery_synced": true,
"nimbuspost_synced": false,
"created_at": "2026-09-01 10:00:00"
}
]
}
/warehouses
Create warehouse
Saves the address and registers it with Delhivery. If the courier call fails the warehouse is still saved and the response carries warning: true — retry with the sync endpoint.
How to use
- Create each pickup location once, then reuse its
pick_address_id. - Check
delhivery_syncedin the response (or GET /warehouses/{id}); only synced warehouses can book Delhivery shipments. - For Nimbus Post, also run the two OTP steps below once per warehouse.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
address_title |
body | string | required | Unique name for the pickup point (max 100). Becomes pick_address_id. e.g. Main Warehouse |
sender_name |
body | string | required | Contact / seller name printed on labels (max 100). e.g. Acme Store |
full_address |
body | string | required | Street address, without city/state/pincode. e.g. Plot 4, Industrial Area Phase 2 |
phone |
body | string | required | 10–15 digits, no spaces or +. e.g. 9811122233 |
email |
body | string | optional | Pickup contact email. |
pincode |
body | string | required | 6 digits. e.g. 110020 |
city |
body | string | required | City. e.g. New Delhi |
state |
body | string | required | State. e.g. Delhi |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/warehouses" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"address_title": "Main Warehouse",
"sender_name": "Acme Store",
"full_address": "Plot 4, Industrial Area Phase 2",
"phone": "9811122233",
"email": "ops@acme.com",
"pincode": "110020",
"city": "New Delhi",
"state": "Delhi"
}'
Response 201
{
"success": true,
"message": "Warehouse created and synced with courier.",
"data": {
"id": 7,
"address_title": "Main Warehouse",
"sender_name": "Acme Store",
"full_address": "Plot 4, Industrial Area Phase 2",
"phone": "9811122233",
"email": "ops@acme.com",
"pincode": "110020",
"city": "New Delhi",
"state": "Delhi",
"pick_address_id": "Main Warehouse",
"delhivery_synced": true,
"nimbuspost_synced": false,
"created_at": "2026-09-01 10:00:00"
}
}
Errors
422— Validation failed.
/warehouses/{id}
Get warehouse
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | Warehouse id. e.g. 7 |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/warehouses/7" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"id": 7,
"address_title": "Main Warehouse",
"sender_name": "Acme Store",
"full_address": "Plot 4, Industrial Area Phase 2",
"phone": "9811122233",
"email": "ops@acme.com",
"pincode": "110020",
"city": "New Delhi",
"state": "Delhi",
"pick_address_id": "Main Warehouse",
"delhivery_synced": true,
"nimbuspost_synced": false,
"created_at": "2026-09-01 10:00:00"
}
}
Errors
404— Warehouse not found.
/warehouses/{id}
Update warehouse
Updates the address on our side only. Changes are not pushed to the courier automatically — call the sync endpoint afterwards.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | Warehouse id. e.g. 7 |
address_title |
body | string | required | Unique name for the pickup point (max 100). Becomes pick_address_id. e.g. Main Warehouse |
sender_name |
body | string | required | Contact / seller name printed on labels (max 100). e.g. Acme Store |
full_address |
body | string | required | Street address, without city/state/pincode. e.g. Plot 4, Industrial Area Phase 2 |
phone |
body | string | required | 10–15 digits, no spaces or +. e.g. 9811122233 |
email |
body | string | optional | Pickup contact email. |
pincode |
body | string | required | 6 digits. e.g. 110020 |
city |
body | string | required | City. e.g. New Delhi |
state |
body | string | required | State. e.g. Delhi |
Code samples
curl -X PUT "https://hubbazzar.com/api/v1/shipping/warehouses/7" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"address_title": "Main Warehouse",
"sender_name": "Acme Store",
"full_address": "Plot 4, Industrial Area Phase 2",
"phone": "9811122233",
"email": "ops@acme.com",
"pincode": "110020",
"city": "New Delhi",
"state": "Delhi"
}'
Response 200
{
"success": true,
"message": "Warehouse updated. Note: Changes are NOT automatically synced to the courier. Use the /sync endpoint if needed.",
"data": {
"id": 7,
"address_title": "Main Warehouse",
"sender_name": "Acme Store",
"full_address": "Plot 4, Industrial Area Phase 2",
"phone": "9811122233",
"email": "ops@acme.com",
"pincode": "110020",
"city": "New Delhi",
"state": "Delhi",
"pick_address_id": "Main Warehouse",
"delhivery_synced": true,
"nimbuspost_synced": false,
"created_at": "2026-09-01 10:00:00"
}
}
Errors
404— Warehouse not found.422— Validation failed.
/warehouses/{id}
Delete warehouse
Removes the address from your account. Existing shipments are not affected.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | Warehouse id. e.g. 7 |
Code samples
curl -X DELETE "https://hubbazzar.com/api/v1/shipping/warehouses/7" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"message": "Warehouse deleted successfully."
}
/warehouses/{id}/sync
Sync warehouse with Delhivery
Re-registers the warehouse with Delhivery. Use after a failed create or after editing the address.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | Warehouse id. e.g. 7 |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/warehouses/7/sync" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"message": "Warehouse synced with courier successfully.",
"data": {
"id": 7,
"address_title": "Main Warehouse",
"sender_name": "Acme Store",
"full_address": "Plot 4, Industrial Area Phase 2",
"phone": "9811122233",
"email": "ops@acme.com",
"pincode": "110020",
"city": "New Delhi",
"state": "Delhi",
"pick_address_id": "Main Warehouse",
"delhivery_synced": true,
"nimbuspost_synced": false,
"created_at": "2026-09-01 10:00:00"
}
}
Errors
502— Courier sync failed — message explains why.
/warehouses/{id}/nimbuspost/send-otp
Nimbus Post — send OTP (step 1)
Nimbus Post verifies the pickup phone by OTP before accepting a warehouse. This sends the OTP to the warehouse phone.
How to use
- Needed once per warehouse, only if you ship with Nimbus Post.
- Call this endpoint, read the OTP from the SMS, then call step 2.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | Warehouse id. e.g. 7 |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/warehouses/7/nimbuspost/send-otp" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"message": "OTP sent to 9811122233."
}
Errors
404— Warehouse not found.422— Nimbus Post OTP send failed.
/warehouses/{id}/nimbuspost/sync
Nimbus Post — verify OTP & register (step 2)
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | Warehouse id. e.g. 7 |
otp |
body | string | required | OTP received on the warehouse phone. e.g. 482913 |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/warehouses/7/nimbuspost/sync" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"otp": "482913"
}'
Response 200
{
"success": true,
"message": "Warehouse synced with Nimbus Post."
}
Errors
422— OTP verification or registration failed.
Couriers, Rates & Serviceability
/couriers
List available couriers + price
The same list the panel shows on "Create Order". Returns every courier you can book for a given mode and weight, with the exact amount that will be debited. Use the returned courier_code in POST /shipments.
How to use
- Call it with the parcel's
express_type,weight,payment_modeandorder_amount. - Show the options to your user (or pick the cheapest
total) and keep the chosencourier_code. - A courier only appears when your rate card has a slab for exactly that weight. Empty list = no rate card / no slab for that weight.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
express_type |
query | enum | optional | surface (default) or air. e.g. surface |
weight |
query | number | optional | KG, default 0.5. e.g. 0.5 |
payment_mode |
query | enum | optional | Prepaid (default) or COD. e.g. COD |
order_amount |
query | number | optional | Needed for COD % fee. e.g. 1499 |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/couriers?express_type=surface&weight=0.5&payment_mode=COD&order_amount=1499" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"express_type": "surface",
"weight_kg": 0.5,
"couriers": [
{
"courier_name": "Delhivery",
"courier_code": "Delhivery",
"weight_slab": "0.5 KG",
"freight": 60,
"cod_charge": 30,
"cod_percentage": 0,
"gst": 16.2,
"total": 106.2
}
]
},
"note": "Only couriers with an active rate-card slab for exactly this weight are listed."
}
/rate-card
Get rate card
Your full active rate card(s): weight slabs, zones, forward/RTO rates and COD fees, grouped by courier.
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/rate-card" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": [
{
"rate_card_id": 3,
"courier": "Delhivery",
"name": "Delhivery",
"weight_slabs": [
{
"id": 1,
"label": "0.5 KG",
"weight": 0.5
}
],
"zones": [
{
"id": 1,
"zone_name": "Zone A"
}
],
"rates": {
"surface": {
"forward": {
"0.5 KG": {
"Zone A": {
"base_rate": 60,
"additional_rate": 50
}
}
}
}
},
"cod_rates": {
"surface": {
"forward": {
"All Slabs": {
"cod_charge": 30,
"cod_percentage": 0
}
}
}
}
}
]
}
/calculate-rate
Calculate rate
Price one courier for one parcel. Weights above the largest slab are charged per extra 0.5 KG.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
courier_code |
body | string | required | Courier code or name. e.g. Delhivery |
weight |
body | number | required | KG, min 0.1. e.g. 0.5 |
express_type |
body | enum | required | surface or air. e.g. surface |
payment_mode |
body | enum | required | Prepaid or COD. e.g. COD |
order_amount |
body | number | optional | Required for COD. e.g. 1499 |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/calculate-rate" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"courier_code": "Delhivery",
"weight": 0.5,
"express_type": "surface",
"payment_mode": "COD",
"order_amount": 1499
}'
Response 200
{
"success": true,
"data": {
"courier": "Delhivery",
"weight_kg": 0.5,
"mode": "surface",
"payment_mode": "COD",
"slab_matched": "0.5 KG",
"breakdown": {
"base_freight": 60,
"cod_charge": 30,
"taxable": 90,
"gst_18pct": 16.2,
"total_charge": 106.2
},
"note": "All amounts in INR. GST @ 18% is included in the total."
}
}
Errors
404— No rate card / slab / rate entry for that courier or weight.422— Validation failed.
/pincode/{pincode}
Check pincode serviceability
Delhivery serviceability for a pincode (prepaid / COD / pickup / reverse). Use it as a pre-check before offering shipping at checkout.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
pincode |
path | string | required | 6 digits. e.g. 122001 |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/pincode/122001" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"pincode": "122001",
"success": true,
"serviceable": true,
"message": "Serviceable"
}
Errors
422— Invalid pincode.
/pincode/bulk
Bulk pincode check
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
pincodes |
body | array | required | 1–50 six-digit pincodes. |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/pincode/bulk" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pincodes": [
"110001",
"400001"
]
}'
Response 200
{
"success": true,
"data": {
"total": 2,
"serviceable": 2,
"not_serviceable": 0,
"results": [
{
"pincode": "110001",
"success": true,
"serviceable": true
}
]
}
}
Shipments
/shipments
Create shipment
Books the shipment with the courier, debits your wallet and returns the AWB. This runs the exact same logic as the panel's Create Order, so wallet/credit checks, commission and courier rules behave identically.
How to use
- Have a synced warehouse (
pick_address_id) — see Warehouses. - Call GET /couriers for this weight/mode to get a valid
courier_codeand the price. - POST /shipments. On success you get
waybill(AWB) immediately and the wallet is debitedshipping_charge.total. - Fetch the label with GET /shipments/{id}/label and give the parcel to the courier.
- Track with webhooks (recommended) or GET /shipments/{id}/track.
- No AWB from the courier means no order and no charge — the call fails with 422.
- Safe retries: always send your own
client_order_id. If the first attempt actually succeeded, the retry returns 409 with the existing shipment instead of booking twice.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
consignee_name |
body | string | required | Receiver name (max 200). e.g. Rahul Sharma |
consignee_mobile |
body | string | required | Receiver phone, 10 digits (max 15 chars). e.g. 9876543210 |
consignee_address1 |
body | string | required | Address line 1. e.g. Flat 12, Green Park |
consignee_address2 |
body | string | optional | Address line 2 / landmark. |
consignee_pincode |
body | string | required | Exactly 6 digits. e.g. 122001 |
consignee_city |
body | string | optional | Strongly recommended — some couriers reject blank city. e.g. Gurugram |
consignee_state |
body | string | optional | Strongly recommended — some couriers reject blank state. e.g. Haryana |
consignee_email |
body | string | optional | Valid email. Recommended for Amazon Shipping. |
payment_mode |
body | enum | required | Prepaid or COD (case-sensitive). e.g. COD |
order_amount |
body | number | required | Invoice value in INR. For COD this is also the amount collected. e.g. 1499 |
pick_address_id |
body | string | required | pick_address_id of one of your warehouses (GET /warehouses). e.g. Main Warehouse |
return_address_id |
body | string | optional | Warehouse to receive RTOs. Defaults to the pickup warehouse. |
express_type |
body | enum | required | surface or air. Must match the courier option you pick. e.g. surface |
courier_code |
body | string | required | A courier_code from GET /couriers for this express_type and weight. e.g. Delhivery |
shipment_weight |
body | number | required | Dead weight in KG, min 0.1. Must match a slab on your rate card. e.g. 0.5 |
shipment_length |
body | number | optional | cm. e.g. 20 |
shipment_width |
body | number | optional | cm. e.g. 15 |
shipment_height |
body | number | optional | cm. e.g. 10 |
client_order_id |
body | string | optional | Your own order number (max 100). Must be unique among your non-cancelled shipments — a duplicate returns 409, which makes retries safe. Auto-generated (API-XXXXXXXX) when omitted. e.g. MY-ORD-2001 |
invoice_number |
body | string | optional | Printed on label/invoice (max 100). |
address_type |
body | enum | optional | Home, Office or Other. Default Home. |
products |
body | array | optional | Items: product_name (required per item), product_quantity, product_price, sku. A single "Product" line is used when omitted. |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/shipments" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_order_id": "MY-ORD-2001",
"invoice_number": "INV-2001",
"consignee_name": "Rahul Sharma",
"consignee_mobile": "9876543210",
"consignee_email": "rahul@example.com",
"consignee_address1": "Flat 12, Green Park Apartments",
"consignee_address2": "Sector 15",
"consignee_pincode": "122001",
"consignee_city": "Gurugram",
"consignee_state": "Haryana",
"address_type": "Home",
"payment_mode": "COD",
"order_amount": 1499,
"pick_address_id": "Main Warehouse",
"return_address_id": "Main Warehouse",
"express_type": "surface",
"courier_code": "Delhivery",
"shipment_weight": 0.5,
"shipment_length": 20,
"shipment_width": 15,
"shipment_height": 10,
"products": [
{
"product_name": "Cotton T-Shirt",
"product_quantity": 2,
"product_price": 749.5,
"sku": "TSH-001"
}
]
}'
Response 201
{
"success": true,
"message": "Shipment created successfully.",
"data": {
"order_id": 1024,
"client_order_id": "MY-ORD-2001",
"waybill": "1234567891",
"courier_code": "Delhivery",
"order_status": "manifested",
"shipping_charge": {
"base": 60,
"cod": 30,
"gst": 16.2,
"total": 106.2
},
"wallet_balance": 1393.8
}
}
Errors
402— Insufficient wallet balance (wallet + approved credit).data.required_amount/available_amountincluded.403— Account type cannot create shipments via API.409— client_order_id already exists (existing shipment returned indata).422— Validation failed, unknown/inactive courier_code, unknown pickup or return address, or the courier rejected the order (message explains why).
/shipments/bulk
Bulk create (up to 50)
Creates many shipments in one call. Each order is processed independently with the same rules as single create; one failure does not stop the rest.
How to use
- Send
orders: an array of up to 50 objects, each with the same fields as Create shipment. - Read
data.succeededanddata.failed(each failure hasindex+reason). HTTP 201 if at least one order was created, 422 if none. - Always set
client_order_idon each order so you can match results and retry only the failed ones.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
orders |
body | array | required | 1–50 order objects (same schema as Create shipment). |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/shipments/bulk" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orders": [
{
"client_order_id": "MY-ORD-2001",
"invoice_number": "INV-2001",
"consignee_name": "Rahul Sharma",
"consignee_mobile": "9876543210",
"consignee_email": "rahul@example.com",
"consignee_address1": "Flat 12, Green Park Apartments",
"consignee_address2": "Sector 15",
"consignee_pincode": "122001",
"consignee_city": "Gurugram",
"consignee_state": "Haryana",
"address_type": "Home",
"payment_mode": "COD",
"order_amount": 1499,
"pick_address_id": "Main Warehouse",
"return_address_id": "Main Warehouse",
"express_type": "surface",
"courier_code": "Delhivery",
"shipment_weight": 0.5,
"shipment_length": 20,
"shipment_width": 15,
"shipment_height": 10,
"products": [
{
"product_name": "Cotton T-Shirt",
"product_quantity": 2,
"product_price": 749.5,
"sku": "TSH-001"
}
]
}
]
}'
Response 201
{
"success": true,
"message": "1 order(s) created, 1 failed.",
"data": {
"succeeded": [
{
"order_id": 1024,
"client_order_id": "MY-ORD-2001",
"waybill": "1234567891"
}
],
"failed": [
{
"index": 1,
"client_order_id": "MY-ORD-2002",
"reason": "Pickup address not found. Please use a valid pick_address_id from your warehouses."
}
]
}
}
/shipments
List shipments
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
status |
query | string | optional | Exact order_status (see Status reference). e.g. in_transit |
payment_mode |
query | enum | optional | Prepaid / COD. |
waybill |
query | string | optional | Exact AWB. |
client_order_id |
query | string | optional | Partial match. |
date_from |
query | date | optional | YYYY-MM-DD. |
date_to |
query | date | optional | YYYY-MM-DD. |
per_page |
query | integer | optional | Max 100, default 20. |
page |
query | integer | optional | Page number. |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/shipments?status=in_transit&per_page=20" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"items": [
{
"id": 1024,
"client_order_id": "MY-ORD-2001",
"waybill": "1234567891",
"order_status": "in_transit",
"payment_mode": "COD",
"order_amount": 1499,
"cod_amount": 1499,
"shipping_charge": 70.8,
"courier_code": "Delhivery",
"express_type": "surface",
"pick_address_id": "Main Warehouse",
"created_at": "2026-10-01 11:20:05"
}
],
"total": 1,
"per_page": 20,
"current_page": 1,
"last_page": 1
}
}
/shipments/{id}
Get shipment
Full details of one shipment (consignee, dimensions, products, charges, refund and delivery info). id is the order_id returned at creation.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | order_id. e.g. 1024 |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/shipments/1024" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"id": 1024,
"client_order_id": "MY-ORD-2001",
"waybill": "1234567891",
"order_status": "in_transit",
"payment_mode": "COD",
"order_amount": 1499,
"cod_amount": 1499,
"shipping_charge": 70.8,
"courier_code": "Delhivery",
"express_type": "surface",
"pick_address_id": "Main Warehouse",
"created_at": "2026-10-01 11:20:05",
"invoice_number": "INV-2001",
"consignee_name": "Rahul Sharma",
"consignee_mobile": "9876543210",
"consignee_address1": "Flat 12, Green Park",
"consignee_address2": "Sector 15",
"consignee_pincode": "122001",
"consignee_city": "Gurugram",
"consignee_state": "Haryana",
"shipment_weight": 0.5,
"shipment_length": 20,
"shipment_width": 15,
"shipment_height": 10,
"products": [
{
"product_name": "Cotton T-Shirt",
"product_quantity": 2
}
],
"extra_charges": 30,
"tax_amount": 16.2,
"refunded_amount": 0,
"cancel_reason": null,
"cancelled_at": null,
"picked_up_at": null,
"delivered_at": null,
"label_printed": false,
"channel": "api",
"updated_at": "2026-10-01 11:20:05"
}
}
Errors
404— Shipment not found.
/shipments/{id}/track
Track shipment
Status + scan history. Works for every courier (Delhivery, Shadowfax, Amazon Shipping, Nimbus Post) and returns the same shape. Without sync you get cached data (refreshed by courier webhooks); with sync=1 we fetch live from the courier first.
How to use
- Prefer webhooks (order.status_updated) for real-time updates and use this for on-demand lookups.
- Pass
sync=1to force a live refresh — this also updates the order status, NDR flags and delivery date. - If the live call fails you still get the last cached data plus
data.sync_warning. - Event fields
event_date,status_code,status_description,status_locationare present for every courier; Delhivery events carry extra scan fields.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | order_id. e.g. 1024 |
sync |
query | boolean | optional | 1 = fetch live from the courier first. e.g. 1 |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/shipments/1024/track?sync=1" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"order_id": 1024,
"waybill": "1234567891",
"courier_code": "Delhivery",
"order_status": "in_transit",
"is_ndr": false,
"tracking": {
"status": true,
"synced_at": "2026-10-02 10:15:00",
"current_status": {
"status_code": "in_transit",
"scan_type": "UD",
"status_description": "Shipment in transit",
"status_location": "Gurugram_Hub (Haryana)",
"event_date": "2026-10-02T09:40:00"
},
"tracking_events": [
{
"event_date": "2026-10-02T09:40:00",
"status_code": "in_transit",
"scan_type": "UD",
"status_location": "Gurugram_Hub (Haryana)",
"status_description": "Shipment in transit"
},
{
"event_date": "2026-10-01T18:05:00",
"status_code": "manifested",
"scan_type": "UD",
"status_location": "Delhi_Hub (Delhi)",
"status_description": "Pickup done"
}
],
"estimated_delivery_date": "2026-10-04"
}
}
}
Errors
404— Shipment not found.
/shipments/{id}/label
Download label (PDF)
Returns the shipping label as a PDF file (Content-Type: application/pdf). It is the same document the panel prints — Amazon Shipping's own label for Amazon orders, your configured label template for the rest. Save the response body to a file.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | order_id. e.g. 1024 |
format |
query | string | optional | url returns JSON with a panel link instead of the PDF (needs a panel login to open). |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/shipments/1024/label" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-o document.pdf
Response 200
Errors
404— Shipment not found.422— No AWB assigned yet.502— Label could not be generated.
/shipments/labels
Bulk labels (one merged PDF)
Up to 100 labels merged into a single PDF. Orders without an AWB are skipped; the number included is in the X-Label-Count header.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
order_ids |
body | array | required | 1–100 order ids. |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/shipments/labels" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"order_ids": [
1024,
1025
]
}' \
-o document.pdf
Response 200
Errors
404— None of the orders were found or have an AWB.422— Validation failed.
/shipments/{id}/invoice
Download invoice (PDF)
The order invoice rendered with your invoice settings.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | order_id. e.g. 1024 |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/shipments/1024/invoice" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-o document.pdf
Response 200
Errors
404— Shipment not found.
/shipments/{id}/cancel
Cancel shipment
Cancels with the courier first; only if the courier accepts is the order cancelled locally and the freight refunded to your wallet.
How to use
- Works until the courier picks the parcel up / the shipment reaches a terminal state.
- A courier refusal returns 422 and leaves the order untouched — nothing is refunded.
- On success the
order.cancelledwebhook fires.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | order_id. e.g. 1024 |
reason |
body | string | optional | Stored as the cancel reason. e.g. Customer changed mind |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/shipments/1024/cancel" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reason": "Customer changed mind"
}'
Response 200
{
"success": true,
"message": "Shipment cancelled successfully.",
"data": {
"order_id": 1024,
"waybill": "1234567891",
"refunded_amount": 106.2,
"wallet_balance": 1500,
"courier_cancel": true
}
}
Errors
404— Shipment not found.422— Not cancellable, or the courier rejected the cancellation.
/shipments/bulk-cancel
Bulk cancel (up to 50)
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
order_ids |
body | array | required | 1–50 order ids. |
reason |
body | string | optional | Cancel reason. |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/shipments/bulk-cancel" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"order_ids": [
1024,
1025
],
"reason": "Out of stock"
}'
Response 200
{
"success": true,
"message": "1 order(s) cancelled, 1 failed.",
"data": {
"succeeded": [
{
"order_id": 1024,
"waybill": "1234567891",
"refunded_amount": 106.2
}
],
"failed": [
{
"order_id": 1025,
"reason": "Order is not cancellable (status: delivered)"
}
],
"wallet_balance": 1500
}
}
NDR (Non-Delivery Reports)
/ndr
List NDR shipments
Shipments whose last delivery attempt failed and are waiting for your decision.
How to use
- Poll this, or react to
order.status_updatedwebhooks whereis_ndris true. - Pick an action per shipment (re-attempt or reschedule) using the action endpoints.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
action_status |
query | enum | optional | pending (no action yet) or actioned. e.g. pending |
nsl_code |
query | string | optional | Filter by NSL reason code. |
date_from |
query | date | optional | NDR date from. |
date_to |
query | date | optional | NDR date to. |
per_page |
query | integer | optional | Max 100. |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/ndr?action_status=pending" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"items": [
{
"id": 1030,
"client_order_id": "MY-ORD-2007",
"waybill": "1234567899",
"courier_code": "Delhivery",
"consignee_name": "Rahul Sharma",
"consignee_mobile": "9876543210",
"consignee_city": "Gurugram",
"consignee_pincode": "122001",
"order_status": "pending",
"is_ndr": true,
"ndr_reason": "Consignee unavailable",
"nsl_code": "EOD-6",
"ndr_at": "2026-10-02 12:00:00",
"ndr_action_type": null,
"ndr_upl_id": null,
"ndr_action_at": null,
"payment_mode": "COD",
"order_amount": 1499
}
],
"total": 1,
"per_page": 20,
"current_page": 1,
"last_page": 1
}
}
/ndr/{id}/action
Submit NDR action
Tells the courier what to do. Routed automatically to the shipment's courier (Delhivery, Shadowfax, Amazon Shipping, Nimbus Post).
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | order_id. e.g. 1030 |
action |
body | enum | required | RE-ATTEMPT (deliver again) or PICKUP_RESCHEDULE. e.g. RE-ATTEMPT |
remark |
body | string | optional | Free text (max 500). |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/ndr/1030/action" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "RE-ATTEMPT"
}'
Response 200
{
"success": true,
"message": "NDR action submitted successfully.",
"data": {
"order_id": 1030,
"waybill": "1234567899",
"action": "RE-ATTEMPT",
"upl_id": "UPL-8891",
"action_at": "2026-10-02 14:00:00"
}
}
Errors
404— Order not found.422— No active NDR / no AWB / courier rejected the action.
/ndr/bulk-action
Bulk NDR action (up to 100)
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
order_ids |
body | array | required | 1–100 order ids. |
action |
body | enum | required | RE-ATTEMPT or PICKUP_RESCHEDULE. e.g. RE-ATTEMPT |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/ndr/bulk-action" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"order_ids": [
1030,
1031
],
"action": "RE-ATTEMPT"
}'
Response 200
{
"success": true,
"message": "1 action(s) submitted, 1 failed.",
"data": {
"succeeded": [
{
"order_id": 1030,
"waybill": "1234567899",
"upl_id": "UPL-8891"
}
],
"failed": [
{
"order_id": 1031,
"waybill": "1234567800",
"reason": "API error"
}
]
}
}
/ndr/sync
Refresh NDR status
Re-checks your open shipments with their couriers and updates NDR flags. Delhivery shipments are refreshed in bulk; for the other couriers up to 40 shipments are refreshed per call. Normally unnecessary — courier webhooks keep this current.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
limit |
body | integer | optional | Max shipments to scan (default 200, max 500). e.g. 200 |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/ndr/sync" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"limit": 200
}'
Response 200
{
"success": true,
"message": "Synced 57 orders.",
"data": {
"synced": 57
}
}
/ndr/nsl-codes
NSL reason codes
Lookup table of every NDR reason code (also listed in the Reference section).
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/ndr/nsl-codes" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": [
{
"nsl_code": "EOD-6",
"remark": "Consignee unavailable"
}
],
"total": 102
}
Wallet & COD
/wallet
Wallet balance
Balance and credit limit. Recharge the wallet from the panel (Wallet → Add Money). Check available_total before bulk jobs.
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/wallet" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"wallet_balance": 1500,
"wallet_credit": 0,
"available_total": 1500,
"currency": "INR",
"note": "available_total = wallet_balance + wallet_credit. Shipping charges are deducted from available_total."
}
}
/wallet/transactions
Wallet transactions
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
type |
query | string | optional | Transaction type (e.g. debit, credit, refund). |
payment_method |
query | string | optional | Payment method. |
reference |
query | string | optional | Partial match on reference (e.g. an order id). |
date_from |
query | date | optional | YYYY-MM-DD. |
date_to |
query | date | optional | YYYY-MM-DD. |
per_page |
query | integer | optional | Max 100. |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/wallet/transactions" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"items": [
{
"id": 501,
"transaction_id": "SHP-123456",
"type": "debit",
"payment_method": "wallet",
"amount": 106.2,
"balance_after": 1393.8,
"reference": "Shipping charge for MY-ORD-2001",
"created_at": "2026-10-01 11:20:05"
}
],
"total": 1,
"per_page": 20,
"current_page": 1,
"last_page": 1
}
}
/cod-remittances/summary
COD summary
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/cod-remittances/summary" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"total_remitted_inr": 25000,
"total_accruing_inr": 4200,
"counts": {
"pending": 2,
"in_progress": 1,
"completed": 14
}
}
}
/cod-remittances
List COD remittances
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
from |
query | date | optional | cod_date from. |
to |
query | date | optional | cod_date to. |
status |
query | string | optional | pending, in_progress, completed, settled, failed. |
per_page |
query | integer | optional | Max 100. |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/cod-remittances" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"summary": {
"total_remitted": 25000,
"total_accruing": 4200
},
"items": [
{
"id": 9,
"cod_date": "2026-09-28",
"settle_at_date": "2026-10-01",
"settlement_days": 3,
"charge_percent": 1.5,
"total_cod_amount": 10000,
"total_charge_amount": 150,
"total_net_amount": 9850,
"adjusted_in_wallet": 9850,
"status": "completed",
"status_label": "Completed",
"awb_count": 8
}
],
"total": 1,
"per_page": 20,
"current_page": 1,
"last_page": 1
}
}
/cod-remittances/{id}
COD remittance detail
One remittance with the AWBs it covers and the wallet transactions that settled it.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
id |
path | integer | required | Remittance id. e.g. 9 |
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/cod-remittances/9" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"id": 9,
"cod_date": "2026-09-28",
"status": "completed",
"total_net_amount": 9850,
"items": [
{
"awb": "1234567891",
"cod_amount": 1499,
"charge": 22.49,
"net_amount": 1476.51
}
],
"transactions": [
{
"adjust_via": "wallet",
"wallet_transaction_ref": "COD-9",
"opening_balance": 1500,
"closing_balance": 11350,
"transaction_amount": 9850,
"created_at": "2026-10-01 06:00:00"
}
]
}
}
Errors
404— COD remittance not found.
Webhook configuration
/webhook
View webhook config
Code samples
curl -X GET "https://hubbazzar.com/api/v1/shipping/webhook" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"data": {
"webhook_url": "https://yourapp.com/hooks/shipping",
"webhook_secret": "***a1b2",
"has_secret": true
}
}
/webhook
Save webhook config
Sets where events are delivered. Provide a webhook_secret (16–64 chars) or auto_secret: true to have one generated. The plaintext secret is returned only when it is set/created — store it.
Parameters
| Name | In | Type | Description | |
|---|---|---|---|---|
webhook_url |
body | string | required | Public HTTPS URL (max 500). e.g. https://yourapp.com/hooks/shipping |
webhook_secret |
body | string | optional | 16–64 chars; used to sign deliveries. |
auto_secret |
body | boolean | optional | Generate a random secret when none is given. e.g. 1 |
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/webhook" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://yourapp.com/hooks/shipping",
"auto_secret": true
}'
Response 200
{
"success": true,
"message": "Webhook configuration saved.",
"data": {
"webhook_url": "https://yourapp.com/hooks/shipping",
"webhook_secret": "k3J…48chars"
}
}
/webhook/test
Send test event
Fires a signed webhook.test event at your URL so you can verify signature handling.
Code samples
curl -X POST "https://hubbazzar.com/api/v1/shipping/webhook/test" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"message": "Test webhook dispatched to https://yourapp.com/hooks/shipping"
}
Errors
422— No webhook URL configured.
/webhook
Remove webhook
Code samples
curl -X DELETE "https://hubbazzar.com/api/v1/shipping/webhook" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN"
Response 200
{
"success": true,
"message": "Webhook configuration removed."
}
Webhook events
Set your URL with POST /webhook (or in API Settings). We POST JSON to it when something happens, so you don't have to poll. Respond with any 2xx quickly; process the event asynchronously. Deliveries are one-shot (no automatic retry), so also reconcile with GET /shipments now and then.
Verify the signature
Compute HMAC-SHA256(secret, raw request body) and compare in constant time. Use the raw body, not re-encoded JSON.
<?php
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '')) { http_response_code(401); exit; }
$event = json_decode($raw, true); // $event['event'], $event['data']
http_response_code(200);Events
order.createdA shipment was created through the API.{
"event": "order.created",
"timestamp": "2026-10-04T14:30:00+05:30",
"data": {
"order_id": 1024,
"client_order_id": "MY-ORD-2001",
"waybill": "1234567891",
"courier_code": "Delhivery",
"order_status": "manifested",
"shipping_charge": {
"base": 60,
"cod": 30,
"gst": 16.2,
"total": 106.2
},
"wallet_balance": 1393.8
}
}order.status_updatedA courier pushed a status change to us (Delhivery, Shadowfax, Amazon Shipping and Nimbus Post). Fires on every real transition; a manual track?sync=1 refreshes the status but does not emit this event.{
"event": "order.status_updated",
"timestamp": "2026-10-04T14:30:00+05:30",
"data": {
"order_id": 1024,
"client_order_id": "MY-ORD-2001",
"waybill": "1234567891",
"previous_status": "in_transit",
"new_status": "delivered",
"status_description": "Shipment delivered",
"status_location": "Gurugram (Haryana)",
"event_date": "2026-10-04T14:28:00",
"nsl_code": null,
"is_ndr": false
}
}order.cancelledA shipment was cancelled via the API.{
"event": "order.cancelled",
"timestamp": "2026-10-04T14:30:00+05:30",
"data": {
"order_id": 1024,
"client_order_id": "MY-ORD-2001",
"waybill": "1234567891",
"refunded_amount": 106.2
}
}orders.bulk_cancelledA bulk cancel finished.{
"event": "orders.bulk_cancelled",
"timestamp": "2026-10-04T14:30:00+05:30",
"data": {
"succeeded_count": 2,
"failed_count": 0,
"succeeded": [
{
"order_id": 1024,
"waybill": "1234567891",
"refunded_amount": 106.2
}
]
}
}ndr.action_submittedAn NDR action was sent to the courier.{
"event": "ndr.action_submitted",
"timestamp": "2026-10-04T14:30:00+05:30",
"data": {
"order_id": 1030,
"waybill": "1234567899",
"action": "RE-ATTEMPT",
"upl_id": "UPL-8891",
"nsl_code": "EOD-6",
"ndr_reason": "Consignee unavailable"
}
}webhook.testTriggered by POST /webhook/test.{
"event": "webhook.test",
"timestamp": "2026-10-04T14:30:00+05:30",
"data": {
"message": "This is a test webhook from Hubbazzar Shipping API.",
"user_id": 42
}
}Order statuses
order_status uses one vocabulary for all couriers — each courier's raw statuses are normalised for you. Filter with GET /shipments?status=….
| order_status | Meaning | Final? |
|---|---|---|
manifested | Order booked with the courier, pickup pending. | No |
not_picked | Pickup attempted / not yet collected. | No |
in_transit | Moving through the courier network. | No |
dispatched | Handed to the delivery agent. | No |
out_for_delivery | Out for delivery today. | No |
pending | Delivery attempt did not complete (see is_ndr). | No |
ndr | Non-delivery report — needs your action. | No |
delivered | Delivered to the consignee. | Yes |
rto | Return to origin initiated. | No |
rto_in_transit | Returning to your warehouse. | No |
rto_returned | Returned to your warehouse. | Yes |
cancelled | Cancelled (locally code 227 on older records). | Yes |
lost | Lost / damaged in transit. | Yes |
Older records may still carry numeric Delhivery codes (220–238, 531; e.g. 226 delivered, 227 cancelled, 237 lost). A shipment can be cancelled until it reaches a final state (delivered, rto, cancelled, lost) — and, in practice, until the courier has picked it up.
NDR reason codes (102)
Appear as nsl_code on NDR shipments. Also available live from GET /ndr/nsl-codes.
| Code | Reason |
|---|---|
EOD-53 | Amount disputed by customer |
EOD-26 | Consignee unavailable |
SD-107 | Order will be rescheduled due to low capacity - Drop |
EOD-89 | Customer wants to reschedule beyond 3 working days |
CL-102 | Canceled as per client's instructions |
CL-101 | Re-Scheduled as per client's instruction |
ST-6W | Consignee verified cancellation |
ST-120 | Maximum attempts reached for self collect |
EOD-118 | Temporary stair/lift issue |
ST-NI | IVR call not received by customer |
EOD-40 | Payment Mode / Amt Dispute |
X-IIN3R | No Service Zone. To be RTO'd |
EOD-14 | Office closed |
DTUP-229 | Content Short Reported By client |
EOD-147 | Consignee refused to Accept |
EOD-56 | Customer phone not reachable |
EOD-35 | Other |
EOD-11 | Consignee Unavailable |
EOD-158 | Failed KYC -Code not available |
EOD-106 | Reached Maximum attempt count |
X-IIN1R | No Service Zone. To be RTO'd |
EOD-6 | Consignee refused to accept/order cancelled |
DLYB2B-107 | Payment mode/ Amount dispute |
X-IIN4R | No Service Zone. To be RTO'd |
EOD-103 | Reached maximum attempt count |
EOD-50 | Returned as per client instructions |
EOD-114 | Buyer refused order |
EOD-102 | Customer not ready for e-KYC |
EOD-43 | Consignee will collect from branch |
EOD-85 | Customer phone not reachable |
SC-107 | Customer requested for self collect |
COVID19-007 | Corona Client Closed |
EOD-15 | Recipient wants delivery at a different address |
COVID19-003 | Corona Police/AdministrationShut Down |
ST-205 | Consignee unavailable - Consignee location permanently closed |
EOD-70 | Unable to reach Customer |
DLYRPC-417 | Address on package is different from HQ address |
EOD-149 | On Hold. Recipient unable to Accept Delivery |
RT-105 | Wrong pin code & address is not serviceable |
COVID19-004 | Corona Limited Dispatch Timings |
CL-108 | Canceled as per customer instruction |
EOD-65 | Not attempted |
DLYDC-107 | Office/Institute closed |
COVID19-010 | Corona Red Zone Area |
EOD-42 | Product Dispute |
EOD-107 | No Service Zone |
EOD-18 | Consignee has refused delivery of product |
RT-114 | Address details invalid |
EOD-17 | Non serviceable location |
DLYB2B-105 | No Entry/ Entry restricted |
EOD-51 | Deferred by customer |
SC-104 | Bad Address |
EOD-121 | Entry restricted area |
EOD-74 | Incomplete address & contact details |
EOD-8 | Wanted to open the package before payment |
EOD-69 | Recipient wants open delivery |
EOD-55 | Customer not available at home |
EOD-104 | Entry restricted area |
ST-NT | NDR call not received by customer |
EOD-32 | Consignee unavailable for extended duration |
EOD-12 | Door locked, retrial tomorrow (Customer to be called) |
EOD-148 | Recipient unavailable. Establishment closed |
ST-109 | Out of service area |
EOD-41 | Payment Mode-Amount dispute |
EOD-78 | Cash not ready with the customer. To be attempted again |
FMEOD-143 | Seller/Buyer cancelled order - Geofenced |
X-IIN2R | No Service Zone. To be RTO'd |
EOD-13 | No such person at the given address |
DLYB2B-108 | Consignee not reachable/Contact details unavailable |
EOD-151 | Consignee unavailable - Permanently closed |
ST-NI6 | No customer response from IVR call |
RT-107 | Unsuccessful NDR Reattempt |
X-NSZ | Non-serviceable location |
ST-102 | Bad/Incomplete Address |
EOD-83 | Damaged/Used Product |
EOD-67 | Customer phone not Reachable |
SC-102 | ODA Shipment |
FMEOD-115 | Temporary stairs/lift problem |
EOD-72 | Consignee did not have cash |
EOD-16 | Payment not ready with customer |
EOD-110 | Customer did not show ID card |
DLYLH-146 | Appointment awaited from consignee |
EOD-45 | Duplicate consignment for RTO |
FMEOD-118 | Seller/Buyer cancelled order |
EOD-59 | Not interested |
SC-103 | Self Collect requested by customer |
EOD-3 | Delivery Rescheduled by Customer |
EOD-138 | Consignee did not receive the Code |
EOD-97 | Customer does not want to handover documents |
FMEOD-150 | Seller/Buyer cancelled order - OTP Verified |
FMEOD-138 | Seller/Buyer cancelled order - qr verified |
DTUP-226 | Address changed by Customer |
EOD-76 | Consignee not available for extended duration |
DLYB2B-106 | Payment Issue ( Amount Dispute/ Cash/ Cheque not ready |
EOD-34 | Other |
DLYDC-132 | Out of Delivery Area (ODA) |
EOD-73 | Bad/Incomplete address |
EOD-6O | Code verified cancellation |
EOD-105 | Shipment seized by consignee |
EOD-46 | Duplicate Consignment from Shipper |
EOD-112 | Dispatch but not attempted due to rain |
EOD-98 | No KYC request from Customer Side |
Troubleshooting
GET /couriers returns an empty listGET /rate-card → weight_slabs, or ask support to enable a courier.Invalid courier_codecourier_code returned by GET /couriers for the same express_type.Pickup address not foundpick_address_id must equal a warehouse's pick_address_id from GET /warehouses (not its numeric id).POST /warehouses/{id}/nimbuspost/send-otp then /nimbuspost/sync with the OTP.402 Insufficient wallet balanceGET /wallet → available_total.409 on createclient_order_id is already booked — use the returned shipment or pick a new id (cancelled shipments free their id).GET /shipments/{id}.?sync=1 to force a live refresh; subscribe to order.status_updated for push updates.