HMV Courier API
A REST API to quote, book, track, label and get real-time webhooks for deliveries — built for storefronts, WMS integrations and shipping aggregators.
The API is organised around predictable, resource-oriented URLs, returns JSON, and uses standard HTTP verbs and status codes. Every response shares one envelope so you branch on a single field.
# All endpoints are relative to https://api.hmvcourier.com # Authenticate every request Authorization: Bearer hmv_live_...
Authentication
Authenticate with your secret API key as a bearer token. Create and revoke keys in Settings → API. A key is shown once — store it securely.
Rate & scope
Keys are scoped to your business — every request only ever sees and affects your own bookings and wallet.
Requests & errors
Send JSON bodies with Content-Type: application/json. Money is in naira (NGN) as plain numbers — not kobo. Timestamps are Africa/Lagos local time.
On failure, the HTTP status gives the class and error.code is a stable machine-readable string.
| Status | error.code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key. |
| 402 | insufficient_funds | Wallet balance below the booking price. |
| 404 | not_found | No such endpoint, or a booking that isn't yours. |
| 405 | method_not_allowed | Wrong HTTP verb. |
| 409 | not_cancellable | Booking already picked up / cancelled. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different body. |
| 422 | invalid_request | A field is missing or malformed. |
| 422 | cross_state_not_allowed | same_day/next_day across two states. |
Idempotency
Safely retry a POST /v1/bookings without risk of a double booking or
double wallet charge: send an Idempotency-Key header with a unique value
(a UUID) per booking. If a request with that key already completed, we return the
original response verbatim (with Idempotent-Replayed: true) instead
of booking again.
Reusing a key with a different body returns 409 idempotency_conflict.
Keys are scoped to your business; use a fresh key for each new booking.
curl https://api.hmvcourier.com/v1/bookings \ -H "Authorization: Bearer hmv_live_..." \ -H "Idempotency-Key: 3f1c9e2a-7b6d-4a11-9c02-8e5f" \ -H "Content-Type: application/json" \ -d '{ ... }' # Retry the exact same call → same response, charged once.
Webhooks
Register endpoint URLs in Settings → API → Webhooks. When a booking changes status we POST a signed JSON event to every active endpoint — so you never poll.
Event
data.status is the new status, data.previous_status the one before.Delivery & retries
We expect a 2xx within 8s. Non-2xx or timeout is retried with exponential backoff (immediate, 1m, 5m, 30m, 2h, 6h) for up to 6 attempts. Make your handler idempotent using the event id.
Statuses you'll see
picked_up · at_origin_hub · in_transit · at_destination_hub · out_for_delivery · delivered · failed · returned · cancelled
POST /your/webhook HTTP/1.1 Content-Type: application/json HMV-Event: booking.status_changed HMV-Delivery: 4821 HMV-Signature: t=1753329123,v1=3f9a…
Verifying signatures
Each delivery is signed so you can trust it came from HMV. The HMV-Signature header is t=<unix>,v1=<hex> where v1 = HMAC_SHA256(secret, "<t>.<raw_body>").
Recompute it with your endpoint's signing secret (whsec_…) over the raw request body and compare in constant time. Optionally reject if t is more than a few minutes old.