Every Relay request carries a per-partner API key, and every request except public tracking is signed with an HMAC-SHA256 signature over a timestamped canonical string. Signing proves the request body was not altered in transit and binds it to a moment in time, defeating tampering and replay. This page is the practical guide; the wire contract lives in the API Reference.
Keys are environment-scoped: a sandbox key authenticates only against the sandbox host, a production key only against production. Keys and signing secrets never cross environments, and sandbox never touches live carriers.
Each partner is issued an API key presented on every request in the X-Api-Key header. The key identifies the partner and selects that partner's rate limits, scopes, and IP allowlist.
Beyond the API key, every request (except public tracking, ยง6) is signed. Three headers participate:
| Header | Value |
|---|---|
| X-Api-Key | The partner API key. |
| X-Relay-Timestamp | Current time in unix seconds. |
| X-Relay-Signature | HMAC-SHA256(signing_secret, timestamp + "." + raw_body), hex-encoded. |
., then the exact raw request body as sent on the wire. For bodyless requests the body is the empty string, so the signed string is timestamp + ".". The server recomputes the HMAC over the raw received bytes and compares in constant time.import time, hmac, hashlib, json, requests API_KEY = "pk_sandbox_..." # X-Api-Key SIGNING_SECRET = b"whsec_..." # per-partner signing secret (bytes) BASE = "https://sandbox.relay.lastmile.us/v2" # Serialize the body ONCE and send the exact bytes you signed. body = json.dumps({"direction": "return", "fulfillment": "print_label", "order_id": "A-10042"}, separators=(",", ":")).encode() ts = str(int(time.time())) msg = ts.encode() + b"." + body # timestamp + "." + raw_body sig = hmac.new(SIGNING_SECRET, msg, hashlib.sha256).hexdigest() resp = requests.post(BASE + "/shipments", data=body, headers={ "X-Api-Key": API_KEY, "X-Relay-Timestamp": ts, "X-Relay-Signature": sig, "Idempotency-Key": "11111111-2222-3333-4444-555555555555", "Content-Type": "application/json", })
invalid_signature.Relay rejects any request whose X-Relay-Timestamp differs from server time by more than 300 seconds (invalid_signature, details.reason = timestamp_skew). Because the timestamp is inside the signed string, a captured request cannot be replayed after the window, and a fresh timestamp cannot be forged without the signing secret.
{ "error": { "code": "invalid_signature",
"message": "signature mismatch",
"details": { "reason": "timestamp_skew" } } }
Every POST accepts an Idempotency-Key. Relay fingerprints the request and its response, keyed per partner and per endpoint:
idempotency_conflict (409).Idempotency makes network retries safe without opening a replay hole, complementing the timestamp-bound signature.
Outbound webhook bodies are signed with the per-webhook secret returned when the endpoint is registered, using the same scheme:
X-Relay-Signature = HMAC-SHA256(webhook_secret, timestamp + "." + raw_webhook_body)
On every delivery you must:
hmac.compare_digest or equivalent).X-Relay-Timestamp is stale (> 300s).Each webhook has its own secret, so rotating or deleting one endpoint's secret never affects another.
GET /v2/tracking/{tracking} is intentionally lighter so it can back customer-facing tracking pages: it requires the API key only and no signature. It exposes only shipment status and scan history for that tracking number โ never full addresses, pricing, partner metadata, or other shipments. Tracking numbers are high-entropy and unguessable.
Each partner key is limited (default 100 requests/minute per environment). Exceeding it returns 429 with rate_limited and a Retry-After header โ a clear, machine-readable backoff signal. Limits can be raised per partner by arrangement.
A partner may also provide a list of source IPs/CIDRs. When set, Relay rejects that key's requests from any other address before doing any work. Allowlisting is recommended for production keys.
X-Api-Key on every request; use the environment-matched key.X-Relay-Timestamp + X-Relay-Signature over timestamp + "." + raw_body.Idempotency-Key to every POST and reuse it on retries.