Dashboard Support

Introduction

The TrackCourier.io API provides a unified interface to track shipments across major Indian and international couriers. With one integration, you can track packages from Speed Post, Blue Dart, Delhivery, DTDC, Shree Tirupati and many more.

Base URL: https://api.trackcourier.io/v1

All API requests are made over HTTPS. Requests made over plain HTTP will fail.

Authentication

All API requests require authentication via the X-API-Key header.

X-API-Key: your_api_key_here

Get your API key from your dashboard after signing up.

Rate Limits

Plan Monthly Requests Rate Limit
Free 100 10/minute
Starter 5,000 60/minute
Pro 50,000 300/minute

๐Ÿš€ Get Started in 3 Steps

Start tracking shipments in under 5 minutes. No complex setup required.

1

Get Your Free API Key

Sign up with Google and get 100 free requests/month instantly. No credit card required.

Get API Key โ†’
2

Make Your First Tracking Request

Copy this code and replace your_api_key with your actual key:

curl "https://api.trackcourier.io/v1/track?courier=dtdc&tracking_number=R1000482882" \ -H "X-API-Key: your_api_key"
import requests response = requests.get( "https://api.trackcourier.io/v1/track", params={"courier": "dtdc", "tracking_number": "R1000482882"}, headers={"X-API-Key": "your_api_key"} ) data = response.json() print(data["data"]["ShipmentState"]) # "intransit", "delivered", etc.
const response = await fetch( "https://api.trackcourier.io/v1/track?courier=dtdc&tracking_number=R1000482882", { headers: { "X-API-Key": "your_api_key" } } ); const data = await response.json(); console.log(data.data.ShipmentState); // "intransit", "delivered", etc.
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://api.trackcourier.io/v1/track?courier=dtdc&tracking_number=R1000482882"); curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: your_api_key"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $data = json_decode($response, true); echo $data["data"]["ShipmentState"]; // "intransit", "delivered", etc.
3

Handle the Response

You'll get back a JSON response with the shipment status and tracking history:

