Skip to main content

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/5xx response or on a network error.
  • Sent to your webhook_url using your webhook_method (POST or PUT).
  • 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.

There is no job.created on this path

An 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 fires

scheduled 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:

eventTypeWhen it firesTerminal
job.createdThe Job was accepted and is waiting for its print_at — scheduled Jobs only
job.queuedThe Job is queued to print. First callback on an immediate Job
job.storedThe Job's data has been stored
job.processingThe receipt is being prepared
job.processedThe receipt is prepared and ready for the printer
job.downloadedThe printer has collected the receipt
job.printedThe receipt printed●
job.expiredThe Job was not printed inside its expiration window●
job.errorThe Job failed●
job.cancelledA 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.created is not a general "job was created" callback. It fires only for a Job born scheduled. An immediate Job's first callback is job.queued.
  • job.scheduled is not an event type. scheduled appears as a state value, never as an eventType.

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.

  • Create job — setting webhook_url and webhook_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