Create an endpoint
In the dashboard, open Webhooks, choose Add endpoint, enter your URL and pick the events you want. Or call the API with a key that has theadmin scope:
secret, which starts with whsec_. It is shown once and never again, so store it in your secret manager now. If you lose it, delete the endpoint and create a new one.
Each endpoint belongs to one mode, the mode of the key that created it (or of the dashboard test mode toggle). It receives only events of that mode.
URLs must be https: for live endpoints, must not contain a username or password, and must resolve to a public address. The check runs when you create or change an endpoint and again before every send, so an address that later points at a private network is refused. A URL that fails answers 422 invalid_url.
Event types
Subscribe an endpoint only to the events you handle. See Printing PDFs for what each job state means.
Payload
Every request body is a JSON envelope:id identifies the event and is the same on every retry and replay. timestamp is when the event happened and mode is live or test. For printjob.* events, data is the print job object as GET /v1/printjobs/{id} returns it (the example is shortened). For computer.* events, data holds the computer id with its name and state. For printer.* events, data holds the printer id and the computerId it belongs to, with the details of the change.
Headers
Verify signatures
Always verify the signature before you trust a request. The SDKs check the signature and reject timestamps more than 300 seconds from now, which stops replayed requests.base64(HMAC-SHA256(key, "{webhook-id}.{webhook-timestamp}.{body}")), where key is the base64 decoded part of the secret after whsec_. Compare the result with each v1, entry of webhook-signature (the header can hold several, separated by spaces) using a constant time comparison, and reject timestamps more than 300 seconds from now.
Retries
Attempt 1 is sent immediately. A delivery succeeds when your endpoint answers with any 2xx status within 10 seconds. Anything else (another status, a timeout, a connection error) is a failure, and PrintEasy waits and tries again:
Each wait varies by plus or minus 20 percent (jitter). If attempt 9 also fails, the delivery is marked
failed. A delivery is pending before its first send, retrying between attempts, succeeded after a 2xx and failed when attempts run out.
Respond quickly and do the real work afterwards, for example by putting the event on a queue. Redirects are not followed.
Auto-disable after 5 days of failures
PrintEasy records when an endpoint started failing and clears that mark on any success. An endpoint whose deliveries keep failing for 5 days is disabled with the reasonDisabled automatically after 5 days of failed deliveries, and every organization owner gets an email. Fix your receiver, then re-enable the endpoint in the dashboard (or PATCH /v1/webhooks/{id} with {"enabled": true}). Events produced while the endpoint was disabled are not sent to it; use the event feed to catch up.
Replays and duplicates
The delivery log in the dashboard (andGET /v1/webhooks/{id}/deliveries) shows every attempt with its status code and the start of the response. Replay a delivery from the dashboard or with POST /v1/webhooks/deliveries/{id}/replay. A replay sends the same event again with the same webhook-id. Replays need an enabled endpoint.
Delivery is at least once, so a request can arrive more than once, and events can arrive out of order. Store the webhook-id values you have handled and skip repeats. Use the job state in data to ignore an older state that arrives after a newer one. Delivery logs are kept for 30 days.
Test mode endpoints
An endpoint created with a test key, or while the dashboard is in test mode, receives only test events, and may use anhttp: URL (for example a tunnel to your laptop). Use it with the virtual printers in Test mode to exercise your receiver end to end.
Event feed alternative
If you cannot expose an endpoint, pollGET /v1/events. It returns the events of your key’s mode, a page at a time. Use dir=asc with after set to the nextCursor of your previous call to read new events in order. Make the first call without after (an empty after= is rejected with 400):
nextCursor you got back as after on each later call:
data, hasMore and nextCursor. When a page is empty, nextCursor is the after you sent, so you can keep polling with the same value.
With dir=asc, an event shows up in the feed about 2 seconds after it happens. Keep after at the last event id you processed and poll again; do not skip ahead to a newer id, or you could miss an event that appears a moment later.