Trackboria
Delivery Control
developer documentation

Build on the Trackboria e-commerce API.

Create delivery orders from your own system, read their status and tracking timeline, and receive signed webhooks as shipments move. This page and the OpenAPI document below cover everything a third-party integration needs.

The base URL is https://api.trackboria.com. Every endpoint on this page is versioned under /v1.

Authentication

Every request needs a merchant API key. Create, rotate, and revoke keys on the Integrations screen of your merchant dashboard. A key starts with tbk_live_ and its full value is shown once, at creation. Store it in your secret manager; if you lose it, create a new key.

Send the key with each request, either as Authorization: Bearer tbk_live_... or in the X-API-Key header.

Keys carry scopes. Creating an order needs orders:write, reading one needs orders:read, and the tracking endpoint needs tracking:read. A key that is missing a scope an endpoint requires is refused with API_KEY_SCOPE_MISSING.

The public API accepts 120 requests per minute per key. Above that, requests return HTTP 429 until the minute window passes.

Order endpoints

Create an order

POST /v1/public-api/orders

Creates an order and its shipment. Needs the orders:write scope. externalOrderRef is your own order reference; you can use it later to fetch the order.

curl -X POST https://api.trackboria.com/v1/public-api/orders \
  -H "Authorization: Bearer tbk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: SHOP-1001-attempt-1" \
  -d @order.json
Request body
{
  "externalOrderRef": "SHOP-1001",
  "customer": {
    "fullName": "Amina Bello",
    "phoneE164": "+2348012345678"
  },
  "deliveryAddress": {
    "freeTextAddress": "12 Adeola Odeku Street, Victoria Island",
    "city": "Lagos",
    "state": "Lagos",
    "countryCode": "NG",
    "landmark": "Opposite the blue bank branch"
  },
  "paymentType": "COD",
  "codAmountMinor": 1550000,
  "currency": "NGN",
  "items": [
    {
      "sku": "TSHIRT-M-BLK",
      "name": "T-shirt, medium, black",
      "quantity": 2,
      "unitPriceMinor": 775000
    }
  ]
}

Amounts are integers in minor units (kobo, cents). paymentType is one of COD, MobileMoney, Card, BankTransfer, or Wallet. deliveryAddress.countryCode is optional and defaults to your own country; a different country makes the order cross-border, which needs a plan that includes it.

Response, 201 Created
{
  "orderId": "ckz3f8p2m0001mnop4qrs5tuv",
  "externalOrderRef": "SHOP-1001",
  "shipmentId": "ckz3f8p2m0003mnopqw8xyz9a",
  "shipmentState": "created",
  "addressAssessment": {
    "confidenceScore": 0.92,
    "verified": true,
    "needsManualVerification": false
  }
}

Fetch an order

GET /v1/public-api/orders/{reference}

Returns the order, its customer and items, and the shipment with its public event timeline. reference is either the Trackboria orderId or your own externalOrderRef. Needs the orders:read scope.

curl https://api.trackboria.com/v1/public-api/orders/SHOP-1001 \
  -H "Authorization: Bearer tbk_live_..."
Response, 200 OK
{
  "orderId": "ckz3f8p2m0001mnop4qrs5tuv",
  "externalOrderRef": "SHOP-1001",
  "createdAt": "2026-08-12T09:14:03.000Z",
  "cancelledAt": null,
  "currency": "NGN",
  "paymentType": "COD",
  "codAmountMinor": 1550000,
  "customer": {
    "fullName": "Amina Bello",
    "phoneE164": "+2348012345678"
  },
  "items": [
    {
      "sku": "TSHIRT-M-BLK",
      "name": "T-shirt, medium, black",
      "quantity": 2,
      "unitPriceMinor": 775000
    }
  ],
  "shipment": {
    "shipmentId": "ckz3f8p2m0003mnopqw8xyz9a",
    "shipmentState": "in_transit",
    "paymentState": "expected",
    "deliveredAt": null,
    "etaAt": "2026-08-13T16:00:00.000Z",
    "timeline": [
      {
        "eventType": "shipment.created",
        "occurredAt": "2026-08-12T09:14:03.000Z",
        "newState": "created"
      },
      {
        "eventType": "shipment.assigned",
        "occurredAt": "2026-08-12T11:02:47.000Z",
        "newState": "assigned"
      }
    ]
  }
}

Fetch tracking

GET /v1/public-api/orders/{reference}/tracking

Returns shipment status and events only, without customer or item details, so it can back systems that should not see order contents. Cross-border checkpoint events carry the checkpoint name and country. reference works the same way as above. Needs the tracking:read scope.

curl https://api.trackboria.com/v1/public-api/orders/SHOP-1001/tracking \
  -H "Authorization: Bearer tbk_live_..."
Response, 200 OK
{
  "orderId": "ckz3f8p2m0001mnop4qrs5tuv",
  "externalOrderRef": "SHOP-1001",
  "shipment": {
    "shipmentId": "ckz3f8p2m0003mnopqw8xyz9a",
    "shipmentState": "in_transit",
    "paymentState": "expected",
    "deliveredAt": null,
    "etaAt": "2026-08-13T16:00:00.000Z",
    "events": [
      {
        "eventType": "shipment.leg_started",
        "occurredAt": "2026-08-12T14:20:00.000Z",
        "previousState": "assigned",
        "newState": "in_transit",
        "checkpoint": null
      },
      {
        "eventType": "shipment.leg_checkpoint",
        "occurredAt": "2026-08-13T08:05:12.000Z",
        "previousState": null,
        "newState": null,
        "checkpoint": {
          "name": "Arrived at export hub",
          "countryCode": "NG"
        }
      }
    ]
  }
}

