Developers
Build on ParcelOps
Quote, book, track and pull documents for your shipments over a clean REST API, and let webhooks push status changes to you. The whole reference is on this page — no sales call between you and the docs.
Quick start
Your first request in under a minute
- 01Log in to your courier’s customer portal and create a key under Integrations → API Keys. The key acts as your customer account — it sees your shipments, your invoices, nothing else.
- 02Send it as a Bearer token:
curl https://api.parcelops.co.za/api/v1/customer/shipments \
-H "Authorization: Bearer po_YOUR_API_KEY"Not on ParcelOps yet? Any courier running ParcelOps can enable portal access for you — or start a trial and issue your own keys.
Webhooks
Six events, pushed to you
Subscribe from the portal (Integrations → Webhooks) and ParcelOps POSTs each event to your endpoint as it happens — no polling.
shipment.collectedThe parcel has been picked up from the sendershipment.out_for_deliveryOn a vehicle for final deliveryshipment.deliveredDelivered, with POD capturedshipment.failed_deliveryA delivery attempt failed, with the reasoninvoice.issuedAn invoice has been raised on your accountinvoice.paidA payment has been receipted against an invoiceThe delivery contract
- Every delivery is signed:
X-Parcelops-Signaturecarries an HMAC-SHA256 of the raw body, keyed with your endpoint’s secret. X-Parcelops-Delivery-Idis unique per delivery — use it to deduplicate.- Respond with any 2xx within 15 seconds. Anything else is retried five times with exponential backoff, starting a minute later.
Example payload
{
"id": "9f3c2a44-1c1e-4b7e-9a1d-8c2f6f5e7d10",
"event": "shipment.delivered",
"timestamp": "2026-09-09T14:32:07.000Z",
"tenantSlug": "your-courier",
"data": {
"waybillNumber": "ABC-000123",
"status": "DELIVERED"
}
}Verifying the signature (Node)
import crypto from "crypto";
// X-Parcelops-Signature: sha256=<hex hmac of the raw body>
function verify(rawBody, header, secret) {
const expected = "sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Reference
Every endpoint, in full
The complete customer API — request and response schemas, try-it-out, and copy-paste examples in your language.
Get started
Ship your integration this week
An API key from the portal is all you need. If you get stuck, a developer answers — not a ticket queue.