Webhooks
Webhooks
Section titled “Webhooks”Webhooks let your Board send HTTP requests to external services whenever certain events happen during production — a quantity is logged, a task is started, a task is blocked, a job is completed, and more. This is how you connect your Board to Slack, Microsoft Teams, an ERP, a notification platform, a custom script, or anything else that can receive an HTTP request.
Webhooks live under App Settings → Webhooks.

Note: Webhooks must be enabled for your account. If the page shows a notice saying webhooks are disabled, contact support to turn them on.
Table of contents
Section titled “Table of contents”- What is a webhook?
- When to use webhooks
- Creating a webhook
- Field reference
- Events you can subscribe to
- Payload reference
- Authentication (API key)
- Workstation scoping
- Testing a webhook
- Worked examples
- Logs and troubleshooting
- Security notes
What is a webhook?
Section titled “What is a webhook?”A webhook is a URL you give to your Board. Whenever an event you’ve subscribed to occurs, the Board sends an HTTP POST request to that URL with a small JSON payload describing what happened.
The other end is your choice — a Slack incoming webhook URL, a Zapier “Catch Hook” trigger, an endpoint on your own server, an automation platform, anything that can receive HTTP and parse JSON.
Webhooks are one-way and fire-and-forget — the Board doesn’t wait for a meaningful response, it just records the status code for diagnostics. If your endpoint is slow or temporarily down, the Board doesn’t retry automatically.
When to use webhooks
Section titled “When to use webhooks”Some real-world use cases:
- Notifications — post a message in Slack/Teams whenever a task is blocked, a job is completed, or a high-priority task starts on a specific workstation.
- ERP / accounting sync — let your back-office system know quantities have been manufactured so inventory and invoicing can update.
- Custom dashboards — feed an external Grafana, Looker, or homemade dashboard with production data in real time.
- Triggering other automations — kick off shipping label printing, packaging instructions, or quality-control workflows when a job hits a completed status.
- Audit / compliance logging — stream every quantity change or time log to an external archive.
Creating a webhook
Section titled “Creating a webhook”- Go to App Settings → Webhooks.
- Fill in the form at the top of the page:
- Source — which order source should trigger this webhook (or All sources).
- Webhook URL — the URL your external service exposes.
- API Key (optional) — an authentication token (see Authentication).
- Events — tick one or more events you want to receive.
- Workstations (optional) — pick specific workstations to scope to, or leave empty to fire for all workstations.
- Click Add (or Update if editing an existing webhook).
The new webhook appears in the table below. You can edit it any time, delete it, or send a manual test request.
Field reference
Section titled “Field reference”| Field | Description |
|---|---|
| Source | The order source the webhook applies to. Pick All sources to receive events for orders from any source, or pick a specific source key (manual, shopify, etc.) to only fire when the job’s order came from that source. |
| Webhook URL | The full HTTPS URL the Board should POST to. Maximum 2000 characters. |
| API Key (optional) | If set, the Board sends it on every request in two headers — Authorization: Bearer <key> and X-API-Key: <key>. Use this if your endpoint requires authentication. Leave empty for public endpoints (e.g., Slack incoming webhooks). |
| Events | One or more events that trigger this webhook. See Events. |
| Workstations (optional) | A list of workstations this webhook is scoped to. Empty = fire for all workstations. See Workstation scoping. |
Events you can subscribe to
Section titled “Events you can subscribe to”Each webhook fires only for events you’ve checked. You can tick as many as you like.
qty_changed — Quantity changed
Section titled “qty_changed — Quantity changed”Fires every time a quantity is recorded against a workstation on any job — through the dashboard, the Kiosk, the mobile app, or the API. The payload includes the workstation, the total manufactured so far for the job, and identifying fields about the job and order.
Example use: feed an ERP system in real time with manufacturing progress.
time_log_saved — Time log saved
Section titled “time_log_saved — Time log saved”Fires whenever a time-tracking log entry is created — either manually via the Kiosk “Submit” button, or when a timer is stopped. The payload includes the duration in minutes, the employee, the workstation, any note, and the quantity (if any) that was logged at the same time.
Example use: log productivity to a billing/payroll system.
job_blocked — Task blocked
Section titled “job_blocked — Task blocked”Fires when an employee marks a task as blocked (for example, “missing material”, “machine down”). The payload includes the block note explaining the reason.
Example use: post an immediate alert to Slack so a supervisor can investigate.
job_completed — Task completed (whole job)
Section titled “job_completed — Task completed (whole job)”Fires when an entire job is completed — i.e., when the job’s status transitions to the configured completion status (either manually changed, or automatically when every workstation reaches its planned quantity). The payload includes the total duration in minutes across all time-tracking entries on the job.
Example use: trigger a shipping label or invoicing workflow once a job is fully done.
task_started — Task started at workstation
Section titled “task_started — Task started at workstation”Fires once per workstation per job, the first time activity is recorded on it. Activity means either the first quantity > 0 is logged, or the first time-tracking timer is started on that workstation — whichever comes first. The payload includes a started_at ISO timestamp.
Example use: notify a supervisor that work has begun on a critical step, so they know production has actually started.
task_completed — Task completed at workstation
Section titled “task_completed — Task completed at workstation”Fires once per workstation per job, the first time that workstation reaches its planned quantity (or for shared-quantity departments, the moment the pooled department quantity meets the job’s total). The payload includes a completed_at ISO timestamp.
Example use: trigger the next-step downstream system (packaging, QA, shipping) the moment a specific production stage finishes.
Idempotency for
task_started/task_completed: each is recorded permanently on the workstation row, so it fires only the first time the condition is met. Logging more quantity or restarting a timer after the first hit will not refire these events.
Payload reference
Section titled “Payload reference”Every event sends a JSON object via HTTP POST. Some fields are common across all events; others are specific to the event type. Empty/null fields are omitted to keep payloads compact.
Base fields (present for all events)
Section titled “Base fields (present for all events)”{ "event": "qty_changed", "job_ref": "PB-0042", "order_ref": "PO-2025-001", "order_external_id": "EXT-12345", "manufactured_qty": 7, "sku": "TABLE-OAK-90", "name": "Oak Coffee Table", "external_id": "ORD-7890", "department": "Assembly", "workstation": "Station A", "workstation_id": 12}| Field | Meaning |
|---|---|
event | The event key — one of the six listed above. |
job_ref | The job’s reference number on your Board. |
order_ref | The parent order’s reference, if any. |
order_external_id | The external ID of the order (e.g., a Shopify order ID). |
manufactured_qty | Total quantity manufactured for this job across all workstations. (For time_log_saved, this is the workstation’s total, not the job’s.) |
sku | The job’s SKU. |
name | The job’s name. |
external_id | The job’s external ID (e.g., the upstream system’s identifier). |
department | Department of the workstation involved, if a workstation is in context. |
workstation | Name of the workstation involved. |
workstation_id | Numeric ID of the workstation involved. |
The department, workstation, and workstation_id fields are present whenever the event relates to a specific workstation (so all events except job_completed, which is whole-job).
Per-event extras
Section titled “Per-event extras”| Event | Extra fields |
|---|---|
qty_changed | — |
time_log_saved | mode ("manual" or "work_stopped"), log_qty, log_note, duration_minutes, employee (object with id, name, surname) |
job_blocked | block_note |
job_completed | status (completion status name), total_duration_minutes |
task_started | started_at (ISO 8601 timestamp) |
task_completed | completed_at (ISO 8601 timestamp) |
Example: task_started payload
Section titled “Example: task_started payload”{ "event": "task_started", "job_ref": "PB-0042", "order_ref": "PO-2025-001", "manufactured_qty": 0, "sku": "TABLE-OAK-90", "name": "Oak Coffee Table", "department": "Assembly", "workstation": "Station A", "workstation_id": 12, "started_at": "2026-05-27T08:14:32+00:00"}Example: time_log_saved payload
Section titled “Example: time_log_saved payload”{ "event": "time_log_saved", "job_ref": "PB-0042", "manufactured_qty": 5, "sku": "TABLE-OAK-90", "name": "Oak Coffee Table", "department": "Assembly", "workstation": "Station A", "workstation_id": 12, "mode": "work_stopped", "log_qty": 2, "log_note": "Two units finished, third in progress", "duration_minutes": 47, "employee": { "id": 8, "name": "Jan", "surname": "Kowalski" }}Authentication (API key)
Section titled “Authentication (API key)”If your endpoint requires authentication, fill in the API Key field. The Board sends it on every request in two headers:
Authorization: Bearer <your-api-key>X-API-Key: <your-api-key>Most endpoints accept either form, so it doesn’t matter which one you check on the receiving side.
Leave API Key empty for public endpoints like Slack incoming webhooks — they don’t expect any auth header.
Workstation scoping
Section titled “Workstation scoping”By default, a webhook fires for every workstation. The optional Workstations field lets you restrict it.
Empty list (default)
Section titled “Empty list (default)”Webhook fires for all workstations. This is the original behaviour — existing webhooks created before this feature are unaffected.
Specific workstations
Section titled “Specific workstations”Webhook fires only when the event’s workstation is in the list. Useful when:
- You want a Slack channel to receive alerts only for the painting team’s workstations.
- You want an ERP integration that only cares about final-assembly workstations.
- Different teams should receive different notifications.
Behaviour with non-workstation events
Section titled “Behaviour with non-workstation events”The job_completed event is fired at the whole-job level (no specific workstation). A webhook with one or more workstations selected will not fire for job_completed — by design. If you want a webhook to receive job_completed, leave the workstation list empty (or create a second webhook just for that event).
Combined with Source
Section titled “Combined with Source”The workstation filter is applied on top of the source filter. A webhook with source=shopify and workstations [A, B] only fires when the event matches both — a Shopify-source job event happening on workstation A or B.
Testing a webhook
Section titled “Testing a webhook”The webhooks table has a paper-plane (✈) icon next to each row — click it to open the test dialog.
- Pick an event from your webhook’s subscribed events.
- A sample payload is rendered as JSON. Edit it freely — change any field to simulate a real scenario.
- Click Send test. The dialog switches to a result panel showing:
- HTTP status code returned by your endpoint
- Time taken (in milliseconds)
- Response body (truncated to 2 KB)
- Any connection error
- Click Back to edit to tweak the payload and try again.
This is the fastest way to verify your endpoint is reachable, parses the payload correctly, and responds with a 2xx status.
Worked examples
Section titled “Worked examples”1. Slack alert when any task is blocked
Section titled “1. Slack alert when any task is blocked”- Source: All sources
- URL: a Slack incoming webhook URL
- API Key: empty
- Events:
job_blockedonly - Workstations: empty
In Slack, the payload arrives as JSON; you can format it nicely with a relay (e.g., a Cloudflare Worker that turns block_note + workstation into a Slack message). Or use a service like Zapier between the two to do the formatting.
2. Email a supervisor when the paint shop finishes a job
Section titled “2. Email a supervisor when the paint shop finishes a job”- Source: All sources
- URL: a Zapier “Catch Hook” URL connected to a Gmail “Send Email” step
- Events:
task_completed - Workstations: tick all paint-shop workstations
The webhook fires the moment the paint shop’s portion of any job is done. Zapier formats the email from the payload fields.
3. Push manufactured quantities to an ERP
Section titled “3. Push manufactured quantities to an ERP”- Source: a specific external source (e.g.,
shopify) - URL: your ERP’s inbound endpoint
- API Key: the ERP’s API token
- Events:
qty_changed,job_completed - Workstations: empty (you want all of them)
The ERP receives a payload after every quantity change plus a final whole-job notice when the job is fully complete.
4. Different notifications per department
Section titled “4. Different notifications per department”Create three separate webhooks, each scoped to its own department’s workstations:
- “Assembly Slack” →
task_started,task_completed,job_blocked, workstations = assembly stations only - “Paint Slack” → same events, workstations = paint-shop stations only
- “Packing Slack” → same events, workstations = packing stations only
Each team only sees alerts that matter to them.
Logs and troubleshooting
Section titled “Logs and troubleshooting”Every webhook request — both real events and manual tests — is appended to a per-tenant log on the server at:
storage/logs/webhooks/webhook_tennent_id_<tenant_id>.logEach line is a JSON object with the timestamp, event, URL, HTTP status, duration, payload, response body, and any error. This is the first place to look when a webhook seems not to be delivering.
Common issues
Section titled “Common issues”- Endpoint returns 4xx / 5xx — check the response body in the log. The Board only stops sending on the next event if the URL is removed; it doesn’t retry the current event automatically.
- No log line at all for an event — your webhook isn’t matching. Verify the source, events, and workstations filters. The Test dialog bypasses these filters and is the fastest way to confirm the URL works.
- Slow endpoint — the Board’s request timeout is 10 seconds. If your endpoint takes longer, the connection drops and an error is logged.
- HTTPS errors — make sure your endpoint has a valid certificate. The Board does not allow self-signed certificates.
- Tenant flag disabled — the page shows a disabled notice if webhooks are turned off for your account. Contact support.
Security notes
Section titled “Security notes”- The URL itself is the credential when no API key is set. Anyone who knows the URL of an unauthenticated webhook (e.g., a Slack incoming webhook) can post to it. Treat unauthenticated URLs as secrets and don’t share them publicly.
- Use the API Key field whenever your endpoint supports auth. It’s a free upgrade in security — the Board sends it with every request.
- HTTPS is required for any endpoint that handles real production data. The webhook URL field accepts
http://too, but only use it for local development. - Payloads include identifiable order/customer information. Make sure the receiving service is approved for handling that data — especially for external sources, third-party automation platforms, and any service that stores or logs the payloads.
- Webhooks are per-tenant. Events from one tenant’s data can never reach another tenant’s webhooks.