Skip to main content

Webhooks

When something happens to a work order or feedback the integration may read, kommuns sends a signed POST to the integration's webhook addresses, so your system knows without asking.

Setting up​

In Organization › Integrations, the integration's Webhooks: New webhook, its address (https://, reachable from the internet) and the event types it should get. Copy the signing secret shown once. Send test posts a ping and shows the answer; each address's deliveries are listed with Redeliver. An integration can have up to 5 addresses.

Events​

TypeWhenExtra fields in data
work_order.createdA work order was created
work_order.updatedTitle, description, priority or target date changedfields
work_order.status_changedAny status changeprevious_status, status
work_order.completedCompletedprevious_status, status
work_order.sent_to_inspectionCompleted and waiting for the consultantprevious_status, status
work_order.cancelledCancelledprevious_status, status
work_order.assignees_changedAssignees added / removedadded, removed
issue.createdFeedback was opened
issue.resolvedFeedback was resolved
pingSend testmessage

A completion sends both work_order.status_changed and work_order.completed — subscribe to the ones you need. An address gets an event only if the integration may read that work order or feedback (scopes and permissions).

The request​

POST /your/webhook
Content-Type: application/json
Kommuns-Event-Id: 3f0c…
Kommuns-Event-Type: work_order.completed
Kommuns-Signature: t=1760000000,v1=5d41402abc4b2a76b9719d911017c592…

{"id": "3f0c…", "type": "work_order.completed", "created_at": "2026-10-05T12:00:00Z",
"organization_id": "…",
"data": {"work_order": {"id": "…", "title": "…", "status": {"code": "complete", "name": "Completed"}, "…": "…"},
"previous_status": "in_progress", "status": "complete", "actor": {"identity_id": "…"}}}

data.work_order / data.issue is the record as GET /work-orders/{id} / GET /issues/{id} shows it when the event is delivered (names in English). data.actor is who made the change (identity_id; for a resolved feedback user_id) — compare it with your integration's identity_id from GET /me to skip your own changes.

Answer 2xx within 10 seconds; do the work afterwards (a queue). Redirects are not followed.

Verifying the signature​

Kommuns-Signature is t=<unix time>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Compute it over the raw body (before parsing JSON), compare in constant time and refuse a time older than 5 minutes.

import hashlib
import hmac
import time

def verified(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
parts = dict(item.split("=", 1) for item in header.split(","))
timestamp, signature = int(parts["t"]), parts["v1"]
if abs(time.time() - timestamp) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)

Delivery​

  • At least once: the same event (Kommuns-Event-Id) may arrive twice — keep the ids you processed.
  • Order: usually in the order of the changes, but not guaranteed; use changed_at / the record itself.
  • Retries: a failed delivery (not 2xx, a timeout) is tried again after 1 and 5 minutes, 30 minutes, 2, 6, 12 and 24 hours (8 attempts in about two days); then it is marked failed. You can Redeliver it from the deliveries list.
  • Turning off: when a delivery fails for good and the address has had no successful delivery for 3 days, the address is turned off; the page shows why. Fix the receiver, turn it on again and redeliver.
  • Events are kept 30 days. For anything missed, sync with changed_since.