Webhook-Events

Webhooks anlegen, die Events, die kanman für Stories, Runs, Gates, Entscheidungen und Teams sendet, Signaturen prüfen sowie die Zustell- und Wiederholungsregeln.

Webhooks schicken eine signierte HTTP-Anfrage an Ihren Server, wenn in kanman etwas passiert: Eine Story wird bereit, ein Run endet oder schlägt fehl, ein Gate schlägt fehl, eine Entscheidung wartet auf eine Antwort, ein Teambericht erscheint. Damit posten Sie in Ihre eigenen Chat-Werkzeuge, aktualisieren ein Dashboard oder starten eigene Automatisierungen.

Webhooks gehören zum Workspace. Owner und Admins legen sie an und verwalten sie; Mitglieder sehen sie nicht.

Webhooks in den Workspace-Einstellungen (Desktop) Webhooks in den Workspace-Einstellungen (Mobile)

Webhook anlegen

  1. Öffnen Sie Einstellungen, Webhooks (/<workspace>/settings/webhooks).
  2. Klicken Sie auf Webhook hinzufügen.
  3. Geben Sie einen Name und die Endpoint-URL Ihres Servers ein.
  4. Wählen Sie unter Events, welche Events der Webhook erhalten soll. Sie sind gruppiert nach Stories, Runs und Pull Requests, Outcome Gate, Entscheidungen sowie Teams und Berichten. Ein neuer Webhook beginnt mit Run abgeschlossen, Run fehlgeschlagen oder gestoppt und Entscheidung wartet.
  5. Klicken Sie auf Webhook hinzufügen.
Formular zum Anlegen eines Webhooks (Desktop) Formular zum Anlegen eines Webhooks (Mobile)

kanman erzeugt für jeden Webhook ein Signatur-Secret (es beginnt mit whsec_). Mit Anzeigen sehen Sie es, mit Neu generieren ersetzen Sie es; Ihr Empfänger braucht es, um Signaturen zu prüfen. Der Schalter an jedem Webhook schaltet die Zustellung ein oder aus. Ein Klick auf den Namen eines Webhooks ändert Name, Endpunkt oder Events.

Events

Event Gesendet, wenn
story.created Eine Story in einem Team erscheint: Ein Issue wurde aus Ihrem Tracker übernommen, oder ein freigegebener Story-Entwurf wurde in den Tracker geschrieben.
story.ready Eine Story in die Spalte Bereit des Teams kommt.
run.started kanman mit der Arbeit an einer Story beginnt (auch bei jedem neuen Versuch).
run.stage_changed Ein Run in eine andere Phase wechselt: Planung, Umsetzung, Prüfung, Review, fertig.
run.completed Ein Versuch eines Runs seine Arbeit beendet.
run.failed Ein Versuch fehlschlägt, in ein Zeitlimit läuft oder gestoppt wird.
pr.opened kanman den Pull Request eines Runs öffnet.
gate.passed Ein Gate des Outcome Gates besteht, zum Beispiel die Clean-Room-Prüfung oder der Reviewer.
gate.failed Ein Gate fehlschlägt oder nicht laufen kann.
decision.created kanman eine Entscheidung von einem Menschen braucht.
decision.resolved Jemand eine Entscheidung beantwortet hat, in der App, in Slack, über den MCP-Server oder die REST-API.
report.published Der Wochenbericht eines Teams fertig ist.
team.paused Ein Team pausiert wird.
team.resumed Ein pausiertes Team fortgesetzt wird.
test Sie auf Test senden klicken. Nicht abonnierbar.

Format der Anfrage

Jede Zustellung ist ein HTTP-POST mit JSON-Body:

{
  "id": "6f1d2c80-4c1e-4f3a-9b7e-2a1d8c5e9f10",
  "event": "run.failed",
  "timestamp": "2026-10-01T09:16:56.512Z",
  "workspaceId": "a1b2c3d4-0000-4000-8000-000000000001",
  "attempt": 1,
  "data": {
    "team": { "id": "...", "slug": "sandbox-team", "name": "Sandbox Team", "keyPrefix": "SBX" },
    "story": {
      "id": "...", "key": "SBX-12", "title": "Export invoices as CSV", "readinessScore": 100,
      "tracker": { "platform": "github", "key": "#12", "url": "https://github.com/acme/app/issues/12" }
    },
    "run": {
      "key": "run-7f3k", "attempt": 2, "status": "failed", "stage": "verifying", "executor": "claude-code",
      "costCents": 140, "budgetCents": 400, "failureClass": "gate", "prUrl": null, "prNumber": null,
      "startedAt": "2026-10-01T09:02:10.000Z", "completedAt": "2026-10-01T09:16:56.000Z"
    },
    "error": "Verification failed"
  }
}

id ist die Zustell-ID; sie bleibt bei Wiederholungen gleich, und attempt zählt die Versuche. timestamp ist der Zeitpunkt des Ereignisses, nicht der Zustellung.

Was data enthält, hängt vom Event ab:

Events Felder in data
story.* team, story
run.* team, story, run; run.stage_changed ergänzt previousStage, run.failed ergänzt error
pr.opened team, story, run, pullRequest (url, number)
gate.* team, story, gate (name, verdict, reason, attempt, runKey, finishedAt)
decision.* team, story, decision (key, kind, authority, title, status, recommendation, options, runKey, resolution, resolvedVia, resolvedBy, resolvedAt, expiresAt); decision.resolved ergänzt result
report.published team, report (periodKey, periodStart, periodEnd, summary, metrics)
team.* team, pausedAt, by

