Skip to main content

Receiving and processing print tasks

This page follows a print task through the Print Service: how it arrives, which instance takes it, how it is printed, retried and confirmed, and what protects it against being printed twice.


Receive modes

Mode Behaviour Use it when
Hybrid (default) WebSocket events, plus a safety poll every safetyPollIntervalSeconds (120 s). While the WebSocket is down it polls every pollingIntervalSeconds (30 s) until the reconnect succeeds. Always, unless there is a reason for another mode
WebSocket Only events. Polls once at start and after every successful (re)subscribe, never periodically. Minimal API load
Polling Polls every pollingIntervalSeconds, no WebSocket Reverb is not reachable from the printer machine (firewall, proxy)

Why the safety poll

Reverb delivers an event once and does not keep it. An event sent during a short network interruption is lost, and a connection can look alive without delivering events. The safety poll finds such tasks at the latest after its interval. Tasks the journal already knows are skipped, so polling never prints twice.

The poll request

GET /api/v1/report-print-task?status=pending&perPage=10&page=1&withMedia=true
    &withRelations=false&withAudit=true&broadcastId=Standard,Warehouse&withoutBroadcastId=false
  1. The filter runs on the server: only tasks of the own broadcast ids are returned.
  2. withMedia=true delivers the PDFs directly, no second request per task.
  3. withAudit=true delivers the creation time for the lead time statistics.
  4. If more pages exist, the next page is requested immediately instead of after the interval.

The WebSocket connection

  1. Pusher protocol 7 against Laravel Reverb, private channel private-report-print-tasks, authorized with POST /api/v1/broadcasting/auth and the API token.
  2. The event report-print-task.created carries { model, id, broadcastId }. If the broadcast id matches, the service loads the task with GET /api/v1/report-print-task/{id}?withMedia=true.
  3. Keep-alive: every pusher:ping of the server is answered with pusher:pong. After 25 s without traffic the service sends its own ping and drops the connection if no pong arrives within 30 s.
  4. Reconnect with exponential backoff from 1 s to 60 s, ±20 % jitter. After every successful subscribe a catch-up poll fetches what was created in the meantime.
  5. A first connection failure is logged as warning. An outage longer than 5 minutes writes event id 3000.

Broadcast ids: which instance prints what

Every print task and every printer in VeloxFactory can carry a broadcast id, usually one per site or printer group. An instance only takes tasks whose broadcast id is in its receive.broadcastIds, both from events and from polls.

Tasks without broadcast id are taken only with includeWithoutBroadcastId. VeloxFactory sends no WebSocket event for them, so they arrive only by polling: in Hybrid within safetyPollIntervalSeconds, in mode WebSocket only at start and after a reconnect. Give print tasks a broadcast id whenever they should print immediately.

⚠️ One instance per broadcast id. The job journal protects against double printing within one instance only. Two instances listening to the same broadcast id, or two instances with includeWithoutBroadcastId, print the same task twice.

Rate limit

The VeloxFactory API limits requests per IP address, by default 10 per minute (API_RATE_LIMIT_PER_MINUTE). The service reads the limit from X-RateLimit-Limit and the rest from X-RateLimit-Remaining in every response and shows both on the dashboard.

Action API calls
Task received by WebSocket2 (GET task, set-printed)
Task received by poll1 (set-printed) plus its share of the poll
Poll1 per page
Error report1 (set-status)
WebSocket login1 (broadcasting/auth)
  1. On HTTP 429 every API call of the process waits until Retry-After (60 s if the header is missing), then the same request is sent again. Print tasks wait, they do not fail. The API state shows Rate limited, event id 2002 is written.
  2. The service does not slow down in advance. A low remaining count is only displayed.
  3. Settings → Receiving shows the base load of the chosen mode in calls per minute (Hybrid with 120 s: 0.5) and warns when it exceeds half of the limit.
  4. The limit counts per IP. All print services, scripts and MCP clients behind the same NAT share it. For print environments raise API_RATE_LIMIT_PER_MINUTE in the .env of your VeloxFactory instance.

Processing