Idempotency

Send a unique Idempotency-Key header with every create request. Retrying with the same key and the same body returns the stored result instead of creating a second order, so a timed-out request is always safe to retry.

Without the header, externalOrderRef is used as the idempotency key, so a retried request still cannot double-create. Sending the header explicitly remains the recommended path.

Reusing a key with a different body is refused with HTTP 409 and the code IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD. Send a new key for a new request, or resend the original body unchanged to receive the stored response.

Errors

Errors are JSON with a stable shape. Branch on code: it never changes. message is a sentence for a person and its wording can change between releases.

Messages are returned in English, or in French when the Accept-Language header prefers it. Codes are identical in both.

{
  "error": "Forbidden",
  "message": "This API key is missing required scopes: orders:write. Create a key that includes them in your Trackboria dashboard.",
  "statusCode": 403,
  "code": "API_KEY_SCOPE_MISSING",
  "details": { "missingScopes": ["orders:write"] }
}

Validation failures return HTTP 400 with the code VALIDATION_FAILED and a details.fields list naming each failing field as a dotted path, for example items.0.unitPriceMinor.

CodeStatusMeaning
API_KEY_REQUIRED401No API key was sent. Add the Authorization or X-API-Key header.
API_KEY_INVALID401The key was not accepted. Malformed, unknown, and revoked keys all get this same response.
API_KEY_SCOPE_MISSING403The key is valid but missing a scope the endpoint requires. details.missingScopes lists them.
IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD409The Idempotency-Key was already used with a different request body.
CROSS_BORDER_NOT_INCLUDED_IN_PLAN403The delivery country differs from your own and your current plan does not include cross-border delivery. details names the plans that do.

Outgoing webhooks

Register an HTTPS endpoint on the Integrations screen and choose the events it receives. Each endpoint gets its own signing secret, prefixed whsec_, shown once at creation.

Event catalogue

An endpoint can subscribe to individual events or to all of them with *. Internal operational events never leave the platform; these are the events that can be delivered:

  • shipment.created a shipment was created for an order.
  • shipment.verified the delivery details passed verification.
  • shipment.assigned a rider or courier was assigned.
  • shipment.reassigned the shipment moved to a different rider or courier.
  • shipment.delivered the parcel was delivered.
  • shipment.failed_attempt a delivery attempt failed.
  • shipment.return_initiated a return to the merchant was started.
  • shipment.returned the parcel is back with the merchant.
  • shipment.cancelled the shipment was cancelled.
  • shipment.leg_started a leg of a cross-border relay started.
  • shipment.leg_handover_completed custody passed between couriers at a relay handover.
  • shipment.leg_checkpoint the shipment passed a checkpoint on a cross-border leg.

Delivery format

Deliveries are POST requests with a JSON body. The top-level id matches the X-Trackboria-Delivery header. A delivery can arrive more than once; store the id and skip ones you have already processed.

POST https://example.com/webhooks/trackboria
Content-Type: application/json
User-Agent: Trackboria-Webhooks/1.0
X-Trackboria-Signature: t=1755082930,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Trackboria-Event: shipment.delivered
X-Trackboria-Delivery: ckz3f8p2m0009mnopde1fgh2i

{
  "id": "ckz3f8p2m0009mnopde1fgh2i",
  "event": "shipment.delivered",
  "occurredAt": "2026-08-13T15:42:10.000Z",
  "data": {
    "orderId": "ckz3f8p2m0001mnop4qrs5tuv",
    "externalOrderRef": "SHOP-1001",
    "shipmentId": "ckz3f8p2m0003mnopqw8xyz9a",
    "shipmentState": "delivered",
    "previousState": "arriving"
  }
}

Verifying signatures

The X-Trackboria-Signature header has the form t=timestamp,v1=signature. The signature is HMAC-SHA256, hex encoded, computed with your endpoint secret over the string timestamp + "." + rawBody, where the timestamp is unix seconds and the body is the raw bytes received. Compare in constant time, and reject deliveries whose timestamp is more than five minutes from your clock.

Node.js verification
const { createHmac, timingSafeEqual } = require('node:crypto');

// rawBody must be the exact bytes received, before any JSON parsing.
function verifyTrackboriaSignature(secret, rawBody, signatureHeader) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((part) => part.trim().split('=')),
  );
  const timestamp = Number(parts.t);
  if (!Number.isFinite(timestamp) || !parts.v1) return false;

  // Reject replays of captured deliveries.
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody)
    .digest('hex');
  const expectedBuffer = Buffer.from(expected, 'hex');
  const presentedBuffer = Buffer.from(parts.v1, 'hex');
  return (
    expectedBuffer.length === presentedBuffer.length &&
    timingSafeEqual(expectedBuffer, presentedBuffer)
  );
}

Retries and automatic disabling

Respond with any 2xx status within 10 seconds; anything else counts as a failed attempt. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours. After the last retry the delivery is dead-lettered; the delivery log on the Integrations screen keeps it visible.

After 15 consecutive failed attempts an endpoint is disabled automatically and you are emailed. Any successful delivery resets the counter. Re-enable the endpoint from the Integrations screen once your receiver is healthy.

OpenAPI document

The machine-readable schema for every public API endpoint, generated from the API itself and kept current by a CI check. Import it into Postman, Insomnia, or a code generator.

Download openapi.json

Trackboria | Developer documentation: e-commerce API, webhooks, OpenAPI