Skip to content

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.

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.


  1. What is a webhook?
  2. When to use webhooks
  3. Creating a webhook
  4. Field reference
  5. Events you can subscribe to
  6. Payload reference
  7. Authentication (API key)
  8. Workstation scoping
  9. Testing a webhook
  10. Worked examples
  11. Logs and troubleshooting
  12. Security notes

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.


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.

  1. Go to App Settings → Webhooks.
  2. 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.
  3. 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.


FieldDescription
SourceThe 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 URLThe 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).
EventsOne 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.

Each webhook fires only for events you’ve checked. You can tick as many as you like.

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.

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.

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.


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.

{
"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
}
FieldMeaning
eventThe event key — one of the six listed above.
job_refThe job’s reference number on your Board.
order_refThe parent order’s reference, if any.
order_external_idThe external ID of the order (e.g., a Shopify order ID).
manufactured_qtyTotal quantity manufactured for this job across all workstations. (For time_log_saved, this is the workstation’s total, not the job’s.)
skuThe job’s SKU.
nameThe job’s name.
external_idThe job’s external ID (e.g., the upstream system’s identifier).
departmentDepartment of the workstation involved, if a workstation is in context.
workstationName of the workstation involved.
workstation_idNumeric 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).

EventExtra fields
qty_changed
time_log_savedmode ("manual" or "work_stopped"), log_qty, log_note, duration_minutes, employee (object with id, name, surname)
job_blockedblock_note
job_completedstatus (completion status name), total_duration_minutes
task_startedstarted_at (ISO 8601 timestamp)
task_completedcompleted_at (ISO 8601 timestamp)
{
"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"
}
{
"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" }
}

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.


By default, a webhook fires for every workstation. The optional Workstations field lets you restrict it.

Webhook fires for all workstations. This is the original behaviour — existing webhooks created before this feature are unaffected.

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.

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).

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.


The webhooks table has a paper-plane (✈) icon next to each row — click it to open the test dialog.

  1. Pick an event from your webhook’s subscribed events.
  2. A sample payload is rendered as JSON. Edit it freely — change any field to simulate a real scenario.
  3. 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
  4. 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.


  • Source: All sources
  • URL: a Slack incoming webhook URL
  • API Key: empty
  • Events: job_blocked only
  • 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.

  • 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.

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.


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>.log

Each 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.

  • 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.

  • 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.