Skip to main content
Instead of polling List orders, your key can have a webhook: an https URL that gets a signed POST when something happens on your account. What each event carries: Webhook events.

Setup

We set the webhook up for you. Contact us with your key’s name and your URL, and we give you a signing secret, once. It starts whsec_. Keep it in a secret manager, like your API key.
  • The URL has to be https on port 443, on a public hostname: no IP addresses and no private or internal addresses. We check it when we set it up and again on every delivery.
  • The key has to be linked to an account and hold read:account, because events carry what your order history shows. shipment.updated also needs ship:cards.
  • One webhook per key. Events start when the webhook is set up, and nothing earlier is replayed.
To change the URL, pause the webhook, or get a new secret, contact us:
  • Pausing holds events, and turning it back on sends what happened meanwhile. Disabling the key does the same.
  • A new secret replaces the old one at once, with no overlap. Deliveries fail your check until you install it, and retry until then.
  • Linking the key to another account stops the webhook until we remove it and set it up again, so one account’s events never reach another’s URL.

What you receive

We look for new events every 2 minutes, or up to 18 minutes apart at our busy times, and longer while a webhook is paused.

Answer quickly

Return any 2xx within 5 seconds, and do slow work after you’ve answered. Anything else counts as a failure, including a redirect, which we don’t follow. We never read the response body.

Check the signature

Every request carries X-CatchBack-Signature: t=<unix seconds>,v1=<hex>. v1 is the HMAC-SHA256 of <t>.<raw request body>, keyed with your whole secret, whsec_ included.
  1. Take the raw bytes of the body, from before any JSON parsing. A framework that parses JSON for you throws them away, so read the body first.
  2. Compute the HMAC and compare it to v1 in constant time.
  3. Refuse a t more than 5 minutes from your clock. This stops a replay. Each retry carries a new t, so a retry never ages out.
In Python, raw_body is bytes.

Test your code

Run your check against this vector, with your clock check off or with your clock set to t (the functions above take the current time as their last argument). It should pass, and fail if you change one character of the body.

Retries

A failure is retried on this schedule, counted from the failure before it: These are the shortest waits: retries go out when we next look for events. That’s about 45 hours in all. After the eighth attempt the event stops and shows as failed on our side. Contact us and we can send failed events again. If your endpoint is down, answers 429 or a 5xx, or doesn’t answer, we stop sending that run’s other events, so one outage doesn’t use up the attempts on everything queued behind it. List orders always has every finished order and List shipments the current statuses, so reconcile against them after an outage.

Duplicates and order

Delivery is at-least-once and unordered. The same event can arrive twice, and a later event can arrive first.
  • Dedupe on id. It never changes for an event, including across retries.
  • Don’t rely on order. For two events about one shipment, the later status_updated_at is the newer.