{ "success": true, "data": { "ShipmentState": "delivered", // Normalised state "MostRecentStatus": "Delivered", // Our short label, not the courier's wording "Checkpoints": [...] // Full history; sort by Date+Time yourself }, "usage": { "used": 1, "quota": 100, "plan": "growth", "percent": 1.0 } }

That's it! You're now tracking shipments. Check the API Reference for more details.

Check everything in one command, before importing anything. This runs our Postman collection, with its own checks, from your machine. It needs Node.js. The tracking requests use recently delivered shipments where we have one for that courier; the webhook requests run only if you also pass --env-var "webhook_url=https://your-receiver/path", and are skipped otherwise:

npx newman run https://api.trackcourier.io/postman --env-var "api_key=your_api_key"

Common Use Cases

๐Ÿ›’ E-commerce

Show customers their order's tracking status on your website

๐Ÿ“ฑ Mobile Apps

Push delivery notifications to your app users

๐Ÿค– Automation

Trigger workflows based on delivery status changes

Track Shipment

GET /v1/track

Track a shipment by courier and tracking number. Returns current status and checkpoint history.

Query Parameters

Parameter Type Description
courier required string Courier slug (e.g., "bluedart", "delhivery")
tracking_number required string AWB or tracking number

Response

{ "success": true, "data": { "MostRecentStatus": "Delivered", "ShipmentState": "delivered", "Checkpoints": [ { "Activity": "Delivered", "CheckpointState": "delivered", "CourierName": "DTDC", "Date": "18-Sep-2026", "Time": "17:08", "Location": "Delhi" }, { "Activity": "Out for Delivery - Shipment is out for Delivery 2026-09-18 14:09:12", "CheckpointState": "outfordelivery", "CourierName": "DTDC", "Date": "18-Sep-2026", "Time": "14:09", "Location": "Delhi" }, { "Activity": "Received at DELHI Delivery Centre", "CheckpointState": "intransit", "CourierName": "DTDC", "Date": "18-Sep-2026", "Time": "09:31", "Location": "Delhi" }, { "Activity": "On The Way - On its way between JAIPUR and DELHI", "CheckpointState": "intransit", "CourierName": "DTDC", "Date": "17-Sep-2026", "Time": "22:15", "Location": "Jaipur to Delhi" } ], "CourierHasNoRecordOfShipment": false, "isEmptyTable": false, "Result": "success", "AdditionalInfo": "", "FetchTime": "4.9084", "PODImageUrl": "", "ExecutionTime": "" }, "usage": { "used": 45, "quota": 100, "plan": "growth", "percent": 45.0 } }

Status Values

StatusDescription
pendingShipment created, not yet picked up
in_transitShipment moving through network
out_for_deliveryWith delivery agent
deliveredSuccessfully delivered
exceptionDelivery issue (RTO, failed attempt)

Response Codes

200 Success - tracking data returned
401 Invalid or missing API key
402 Quota exceeded, or the account needs attention. Check error.code.
404 Courier not found

List Couriers

GET /v1/couriers

Get every courier you can track, with the slug to use in your requests. This endpoint does not count against your quota.

Response

{ "success": true, "data": { "couriers": [ {"slug": "bluedart", "name": "Blue Dart"}, {"slug": "delhivery", "name": "Delhivery"}, {"slug": "dtdc", "name": "DTDC"} ], "count": 37 } }

Get Courier Details

GET /v1/couriers/{slug}

Get details for a specific courier by its slug. This endpoint does not count against your quota.

Response

{ "success": true, "data": { "slug": "delhivery", "name": "Delhivery" } }

Webhooks

Webhooks push shipment events to your server. We re-check the shipments we follow with the courier at scheduled times, for most couriers at our current 3 re-checks a day, and sometimes in between; you do not need to poll for us to keep following them. Two couriers are followed only when looked up: ARC Carrier and Om Logistics. Poll GET /v1/track for them if you need their changes. Events are not real-time: an event about a shipment reaches you only after one of our re-checks sees the change, so it can reach you hours after the courier recorded it.

Set up a webhook in your dashboard. If your account includes live webhooks (the Growth and Business plans do), sign in, open Webhooks in your dashboard, choose + Create webhook, enter your https:// URL, and tick every event you act on. The same page has Rotate signing secret, which issues a new signing secret, shown once; keep accepting the old one as well for at least 15 minutes after you rotate.

Your signing secret signs every webhook on your account, test and live. The call that generates it gives it to you once, if that call succeeds: your account's first create, or a rotation made before any create. Save it then, and make that first create on its own. A later create returns a notice beginning with < in its place; do not store that, as your secret is unchanged.

Test webhooks need no particular plan: create one through the API with "mode": "test". The webhook requests in our Postman collection create one and send a sample delivery to it.

How It Works

1. Configure your webhook URL in the dashboard
2. Subscribe to tracking numbers you want to monitor
3. Receive POST requests when status changes

Webhook Event Types

We send one of these events when we re-check a shipment and find its status changed to one with an event below, or more checkpoints than before.

Event Description
tracking.updated More checkpoints than before, with no change to a state that has its own event
tracking.in_transit The shipment's status changed to in_transit
tracking.out_for_delivery The shipment's status changed to out_for_delivery
tracking.delivered The shipment's status changed to delivered. Terminal: we stop following it
tracking.exception The shipment's status changed to exception, returned or cancelled

A change to a state that has its own event replaces tracking.updated for that update, so one change never produces two events for one webhook. A webhook receives only the events it lists, so list every event you act on.

tracking.exception covers both a problem the shipment can recover from and a terminal non-delivery: read data.status to tell them apart (returned and cancelled are terminal, exception is not).

Webhook Payload

All webhook events are sent as POST requests with a JSON body:

Note: the webhook payload is snake_case throughout, including inside each checkpoint. It is not the same shape as the GET /v1/track response above, which returns our cache document in PascalCase. If you use both surfaces, write two mappings: a model generated from one decodes nothing from the other.

{ "event": "tracking.delivered", "enqueued_at": "2026-09-18T09:14:22Z", "data": { "tracking_number": "7X118822611", "courier": "dtdc", "status": "delivered", "most_recent_status": "Delivered", "origin_city": null, "destination_city": null, "delivered_date": null, "checkpoints": [ { "activity": "Delivered", "checkpoint_state": "delivered", "courier_name": "DTDC", "date": "18-Sep-2026", "time": "17:08", "location": "Delhi" }, { "activity": "Out for Delivery - Shipment is out for Delivery 2026-09-18 14:09:12", "checkpoint_state": "outfordelivery", "courier_name": "DTDC", "date": "18-Sep-2026", "time": "14:09", "location": "Delhi" }, { "activity": "Received at DELHI Delivery Centre", "checkpoint_state": "intransit", "courier_name": "DTDC", "date": "18-Sep-2026", "time": "09:31", "location": "Delhi" }, { "activity": "On The Way - On its way between JAIPUR and DELHI", "checkpoint_state": "intransit", "courier_name": "DTDC", "date": "17-Sep-2026", "time": "22:15", "location": "Jaipur to Delhi" } ], "enqueued_at": "2026-09-18T09:14:22Z" } }

Webhook Security

Verify that webhook requests are from TrackCourier.io by checking the signature.

Signature Verification

Every webhook includes an X-Trackcourier-Signature header containing an HMAC-SHA256 signature of the request body.

Three further headers accompany every delivery. None of the headers is signed: the signature covers the body alone, so take anything you act on from the verified body.

  • X-Webhook-Event: the event type, e.g. tracking.delivered. The verified body carries the same name as event.
  • X-Webhook-Delivery: an attempt identifier, not an event identifier. It is <webhook_id>-<attempt>, so it changes on every retry of the same event, and the first attempt of every event sent to one webhook carries the same value. Do not deduplicate on it. See Handle duplicates below.
  • X-Webhook-Mode: test for a sample delivery to a test webhook, live otherwise. To tell a test delivery apart once its signature verifies, read data.courier in the body: a test delivery's begins synthetic-test-.
X-Trackcourier-Signature: sha256=a1b2c3d4e5f6...

Verification Example (Python)

import hashlib import hmac import json from fastapi import FastAPI, Request from fastapi.responses import JSONResponse app = FastAPI() # Your signing secret. We show it only once: save it when we do. WEBHOOK_SECRET = "your_signing_secret" def verify(raw_body: bytes, header: str, secret: str) -> bool: if not header.isascii(): # compare_digest raises on non-ASCII text return False expected = "sha256=" + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, header) @app.post("/webhook") async def handle_webhook(request: Request): raw_body = await request.body() # the raw bytes, never a re-serialised object signature = request.headers.get("X-Trackcourier-Signature", "") if not verify(raw_body, signature, WEBHOOK_SECRET): return JSONResponse({"error": "Invalid signature"}, status_code=401) event = json.loads(raw_body) # Replace your stored checkpoints for this consignment with # event["data"]["checkpoints"], unless it is null (see Handle duplicates). return {"received": True}

Retry Policy

Any 2xx response counts as delivered. We follow redirects, so do not answer with one: after a 301, 302 or 303 the redirected request is sent as a GET without the body, and we record a success for a delivery your endpoint never received; after a 307 or 308 we send the signed body on to wherever the redirect points. Return 2xx at the URL you configured. Anything else is retried on this schedule:

  • Retry 1: After 1 minute
  • Retry 2: After 5 minutes
  • Retry 3: After 30 minutes
  • Retry 4: After 2 hours
  • Retry 5: After 24 hours

That is up to six attempts in all: the first, then these five retries. After the sixth fails, the delivery is abandoned and is not sent again. We also check your URL before every attempt: a delivery to a host that does not resolve, or that resolves to a private, loopback, link-local or unspecified address, is dropped without a retry. A live webhook is deactivated after 20 of its deliveries in a row have failed in either of these ways; a successful delivery starts that count again. Your dashboard shows each live webhook's failure count and its recent deliveries, and its Reactivate button switches it back on once your endpoint is fixed.

Best Practices

  • Respond quickly: Return 200 within 5 seconds. Process async if needed.
  • Handle duplicates: deliveries are at-least-once, so the same event can reach you more than once. Make your handler idempotent: a payload whose checkpoints is an array carries the shipment's full known history, so replace your stored checkpoints for that consignment with the payload's rather than appending, and keep the ones you hold when checkpoints is null. Applying the same payload twice then changes nothing. To also skip obvious retries, event + data.courier + data.tracking_number + enqueued_at is fixed for the life of an event, but it is not unique: enqueued_at is to the second, so two different tracking.updated events for one consignment queued in the same second share it. Use it to skip repeated work, never to discard an event. Do not key on data.status or the checkpoint list: each retry rebuilds data from our freshest record.
  • Verify signatures: Always validate the HMAC signature.
  • Use HTTPS: Only configure HTTPS webhook URLs.

Error Handling

Errors return a consistent JSON structure with an error code and message.

{ "success": false, "error": { "code": "QUOTA_EXCEEDED", "message": "Monthly quota of 100 requests exceeded. Upgrade your plan for more.\n\nThis same key works again as soon as you upgrade: https://api.trackcourier.io/pricing" } }

Error Codes

Code HTTP Status Description
INVALID_API_KEY 401 API key is invalid or missing
QUOTA_EXCEEDED 402 Monthly request quota exceeded
ACCOUNT_NEEDS_ATTENTION 402 The account needs attention. Email api@trackcourier.io
COURIER_NOT_FOUND 404 Unknown courier slug. When we can tell which slug you meant, the error also carries did_you_mean.
COURIER_NOT_SUPPORTED 400 Tracking for this courier is not available through the API. GET /v1/couriers lists every courier you can track.
TRACKING_NOT_FOUND 404 Tracking number not found
TIMEOUT 504 Courier backend timeout - retry
SERVICE_ERROR 502 Temporary service error - retry

Supported Couriers

Every plan, including Free, can track every courier below. Use the slug in your API requests.

Blue Dart bluedart
Delhivery delhivery
DTDC dtdc
India Post indiapost
Speed Post speedpost
XpressBees xpressbees
Shadowfax shadowfax
Ekart Logistics ekart
Trackon Courier trackon
Shree Maruti shreemaruti
Professional Courier professional
Gati gati
Safexpress safexpress
Canada Post canadapost
Shree Tirupati Courier shreetirupati
Shree Anjani Courier shreeanjani
ST Courier stcourier
VRL Logistics vrl
Skyking skyking
Shree Mahavir Courier shreemahavir
Shree Nandan Courier shreenandan
Shiprocket shiprocket
India Post International indiapostinternational
TCI Express tciexpress
Om Logistics omlogistics
V-Trans vtrans
Rivigo rivigo
TCI Freight tcifreight
ARC Carrier arccarrier
Madhur Courier madhur
Registered Post registeredpost
V-Xpress vxpress
NITCO Roadways nitco
Pos Laju poslaju
GMS Worldwide gmsworldwide
Mark Express markexpress
DP World Express dpworld