Webhooks (beta)
Receive signed event notifications on your own endpoint the moment something happens on the Printeers platform, instead of polling the API.
Webhooks are in beta. They are fully functional and you can build on them today, but while we process feedback from the first users we may still refine the contract.
Looking for the single order-updated callback URL that predates this system? That mechanism keeps working unchanged and is documented in the legacy webhook notification guide. New integrations should use the webhooks described here.
A webhook endpoint is an HTTPS URL on your side that Printeers calls with an HTTP POST whenever an event you subscribed to occurs. The payload tells you what happened and to which resource; you then fetch the current state through the API. The payload format and the available event kinds are documented in the Webhooks reference.
The first available event kinds are shipment.created, fired when a shipment is created for one or more of your orders, and order.canceled, fired when an order is fully canceled. More kinds will follow, but your endpoint only receives the kinds it is subscribed to, so new kinds are never delivered unannounced.
Creating an endpoint
Webhook endpoints are managed in the Dashboard, under Settings. An endpoint belongs to your organization, not to a single store: one endpoint receives the events of all your stores for the event kinds it is subscribed to. If you route per store, read the references in the payload and dispatch on your side.
When you create an endpoint you choose which event kinds it is subscribed to, and the Dashboard shows you the endpoint's signing secret. Store the secret in your platform's secrets store; you need it to verify deliveries.
You can create multiple endpoints, each with its own URL, subscriptions, and secret, for example one per environment or one per consuming system.
The event payload
Deliveries carry a deliberately thin JSON envelope: the event reference, the kind, when it happened, and a subject object holding the reference to the entity the event is about, keyed by the kind's subject prefix (subject.shipment for shipment.* kinds, subject.order for order.* kinds), plus a human-readable message for display only. It does not carry the subject's state, because that state may already have changed by the time you process the delivery.
On receipt, fetch the current state from the API using the reference in the payload. For a shipment.created event, call GET /shipments/{shipmentReference} with subject.shipment.reference to retrieve the tracking details and the shipped items. This way you always act on the latest state, no matter when or in what order deliveries arrive.
The exact envelope schema and each event kind are documented in the Webhooks reference. Unrecognized payload fields must be ignored: fields can be added at any time. The message field is for humans; its wording can change without notice, so never parse it.
Verifying signatures
Every delivery is signed following the Standard Webhooks specification. Three headers accompany each delivery:
| Header | Meaning |
|---|---|
webhook-id |
The event reference (Event_...). Identical across retries; use it to deduplicate. |
webhook-timestamp |
Unix timestamp (seconds) of this delivery attempt, refreshed per retry. |
webhook-signature |
v1, followed by the base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with your endpoint secret. |
Verify the signature before trusting a delivery, and always verify against the raw request body bytes, not a re-serialized version of the parsed JSON. The easiest way is an off-the-shelf Standard Webhooks library, available for most languages. For example, in Node.js:
import { Webhook } from "standardwebhooks";
const webhook = new Webhook(endpointSecret); // "whsec_..."
// Throws when the signature is invalid or the timestamp is too old.
const event = webhook.verify(rawBody, {
"webhook-id": req.headers["webhook-id"],
"webhook-timestamp": req.headers["webhook-timestamp"],
"webhook-signature": req.headers["webhook-signature"],
});
The webhook-signature header can contain multiple space-separated signatures. The delivery is authentic if any one of them verifies; the standard libraries handle this for you.
Rotating a secret
You can rotate an endpoint's secret in the Dashboard. After a rotation the previous secret stays valid for 24 hours: during that grace period every delivery is signed with both the new and the previous secret, so you can deploy the new secret on your side without dropping deliveries. After the grace period only the new secret's signature is sent.
Delivery and retries
- Respond with any
2xxstatus code to acknowledge a delivery. Anything else, including timeouts and connection failures, counts as a failed attempt. - Acknowledge fast: record the delivery, respond, and do your API fetch afterwards, out of band.
- Failed deliveries are retried with increasing backoff, up to 25 attempts spread over up to 8 days. After that the delivery is permanently failed.
- Delivery is at-least-once: the same event can arrive more than once, and the same event is delivered to every endpoint subscribed to its kind. Deduplicate by the
webhook-idheader. - Deliveries are unordered: a retried event can arrive after a newer event. Use the event's
createdtimestamp to order them, or simply fetch current state on every delivery.
The Dashboard shows a delivery log per endpoint with every attempt, its response status, and the response your endpoint returned, so you can see exactly what was delivered and debug failures on your side. Log entries are kept for 90 days; captured response bodies are kept for 10 days.
Failing endpoints are disabled automatically
If an endpoint keeps failing, Printeers disables it: an endpoint is automatically disabled once it has failed 10 or more consecutive attempts sustained over 3 days. A short outage never disables an endpoint.
A disabled endpoint receives nothing: events that occur while an endpoint is disabled are not delivered to it and not queued for later. Once your endpoint is fixed, re-enable it in the Dashboard; delivery resumes with new events from that point on. Fetch anything you missed through the API.
