Authentication

Every request carries your API key as a bearer token. Create keys under Settings → API Keys in your dashboard. Live keys start with sas_live_, test keys with sas_test_ — test keys behave identically but book against carrier sandboxes and are never charged.

Authorization: Bearer sas_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Endpoints

  • GET /profile — account, tier, and can_create_shipments capability.
  • POST /rates — live discounted rates across Canada Post, Purolator, UPS and FedEx for a shipment payload.
  • POST /shipments — book a shipment (all four carriers); returns label URLs and tracking. Idempotent per reference.
  • GET /shipments/{id} — status, tracking events, documents.
  • POST /returns — create a return label / QR for an existing shipment.
  • GET /tracking/{tracking_number} — tracking detail.
  • POST /webhooks, GET /webhooks, DELETE /webhooks/{id} — manage event subscriptions (below).

The API root (GET /api/ecommerce/v1) returns a machine-readable index of all endpoints.

Webhooks

Subscribe an HTTPS endpoint and we push shipment lifecycle events as they happen. Available events:

  • shipment.booked — a label was purchased.
  • shipment.tracking.updated — any tracking movement (includes the event detail).
  • shipment.delivered — delivered (also fires tracking.updated).
  • shipment.exception — a delivery exception (also fires tracking.updated).
  • shipment.voided — the shipment was cancelled/voided.

Each delivery is a JSON POST with an envelope: {"id":"evt_…","event":"shipment.booked","created":"2026-08-20T14:00:00+00:00","data":{…}}. data carries shipment_id, client_ref, carrier, tracking_number, status, live_mode, and event specifics.

For the three tracking events, data also carries the scan itself: tracking_event (one of pickup, in_transit, out_for_delivery, delivered, delivery_attempted, exception), event_type (the carrier's own type), description, location, event_time, status_text, and — on an exception — exception_code and exception_description. Fields with no value are omitted rather than sent empty, so treat every one as optional.

Verifying signatures

Every delivery includes X-SAS-Signature: t=<unix>,v1=<hmac> where v1 is the HMAC-SHA256 of <t>.<raw body> keyed with the whsec_… secret returned when you created the subscription. Verify before trusting a payload, and reject stale timestamps (we recommend a 5-minute tolerance):

$parts = [];
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_SAS_SIGNATURE']), $parts);
$expected = hash_hmac('sha256', $parts['t'] . '.' . file_get_contents('php://input'), $secret);
$valid = hash_equals($expected, $parts['v1']) && abs(time() - (int)$parts['t']) < 300;

Respond with any 2xx within 10 seconds. Failed deliveries retry with backoff (roughly 1 minute to 12 hours, 7 attempts); a subscription that keeps failing is automatically disabled — check is_active via GET /webhooks and re-create it after fixing your endpoint.

Errors & limits

Errors return a JSON body with error and a machine code (e.g. PAYMENT_FAILED, CARD_NOT_ENABLED_FOR_ECOMMERCE) and an appropriate HTTP status. Requests are rate-limited per key; the limit rides standard X-RateLimit-* response headers.

Questions or a use case we don't cover? Contact us — integrations are free on every plan.