Gbam Logistics delivery API

Get delivery prices, book pickups and track parcels from your own website, app or system. Deliveries are paid from your business balance.

Get your API keys

Basics

1. Get prices

POST /quotes. Use a saved pickup address ({"address_id": "…"}) or send one. Prices last 30 minutes. Leave out the delivery name and phone to get prices only (e.g. at your checkout, before the customer has typed them); those quotes come back with "bookable": false.

curl https://gbamlogistics.com/api/v1/quotes \
  -H "Authorization: Bearer sk_test_…" -H "Content-Type: application/json" \
  -d '{
    "pickup":   { "name": "Adaeze Okafor", "phone": "08030000000", "address": "1 Shop Road", "landmark": "Beside GTBank", "city": "Lekki", "state": "Lagos" },
    "delivery": { "name": "Tunde Bello", "phone": "08051234567", "address": "5 Ademola Street", "city": "Wuse", "state": "FCT" },
    "parcel":   { "weight_kg": 1.5, "description": "Two dresses", "value": 2500000 }
  }'
{ "quotes": [
  { "id": "6f1c…", "service": "Express", "price": { "amount": 520000, "currency": "NGN" }, "eta_days": { "min": 1, "max": 2 }, "expires_at": "…" },
  { "id": "9a2b…", "service": "Standard", "price": { "amount": 341000, "currency": "NGN" }, "eta_days": { "min": 2, "max": 4 }, "expires_at": "…" }
] }

2. Book

POST /shipments. Paid from your balance and booked straight away. Send your own reference (e.g. your order number): repeating a request with the same reference returns the same shipment and never charges twice, so retries are safe.

curl https://gbamlogistics.com/api/v1/shipments \
  -H "Authorization: Bearer sk_test_…" -H "Content-Type: application/json" \
  -d '{ "quote_id": "9a2b…", "reference": "ORDER-1001", "accept_terms": true }'
{ "tracking_code": "QKMFNRNP18", "reference": "ORDER-1001", "status": "booked", "service": "Standard",
  "price": { "amount": 341000, "currency": "NGN" }, "delivery_code": "4821",
  "tracking_url": "https://gbamlogistics.com/track/QKMFNRNP18", "created_at": "…", "test": true }

accept_terms: true confirms the parcel has no prohibited items and you accept the delivery terms. Give delivery_code to your customer only: riders may ask for it at delivery.

Errors to handle: 402 insufficient_funds (top up, or on a credit account: the limit is reached), 402 account_on_hold (an invoice is overdue), 410 quote_expired (get a new quote), 409 quote_used, 400 price_only_quote (the quote was made without the recipient's name and phone: quote again with them).

3. Track and manage

Statuses: booked, picked_up, in_transit, out_for_delivery, delivered, delivery_failed, cancelled, booking_failed, refunded.

4. Webhooks

Add an https address in your dashboard. On every status change we POST {"id", "type": "parcel.delivered", "created_at", "data": {"tracking_code", "reference", "status", "previous_status", "test"}}. Reply with any 2xx within 10 seconds; otherwise we retry after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h. Use id to ignore repeats.

Check every request is from us: X-Signature: t=<unix time>,v1=<HMAC-SHA256 of "t.body" with your signing secret>.

import { createHmac, timingSafeEqual } from "node:crypto";

function isFromUs(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // too old: possible replay
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Verify against the raw request body (before parsing JSON).