Topping Courier
API and integrations

Book and track from your own systems.

A simple REST API for prices, bookings and live status, with webhooks for every update. Plus a plugin for WooCommerce stores.

Instant prices

Get the exact price before you book.

Bookings

One drop-off or fifty, from your systems.

Webhooks

Status changes pushed to your address as they happen.

Sandbox

Test keys that never create real deliveries.

Getting started

  1. Ask for access. API access is for business accounts. Ask for it from your account (API tab), and we'll turn it on.
  2. Create a key in your account under API. Test keys use the sandbox; live keys book real deliveries.
  3. Send the key with every request:

Authorization: Bearer tc_live_xxxxxxxxxxxxxxxx

Base address: https://toppingcourier.ca/api/v1/. Requests and responses are JSON. Amounts are in Canadian dollars, as strings ("38.42"). Times are ISO 8601 with the Toronto time zone.

Test and live keys

Test keys (tc_test_...) book sandbox orders: they are never dispatched, charged or messaged to anyone, and they only appear to test keys. Move a test order along with the simulate call (below) to try your webhooks.

Errors and limits

Errors come back as {"error": "..."}, or a list of field problems, with an HTTP status:

Status Meaning
400 Something in the request is missing or can't be booked (the message says why)
401 The key is missing, unknown or revoked
404 No such order or delivery for this account
409 The order can't be changed any more (for example, already picked up)
429 Daily or monthly call limit reached (see your account for your limits)

Get a price

POST /orders/quote takes the same body as booking and returns the full price, without booking anything.

Book deliveries

POST /orders

json { "speed": "same_day", "pickup": {"contact_name": "Front desk", "phone": "647 555 0100", "company": "Maple Print Shop", "line1": "100 Queen St W", "city": "Toronto", "postal_code": "M5H 2N2"}, "drops": [ {"contact_name": "Sam Lee", "phone": "647 555 0133", "email": "sam@example.com", "line1": "1 Dundas St W", "unit": "200", "city": "Toronto", "postal_code": "M5G 1Z3", "size": "small", "reference": "PO-1002", "instructions": "Ask at reception"} ], "reference": "Batch 42", "payment_method": "invoice" }

  • speed: rush (one drop-off), same_day, next_day or scheduled (add date, and optionally window_hours and window_start).
  • size: doc, small, medium, large or xl.
  • distance_km per drop-off is needed only when we can't measure the addresses ourselves.
  • Optional per drop-off: declared_value, heavy_pieces, return_trip, instructions.
  • payment_method: invoice (approved accounts) or later.

The answer (201) is the order, with a tracking number and tracking link for each delivery.

Orders

  • GET /orders: your orders, newest first. Filters: status, reference, created_after, created_before (YYYY-MM-DD), page, page_size (up to 100).
  • GET /orders/{number}: one order, with its deliveries.
  • POST /orders/{number}/cancel: cancel an order that hasn't been picked up. Body (optional): {"reason": "..."}.
  • GET /orders/{number}/labels.pdf: printable 4 x 6 inch labels, one per delivery.

Deliveries

GET /deliveries/{tracking}: status, arrival estimate, times, and proof of delivery (who received it, and links to the photo).

Statuses: booked, assigned, picked_up, on_the_way, delivered, failed, cancelled.

Sandbox: simulate a delivery

POST /orders/{number}/simulate with {"to": "assigned"}, then picked_up, then delivered (or failed). Test orders only.

Webhooks

Add an HTTPS address in your account under API, and choose the events: created, assigned, picked_up, on_the_way, delivered, failed, cancelled, invoice_issued. We send a POST with:

json {"id": "evt_...", "type": "delivered", "created_at": "2027-03-11T14:37:02-05:00", "test": false, "data": {"order": {...}, "delivery": {...}}}

Answer with any 2xx status within 10 seconds. If you don't, we try again several times with growing gaps. Events can arrive more than once: use the id to ignore repeats.

Check the signature. Each request has a Topping-Signature header: t=<unix time>,v1=<signature>. The signature is the HMAC-SHA256 of <t>.<raw body>, keyed with your endpoint's signing secret (shown once when you add it):

```python
import hashlib, hmac, time

def verify(secret, header, body):
parts = dict(p.split('=', 1) for p in header.split(','))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts['v1']) and abs(time.time() - int(parts['t'])) < 300
```

php function topping_verify($secret, $header, $body) { parse_str(str_replace(',', '&', $header), $p); $expected = hash_hmac('sha256', $p['t'] . '.' . $body, $secret); return hash_equals($expected, $p['v1']) && abs(time() - (int) $p['t']) < 300; }

WooCommerce

Our WooCommerce plugin shows Topping delivery prices at checkout, books the delivery when an order is paid, and adds the tracking link to the order emails. Download the plugin, install it under Plugins → Add New → Upload, then paste your API key in WooCommerce → Settings → Shipping → Topping Courier.

Endpoints

All addresses start with this site's address. Calls marked with a key need Authorization: Bearer tc_.... The OpenAPI file has every field, and the Postman collection has an example for each call.

CallWhat it does
POST /api/v1/quotes (no key)A price for one delivery or a list of drops, without an account
POST /api/v1/orders/quoteThe exact price and promise for an order, without booking it
POST /api/v1/ordersBook one pickup with one or more drop-offs
GET /api/v1/ordersYour orders, newest first (25 a page)
GET /api/v1/orders/{number}One order with its deliveries
POST /api/v1/orders/{number}/cancelCancel before pickup
GET /api/v1/orders/{number}/labels.pdfShipping labels as PDF: one per box
POST /api/v1/orders/{number}/simulateTest orders only: assigned, picked_up, delivered or failed
GET /api/v1/deliveries/{tracking}One delivery by tracking number, with its proof of delivery

Webhooks

Add an HTTPS address in your account (API tab) to get a signed POST for each event: created, assigned, picked_up, on_the_way, delivered, failed, cancelled and invoice_issued. Check the Topping-Signature: t=<time>,v1=<hex> header: v1 is the HMAC-SHA256 of <t>.<raw body> with your endpoint secret.

Drive with Topping
Your vehicle, your hours, paid every week.
Apply to drive