Receiver -> queue -> journal check -> worker (max. maxParallelJobs) -> printer lock -> queue check -> print -> outbox
  1. Journal check. A task the journal knows in state Printing or later is skipped. A task that was confirmed or reported before and is pending again (reprint) is loaded fresh; only if VeloxFactory really shows it as pending is it printed again.
  2. Fetch. Tasks from a WebSocket event are loaded now, tasks from a poll already carry their PDF. A task that no longer exists or is no longer pending is dropped.
  3. Printer. The queue name of the task is resolved to the local printer (alias, see Printers and printing). The Windows default printer is never used; a task without printer name fails with PrinterNotFound.
  4. Printer lock. With serializePerPrinter only one job runs per printer, ordered by task id. Jobs for different printers run in parallel up to maxParallelJobs.
  5. Queue check. Before printing, the Windows queue is checked. Not installed fails immediately. Offline, paper problem or error goes into retry without printing.
  6. Print. See Printers and printing. The spooler document name is VF #{id} {file name}, so the job can be found in the Windows print queue.
  7. Confirm. With confirmation = Spooled the task is confirmed as soon as the spooler accepted it. With Completed the service waits until the spooler job has finished (at most printTimeoutSeconds).

Retries

Retried (transient) Fails immediately (permanent)
Printer reports offline, paper problem or error (PrinterOffline) Printer not installed for the service account (PrinterNotFound)
Spooler refused to accept the job (SpoolerError) PDF missing, unreadable or not renderable (RenderFailed)
Print call or spooler job ran into the timeout, spooler job ended with an error (Timeout, SpoolerError)

With the defaults (retryCount 3, retryBackoffSeconds [5, 30, 120]) a task waits about 2.5 minutes before it is reported as failed. For label printers that are offline longer, for example during a roll change, use more retries or a longer last value, e.g. [5, 30, 120, 300, 600] with retryCount 5.

ℹ️ Once handed to the spooler, a job is never retried. A failure after that point (timeout, spooler error) is only reported. Retrying could print the document twice.

Error reports

Permanent failures are reported with PATCH /api/v1/report-print-task/{id}/set-status, status error and an errorMessage of the form [Category] message, shortened to 1,000 characters. Categories: PrinterNotFound, PrinterOffline, RenderFailed, Timeout, SpoolerError, Unknown. The task shows the message in VeloxFactory.


Job journal and outbox

VeloxFactory has no endpoint to claim a task. The local journal in state.db takes that role and records every task with its state:

State Shown as Meaning
ReceivedReceivedTaken, waiting or in retry
PrintingPrintingPrint call running
SpooledSpooled, not confirmedIn the spooler, confirmation pending. Never printed again.
ConfirmedPrintedVeloxFactory shows the task as printed
FailedFailedFailed, error report pending
ErrorReportedError reportedVeloxFactory shows the task as error
UnknownUnknownThe service ended while printing. Not printed again automatically, reported as error with Print state unknown after service restart, check printer and reset task if needed.
DryRunDry runWritten to the dry-run folder, not confirmed

Outbox. Confirmations and error reports are written to the outbox first and then sent. If the API is unreachable, the outbox retries with backoff (5 s, doubling, at most every 5 minutes) until it succeeds. If VeloxFactory answers 404 (task deleted) or 422 (rejected), the entry is dropped. After a crash, spooled tasks without outbox entry get their confirmation again. The dashboard tile Queue shows open outbox entries as to confirm.

Reprint. To print a task again, use Jobs → Print again in the window. It resets the task to pending in VeloxFactory (PATCH set-printed with false), the next poll prints it. Resetting the task in VeloxFactory itself has the same effect.


Pause processing

A pause stops printing without stopping the service. Use it for printer maintenance, a roll change or while wrong tasks are cleaned up in VeloxFactory.

  1. Set it on the dashboard (Pause / Resume) or under Settings → Processing with reason and optional end time. The pause is stored in config.json, so it survives a restart and a pause set in the window also pauses the headless engine.
  2. While paused: the WebSocket stays connected, events are noted but not processed, periodic polls are skipped. Tasks already in the pipeline wait before their next print attempt; the waiting time does not count as a retry. A print call already in progress is not interrupted. The outbox keeps delivering confirmations.
  3. New tasks stay pending in VeloxFactory.
  4. On resume the service polls immediately and prints everything that arrived meanwhile.
  5. The engine state is Paused with reason and end time. The health endpoint still answers 200.

Dry run

For tests and demonstrations without paper. With processing.dryRunFolder set (or --dry-run <folder> on the command line, which applies to that run only) every task is received, fetched and resolved as usual, but instead of printing the PDF is written to <folder>\<task id>_<file name>. Nothing is confirmed, the task stays pending in VeloxFactory.

⚠️ Dry-run tasks are not printed later. The journal marks them as Dry run, and this instance skips them after the dry run is switched off, although they are still pending in VeloxFactory. Use a test broadcast id for dry runs, or create new print tasks afterwards.