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
- The filter runs on the server: only tasks of the own broadcast ids are returned.
withMedia=truedelivers the PDFs directly, no second request per task.withAudit=truedelivers the creation time for the lead time statistics.- If more pages exist, the next page is requested immediately instead of after the interval.
The WebSocket connection
- Pusher protocol 7 against Laravel Reverb, private channel
private-report-print-tasks, authorized withPOST /api/v1/broadcasting/authand the API token. - The event
report-print-task.createdcarries{ model, id, broadcastId }. If the broadcast id matches, the service loads the task withGET /api/v1/report-print-task/{id}?withMedia=true. - Keep-alive: every
pusher:pingof the server is answered withpusher:pong. After 25 s without traffic the service sends its own ping and drops the connection if no pong arrives within 30 s. - 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.
- 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.
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 WebSocket | 2 (GET task, set-printed) |
| Task received by poll | 1 (set-printed) plus its share of the poll |
| Poll | 1 per page |
| Error report | 1 (set-status) |
| WebSocket login | 1 (broadcasting/auth) |
- 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. - The service does not slow down in advance. A low remaining count is only displayed.
- 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.
- 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_MINUTEin the.envof your VeloxFactory instance.
Processing
Receiver -> queue -> journal check -> worker (max. maxParallelJobs) -> printer lock -> queue check -> print -> outbox
- 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.
- 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.
- 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. - Printer lock. With
serializePerPrinteronly one job runs per printer, ordered by task id. Jobs for different printers run in parallel up tomaxParallelJobs. - Queue check. Before printing, the Windows queue is checked. Not installed fails immediately. Offline, paper problem or error goes into retry without printing.
- 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. - Confirm. With
confirmation = Spooledthe task is confirmed as soon as the spooler accepted it. WithCompletedthe service waits until the spooler job has finished (at mostprintTimeoutSeconds).
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.
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 |
|---|---|---|
Received | Received | Taken, waiting or in retry |
Printing | Printing | Print call running |
Spooled | Spooled, not confirmed | In the spooler, confirmation pending. Never printed again. |
Confirmed | Printed | VeloxFactory shows the task as printed |
Failed | Failed | Failed, error report pending |
ErrorReported | Error reported | VeloxFactory shows the task as error |
Unknown | Unknown | The 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. |
DryRun | Dry run | Written 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.
- 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. - 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.
- New tasks stay pending in VeloxFactory.
- On resume the service polls immediately and prints everything that arrived meanwhile.
- 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.