story ist null bei Events ohne Story, zum Beispiel bei einer Entscheidung über das ganze Team. Behandeln Sie unbekannte Felder als optional: kanman kann Felder ergänzen, entfernt oder benennt sie aber nicht um.

Header Beschreibung
Content-Type application/json
User-Agent kanman-webhooks/1
X-Kanman-Event Event-Typ, zum Beispiel run.failed.
X-Kanman-Delivery Zustell-ID (UUID), dieselbe wie id im Body. Nutzen Sie sie, um Duplikate zu ignorieren.
X-Kanman-Timestamp Unix-Zeit in Sekunden, zu der dieser Versuch signiert wurde.
X-Kanman-Signature sha256= gefolgt von der hexadezimalen HMAC-SHA256-Signatur.

Signatur prüfen

Die Signatur ist HMAC-SHA256 mit Ihrem Webhook-Secret über den String <X-Kanman-Timestamp>.<roher Request-Body>. Berechnen Sie sie über die rohen Bytes, die Sie empfangen haben, vor jedem JSON-Parsen. Wenn Sie den Body parsen und neu serialisieren, ändern sich Leerzeichen und Schlüsselreihenfolge, und die Signatur passt nicht mehr. Lehnen Sie außerdem Zustellungen ab, deren Zeitstempel älter als fünf Minuten ist, damit eine mitgeschnittene Anfrage nicht wiederholt werden kann.

Node.js mit Express:

const crypto = require('crypto')
const express = require('express')

const app = express()
const SECRET = process.env.KANMAN_WEBHOOK_SECRET

// Keep the raw body: the signature covers the exact bytes kanman sent
app.post('/kanman', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Kanman-Timestamp')
  const signature = req.get('X-Kanman-Signature') || ''
  const expected = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(`${timestamp}.${req.body.toString('utf8')}`)
    .digest('hex')

  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300
  const valid = signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  if (!fresh || !valid) return res.status(401).send('invalid signature')

  const { event, data } = JSON.parse(req.body)
  res.status(200).send('ok')
  handle(event, data) // do the real work after answering
})

Python mit Flask:

import hashlib
import hmac
import json
import os
import time

from flask import Flask, request

app = Flask(__name__)
SECRET = os.environ["KANMAN_WEBHOOK_SECRET"].encode()

@app.post("/kanman")
def kanman_webhook():
    timestamp = request.headers.get("X-Kanman-Timestamp", "0")
    signature = request.headers.get("X-Kanman-Signature", "")
    raw = request.get_data()  # raw bytes, not request.json
    expected = "sha256=" + hmac.new(SECRET, timestamp.encode() + b"." + raw, hashlib.sha256).hexdigest()
    if abs(time.time() - int(timestamp)) > 300 or not hmac.compare_digest(expected, signature):
        return "invalid signature", 401
    payload = json.loads(raw)
    print(payload["event"], payload["data"])
    return "ok", 200

Zustellregeln

  • kanman sendet ein Event wenige Sekunden, nachdem es passiert ist.
  • Antworten Sie innerhalb von 10 Sekunden mit einem 2xx-Status. Erledigen Sie langsame Arbeit erst nach der Antwort. Weiterleitungen werden nicht verfolgt.
  • Eine fehlgeschlagene Zustellung wird bis zu viermal wiederholt: nach 1 Minute, 5 Minuten, 30 Minuten und 2 Stunden. Netzwerkfehler, Zeitüberschreitungen sowie Antworten mit 408, 429 und 5xx werden wiederholt; andere 4xx-Antworten gelten sofort als endgültiger Fehlschlag.
  • Nach 5 endgültig fehlgeschlagenen Zustellungen in Folge wird der Webhook pausiert und zeigt Pausiert mit einer kurzen Erklärung; noch wartende Zustellungen für ihn entfallen. Beheben Sie den Endpunkt und schalten Sie den Webhook unter Einstellungen, Webhooks mit seinem Schalter wieder ein. Das Einschalten setzt den Fehlerzähler zurück.
  • Jedes Event wird mindestens einmal zugestellt. Nutzen Sie X-Kanman-Delivery (oder id), um eine bereits verarbeitete Zustellung zu ignorieren.
  • Zustellungen verschiedener Events können in anderer Reihenfolge ankommen. Ordnen Sie sie über timestamp.

Test

Klicken Sie an einem Webhook auf Test senden, um ein test-Event an Ihren Endpunkt zu schicken. Die App zeigt an, ob die Zustellung geklappt hat. Der Test-Body sieht so aus:

{
  "id": "0b9d4a6e-8c2f-4d1a-9e3b-7f6a5c4d3e2b",
  "event": "test",
  "timestamp": "2026-10-01T09:00:00.000Z",
  "workspaceId": "a1b2c3d4-0000-4000-8000-000000000001",
  "attempt": 1,
  "data": {
    "message": "This is a test delivery from kanman",
    "webhookId": "3f0c2b9e-6a51-4c1e-9f0a-2d7b8e41c5aa",
    "webhookName": "Ops alerts"
  }
}

Ein Test wird einmal gesendet und nicht wiederholt. Für lokale Tests machen Sie Ihren Rechner über einen Tunnel erreichbar und tragen die öffentliche HTTPS-URL als Endpunkt ein.

Um einen Webhook zu löschen, klicken Sie auf seinen Löschen-Button und bestätigen mit Webhook löschen.

Bestätigungsdialog zum Löschen eines Webhooks (Desktop) Bestätigungsdialog zum Löschen eines Webhooks (Mobile)

Verwandte Seiten

Zuletzt aktualisiert: January 1, 0001

kanman öffnen