Webhooks
Every Job you create carries a webhook_url and a webhook_method (Create job). Eatabit calls that URL each time the Job's state changes, so your system learns what happened to an order without polling for it.
This page is the reference for those callbacks: what arrives on each path a Job can take, the full list of event types, and what a receiver has to get right.
Delivery contract
- One attempt per state change. Eatabit does not retry on a
4xx/5xxresponse or on a network error. - Sent to your
webhook_urlusing yourwebhook_method(POSTorPUT). - Headers:
Content-Type: application/vnd.api+json,User-Agent: eatabit.io APIv20. - Every attempt is recorded on the Job, whether it succeeded or failed, along with the status code your endpoint returned and any network error. Fetch that history from GET /v20/jobs/{jobId}.
The callback body is a thin state update — it names the state change and identifies the Job, and it deliberately does not repeat your orderData or the Job's event history. For the exact fields, see Callback body on the Create job page.
The Job lifecycle
Which callbacks you receive depends on the path the Job takes. The five paths below are the ones an integrator can be on.
The states are listed in the order a Job moves through them. Note that delivery order is not guaranteed — see Ordering and duplicates — so treat these as the sequence of events, not as a guaranteed sequence of arrivals.
Immediate print
A Job created without a print_at (or with one already in the past) prints as soon as it can. You receive:
job.queued → job.stored → job.processing → job.processed → job.downloaded → job.printed
job.printed is terminal: the receipt came out of the printer and the Job is finished.
job.created on this pathAn immediate Job never sends a job.created callback. Its first callback is job.queued. A receiver that waits for job.created before it starts tracking an order will never start tracking it.
Scheduled print
A Job created with a future print_at is held until that time. You receive:
job.created → (waits until print_at) → job.queued → job.stored → job.processing → job.processed → job.downloaded → job.printed
This is the only path on which job.created appears. It tells you the Job was accepted and is waiting.
job.scheduled never firesscheduled is a Job state, not an event type you will ever receive. The job.created callback for a scheduled Job carries state: "scheduled" — the state is in the body, but the event type is job.created. A receiver waiting for eventType === "job.scheduled" waits forever.
Cancellation
A scheduled Job can be cancelled before it fires with DELETE /v20/jobs/{jobId}. You receive:
job.created → job.cancelled
job.cancelled is terminal and fires once. The Job never prints, so no job.printed follows it.
Expiry
A Job that is not printed inside its expiration window expires instead. You receive the callbacks for however far it got, then:
job.expired
job.expired is terminal. It is the callback that tells you an order did not print. Treat it as an exception to surface to a human, not as a quiet end state.
Errors
A Job that fails while being prepared or delivered sends:
job.error
job.error is terminal. As with expiry, no print happened.
Event catalogue
All ten event types that fire a webhook:
eventType | When it fires | Terminal |
|---|---|---|
job.created | The Job was accepted and is waiting for its print_at — scheduled Jobs only | |
job.queued | The Job is queued to print. First callback on an immediate Job | |
job.stored | The Job's data has been stored | |
job.processing | The receipt is being prepared | |
job.processed | The receipt is prepared and ready for the printer | |
job.downloaded | The printer has collected the receipt | |
job.printed | The receipt printed | ● |
job.expired | The Job was not printed inside its expiration window | ● |
job.error | The Job failed | ● |
job.cancelled | A scheduled Job was cancelled before it printed | ● |
Terminal means no further callbacks for that Job. Every Job that sends callbacks at all ends on exactly one of job.printed, job.expired, job.error or job.cancelled.
Two things worth repeating if you have scrolled straight here:
job.createdis not a general "job was created" callback. It fires only for a Job born scheduled. An immediate Job's first callback isjob.queued.job.scheduledis not an event type.scheduledappears as astatevalue, never as aneventType.
Receiving webhooks
A missed callback is not resent
There is one attempt per state change. A 4xx or 5xx response, a connection failure, or a timeout on your side all end the attempt — nothing is queued for redelivery, and there is no retry schedule to wait out.
So a receiver that is down for ten minutes has permanently missed whatever happened in those ten minutes. The recovery path is to poll GET /v20/jobs/{jobId}, whose event history is authoritative and includes the state changes you did not receive. If printing outcomes are important to your business process, reconcile open orders against that endpoint on a schedule rather than trusting that every callback arrived.
Return a 2xx quickly and do your own work afterwards. Anything your endpoint does before responding — a slow database write, a downstream API call — is time the attempt can fail in.
Ordering and duplicates
- Order is not guaranteed. Callbacks for a Job are sent as its states change, but they can arrive out of order. Do not infer the current state from the order things arrived in.
- A callback can be delivered more than once. Handle deliveries idempotently — the same event type for the same Job should be safe to process twice.
Reconcile against your own record instead of against arrival order. Each callback carries an eventType, an updatedAt, and a state that is the Job's state as of that change — not necessarily its state right now. If you need the Job's current state, read it from GET /v20/jobs/{jobId}.
Callbacks are not signed
The request carries no signature and no shared secret, so you cannot verify from the request alone that it came from Eatabit. Anyone who learns your webhook_url can post to it.
Until signing exists, treat the callback as a notification rather than as trusted data:
- Use an unguessable
webhook_url. - Confirm anything that matters by reading the Job back from GET /v20/jobs/{jobId}, which is authenticated.
- Do not let a callback alone trigger an irreversible action — a refund, a re-send, a charge.
Timeout
The timeout is unspecified. No maximum response time is published, and you should not infer one from observed behaviour or design a receiver that depends on a particular limit. Respond as fast as you can and do slow work after responding.
Related
- Create job — setting
webhook_urlandwebhook_method, and the callback body - Get job — the authoritative event history, and the recovery path for a missed callback
- Cancel job — cancelling a scheduled Job before it prints