Kostenlos testen
Handbuch

Webhooks — Ereignisse senden und empfangen

TaskMonkey meldet Ereignisse per signiertem POST an n8n, Make oder dein System, und fremde Systeme lösen über eine eigene URL Tools oder Assistants aus. Signatur, Event-Katalog, Retries, Sicherheit.

Webhooks

Webhooks verbinden deinen Workspace in beide Richtungen mit anderen Systemen, ohne dass jemand pollen muss:

  • Ausgehend (TaskMonkey → dein System): Sobald etwas passiert — neue Kundennachricht in der Inbox, Tool fertig, Scheduled Task fehlgeschlagen —, schickt TaskMonkey einen signierten POST an eine URL deiner Wahl, z. B. einen n8n-Webhook, ein Make-Szenario oder deinen eigenen Server.
  • Eingehend (dein System → TaskMonkey): Ein Shop, Stripe, ein Formular-Dienst oder n8n ruft eine eigene URL deines Workspaces auf und löst damit ein freigegebenes Tool oder einen Assistant aus.

Beides verwaltest du unter Backend → Verwaltung → Webhooks (/manage/webhooks, Rolle Manager oder Admin).

Webhooks ergänzen die API-Keys: Mit einem Key fragt dein System TaskMonkey („gib mir X“), mit einem Webhook meldet sich TaskMonkey, sobald X passiert — oder ein fremdes System stößt TaskMonkey an, ohne einen Key zu verwalten. Ein Schritt-für-Schritt-Beispiel mit n8n steht im Rezept n8n ↔ TaskMonkey.

Ausgehende Webhooks

Einrichten

  1. Verwaltung → Webhooks → + Ausgehend
  2. URL eintragen (nur https://), Events anhaken, speichern
  3. Das Signatur-Secret (whsec_…) wird genau einmal angezeigt. Beim Empfänger hinterlegen — damit prüft er, dass ein Aufruf wirklich von TaskMonkey kommt.
  4. Test-Event senden — das Ergebnis erscheint nach wenigen Sekunden im Zustell-Log.

Verlorenes Secret? „Secret rotieren“ erzeugt ein neues, das alte gilt ab sofort nicht mehr.

Was gesendet wird

Jede Zustellung ist ein POST mit JSON-Body im immer gleichen Umschlag:

{
  "id": "evt_5f0c2b8e9a7d4c1b8e3f6a2d9c0b1e47",
  "type": "inbox.message.received",
  "api_version": "2026-09-25",
  "created": "2026-09-25T10:15:00Z",
  "workspace": "bloomify",
  "data": { "…": "je nach Event, siehe Katalog" }
}
Header Inhalt
Content-Type application/json
X-TaskMonkey-Event Event-Typ, z. B. inbox.message.received
X-TaskMonkey-Delivery UUID dieser Zustellung — bei Retries gleich, zum Deduplizieren
X-TaskMonkey-Signature t=<unix-zeit>,v1=<hex> (siehe Signatur prüfen)
X-TaskMonkey-Attempt Nummer des Versuchs (1–6)
X-TaskMonkey-Hop Schleifen-Zähler: 1 normal, 2, wenn das Ereignis selbst von einem eingehenden TaskMonkey-Webhook ausgelöst wurde (siehe Schleifen)
User-Agent TaskMonkey-Webhooks/1.0 …

api_version ändert sich nur, wenn sich das Format eines bestehenden Events ändert. Neue Felder können jederzeit dazukommen — ignoriere, was du nicht kennst.

Antworten, Retries, Deaktivierung

  • 2xx innerhalb von 10 Sekunden = zugestellt. Antworte schnell und verarbeite danach (Queue, n8n „Respond: Immediately“).
  • Alles andere (Timeout, Verbindungsfehler, 3xx, 4xx, 5xx) ist ein Fehlversuch. TaskMonkey wiederholt nach 1 min, 5 min, 30 min, 2 h und 12 h — insgesamt 6 Versuche, danach steht die Zustellung auf failed.
  • Weiterleitungen werden nicht verfolgt. Eine 301/302 zählt als Fehler — trag die endgültige URL ein.
  • 410 Gone beendet die Zustellung sofort (kein Retry).
  • Nach 5 endgültig fehlgeschlagenen Zustellungen in Folge wird der Endpoint automatisch deaktiviert, die Admin-Adressen des Workspaces bekommen eine Mail. Reaktivieren: Endpoint öffnen, „Aktiv“ anhaken, speichern. Jede erfolgreiche Zustellung setzt den Zähler zurück.
  • Erneut senden im Log legt eine neue Zustellung an (neue X-TaskMonkey-Delivery, gleiche Event-id).

Fairness: Der Worker bedient die Warteschlange reihum pro Workspace und pro Endpoint (je Durchlauf höchstens 5 Zustellungen pro Workspace und 2 pro Endpoint, mit Zeitbudget). Ein Endpoint, der jede Anfrage in den Timeout laufen lässt, oder ein Workspace mit sehr vielen Events verzögert andere also nicht; bei vielen Events verteilt sich die Zustellung über mehrere Durchläufe (alle 2 s).

Reihenfolge und Duplikate: Zustellungen können in anderer Reihenfolge ankommen, als die Ereignisse passiert sind (z. B. weil eine ältere gerade im Retry hängt), und dieselbe Zustellung kann in seltenen Fällen zweimal ankommen. Dedupliziere über X-TaskMonkey-Delivery bzw. — wenn auch manuelle Wiederholungen nur einmal wirken sollen — über die Event-id; ordne über created.

Das Zustell-Log (Verwaltung → Webhooks → Endpoint) zeigt je Zustellung Status, Versuche, Antwort-Code, Dauer, die ersten 1000 Zeichen der Antwort und den Payload. Einträge werden nach 30 Tagen gelöscht.

Signatur prüfen

v1 ist HMAC-SHA256(secret, "<t>.<roher Body>") als Hex. Prüfe immer gegen den rohen Body (nicht gegen ein neu serialisiertes JSON), vergleiche zeitkonstant und lehne Zeitstempel ab, die mehr als 5 Minuten abweichen — so lässt sich ein mitgeschnittener Aufruf nicht später wiederholen.

Node.js (Express)

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

app.post('/taskmonkey', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-TaskMonkey-Signature') || '';
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const t = parseInt(parts.t, 10);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400);

  const expected = Buffer.from(
    crypto.createHmac('sha256', process.env.TASKMONKEY_WEBHOOK_SECRET)
      .update(`${t}.${req.body}`).digest('hex')
  );
  const given = Buffer.from(parts.v1 || '');
  if (given.length !== expected.length || !crypto.timingSafeEqual(expected, given)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body);
  res.sendStatus(204);            // erst quittieren …
  handle(event);                  // … dann verarbeiten
});

Python (Flask)

import hmac, hashlib, os, time
from flask import Flask, request, abort

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

@app.post("/taskmonkey")
def taskmonkey():
    raw = request.get_data()  # roher Body
    parts = dict(p.split("=", 1) for p in request.headers.get("X-TaskMonkey-Signature", "").split(",") if "=" in p)
    t = int(parts.get("t", "0"))
    if abs(time.time() - t) > 300:
        abort(400)
    expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(401)
    event = request.get_json()
    # … verarbeiten
    return "", 204

PHP

$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_TASKMONKEY_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $parts);
$t = (int)($parts['t'] ?? 0);
if (abs(time() - $t) > 300) {
    http_response_code(400);
    exit;
}
$expected = hash_hmac('sha256', $t . '.' . $raw, getenv('TASKMONKEY_WEBHOOK_SECRET'));
if (!hash_equals($expected, (string)($parts['v1'] ?? ''))) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);
http_response_code(204);
// … verarbeiten

Event-Katalog

Abonnieren kannst du genau diese Events — sie stehen im Code (src/Service/Webhook/WebhookEvents.php), nicht als Freitext.

Event Wann
tool.completed Ein Tool mit 'webhook_event' => true ist fertig (Erfolg oder Fehler)
inbox.thread.created Erste Nachricht eines neuen Inbox-Threads (E-Mail, WhatsApp, Facebook, Instagram)
inbox.message.received Jede eingehende Kundennachricht in der Inbox
inbox.thread.status_changed Thread erledigt, wartet auf Kunde, zurückgestellt oder wieder geöffnet
scheduled_task.completed Scheduled Task erfolgreich
scheduled_task.failed Scheduled Task fehlgeschlagen
public_tool.submitted Aufruf über die Public Tool API (Formular, Landingpage)
instagram.connected Ein Instagram-Profi-Konto wurde verbunden (eigene Verbindung oder Partner-Nutzer mit ref, siehe Instagram)
instagram.revoked Eine Instagram-Verbindung ist weg: in Instagram entfernt, Datenlöschung, abgelaufen oder getrennt (reason)
custom.* / custom.<name> Eigene Events aus Tools (emitWebhookEvent)
webhook.test Nur über „Test-Event senden“, an genau diesen Endpoint

Beispiele für data:

tool.completed — nur für Tools, die 'webhook_event' => true tragen (genau true). Gilt für Starts aus Chat, API-Key, Scheduled Task und Public Tool API. Nicht für Läufe, die ein eingehender Webhook ausgelöst hat (Schleifen-Schutz), nicht für Tools, die ein anderes Tool intern aufruft, und nicht in bin/cake test_tool.

{
  "tool": "createLead",
  "success": true,
  "error": null,
  "entry_type": "api_key",
  "execution_id": "0b7c…",
  "duration_ms": 412,
  "args": { "email": "max@example.com" },
  "result": { "success": true, "lead_id": 4711 }
}

args und result gehen mit, weil das Tool das per Flag ausdrücklich will. Felder, deren Name nach Geheimnis aussieht (password, token, secret, api_key, authorization, iban …), werden durch "[entfernt]" ersetzt, Felder mit _-Präfix (_api_key, _client, _webhook) fallen weg.

inbox.thread.created

{
  "thread": { "chat_id": "email:kunde@example.com:3f2a…", "channel": "email", "status": "open" },
  "first_message": {
    "id": "9d1e…",
    "channel": "email",
    "subject": "Reklamation Bestellung 1001",
    "from": { "name": "Max Muster", "address": "kunde@example.com" },
    "text": "Hallo, die Pflanze kam beschädigt an …",
    "received_at": "2026-09-25T10:14:58+02:00",
    "attachments": [{ "filename": "foto.jpg", "mimetype": "image/jpeg", "size": 184223 }]
  }
}

inbox.message.received

{
  "thread": { "chat_id": "whatsapp:+4917012345678", "channel": "whatsapp", "is_new": false },
  "message": {
    "id": "a41c…",
    "channel": "whatsapp",
    "subject": null,
    "from": { "name": "Max", "address": "+4917012345678" },
    "text": "Wann kommt meine Bestellung?",
    "received_at": "2026-09-25T10:15:00+02:00",
    "attachments": []
  }
}

text ist der Klartext der Nachricht (max. 10 000 Zeichen). Anhänge werden nur beschrieben, nicht mitgeschickt.

inbox.thread.status_changed

{
  "chat_id": "email:kunde@example.com:3f2a…",
  "channel": "email",
  "previous_status": "open",
  "status": "resolved",
  "source": "user"
}

status/previous_status: open (offen), waiting (wartet auf Kunde), snoozed (zurückgestellt), resolved (erledigt).

source: user (Mitarbeiter in der Inbox), auto_resolve (Spam-/System-Filter), customer_reply (Kunde antwortet auf einen erledigten, wartenden oder zurückgestellten Thread), snooze_ended (Zurückstellen abgelaufen), commslayer_sync, system.

scheduled_task.completed / scheduled_task.failed

{ "task": "daily_report", "tool": "sendDailyReport", "success": false, "error": "Shopify: 503", "duration_ms": 8120 }

public_tool.submitted

{
  "tool": "confirmPhotoUpload",
  "success": true,
  "origin": "https://aktion.bloomify.de",
  "ip_prefix": "91.64.12.0",
  "args": null,
  "result": null
}

args und result (die Formulardaten und die Antwort) sind nur gefüllt, wenn das Tool 'webhook_event' => true trägt — sonst erfährst du nur, dass ein Aufruf stattfand.

custom.<name> — was dein Tool in data mitgibt:

$ctx['tool']('emitWebhookEvent', [
    'name' => 'lead.qualified',              // → custom.lead.qualified
    'data' => ['lead_id' => $id, 'score' => 87],
]);
{ "lead_id": 4711, "score": 87 }

Namen: Kleinbuchstaben, Ziffern, ., _, -, max. 60 Zeichen. emitWebhookEvent ist ein Shared-Tool (in jedem Workspace vorhanden) und kann auch einem Assistant zugewiesen werden. Im Test-Modus sendet es nichts. Eigene Handler senden direkt über $ctx['emitWebhook']('lead.qualified', $data) (gleiche Regeln; Katalog-Events lassen sich so nicht fälschen — der Name wird immer zu custom.<name>).

webhook.test

{ "message": "Test-Event aus TaskMonkey. Wenn du das siehst, stimmt die Verbindung.", "endpoint_id": 3, "triggered_by": "marcus@example.com" }

Was noch nicht als Event kommt

chat.message.created (jede Chat-Nachricht) ist bewusst noch nicht dabei: Backend-, Widget- und Sub-Chats laufen über denselben Speicherpfad, und interne Mitarbeiter-Chats sollen nicht nach außen gehen. Bis dahin: ein eigenes Event per emitWebhookEvent aus dem Assistant. Ebenfalls später: inbox.thread.created für Widget-Chats, Zuweisung/Kategorie-Änderungen.

Eingehende Webhooks

Einrichten

  1. Ziel freigeben — in der Tool-Definition:
'createLead' => [
    'description' => 'Legt einen Lead an.',
    'internal' => true,
    'inbound_hook' => true,          // Ziel eingehender Webhooks
    'inbound_hook_write' => true,    // darf schreiben — Begründung im Kommentar
    'parameters' => [
        'type' => 'object',
        'properties' => [
            'email' => ['type' => 'string', 'pattern' => '^[^@\s]+@[^@\s]+$'],
            'name'  => ['type' => 'string', 'maxLength' => 100],
        ],
        'required' => ['email'],
    ],
    'handler' => function (array $results, array $args, array $ctx): array {
        // $args['_webhook'] = ['hook_id', 'hook_name', 'delivery_id', 'external_id', 'received_at']
        // …
    },
],

Oder einen Assistant mit 'inbound_hook' => true in seiner Definition.

  1. Verwaltung → Webhooks → + Eingehend: Name, Auth-Format, Ziel, optional Mapping.
  2. URL (https://app.taskmonkey.de/api/v1/hooks/hk_…) und Secret werden einmalig angezeigt — im aufrufenden System eintragen.

Freigabe

Ziel Flag Hinweis
Tool 'inbound_hook' => true oder 'external_api' => true genau true; Tools mit async: true sind ausgeschlossen
Tool, schreibend zusätzlich 'inbound_hook_write' => true (oder 'external_api_write' => true) sonst läuft es im Read-only-Kontext
Assistant 'inbound_hook' => true in der Assistant-Definition seine Tools laufen im Read-only-Kontext
Assistant, schreibend zusätzlich 'inbound_hook_write' => true in der Assistant-Definition nur, wenn der Assistant aus fremden Daten wirklich schreiben soll

Die Freigabe wird bei jeder Verarbeitung neu geprüft: nimmst du das Flag weg, laufen auch bestehende Hooks ins Leere. Der Builder setzt inbound_hook/inbound_hook_write nur nach eigener Rückfrage (confirm_exposure), wie bei public_api und external_api.

Auth-Formate

Format Header Prüfung Replay-Schutz
TaskMonkey-HMAC X-TaskMonkey-Signature: t=…,v1=… wie oben Zeitstempel-Toleranz (Default 300 s) + Signatur (gleicher Zeitstempel + Body nur einmal) + X-TaskMonkey-Delivery
Stripe-Style Stripe-Signature: t=…,v1=… identisch, mit dem Stripe-Endpoint-Secret Toleranz + Signatur + Event-id aus dem Body
Shopify X-Shopify-Hmac-Sha256 base64(HMAC-SHA256(secret, body)) Hash des signierten Bodys (derselbe Body wird nur einmal verarbeitet)
Bearer Authorization: Bearer <secret> Token-Vergleich Idempotency-Key / X-Delivery-Id / X-Request-Id, falls gesendet

Bei Shopify und Stripe gibt der Anbieter das Secret vor — beim Anlegen im Feld „Secret des Anbieters“ einfügen. Sonst erzeugt TaskMonkey eins. Bearer ist am einfachsten, aber ohne Zeitstempel: wer den Token hat, kann beliebig senden — nimm HMAC, wo das aufrufende System es kann.

Beispiel TaskMonkey-HMAC mit curl:

BODY='{"email":"max@example.com","name":"Max"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$TM_HOOK_SECRET" | sed 's/^.* //')
curl -X POST https://app.taskmonkey.de/api/v1/hooks/hk_… \
  -H 'Content-Type: application/json' \
  -H "X-TaskMonkey-Signature: t=$TS,v1=$SIG" \
  -H "X-TaskMonkey-Delivery: $(uuidgen)" \
  -d "$BODY"
{"success":true,"accepted":true,"delivery_id":"6f1d0c1e-…"}

Ein ausgehender TaskMonkey-Webhook eines Workspaces kann so direkt den eingehenden eines anderen auslösen — Signatur und Header passen zueinander.

Body → Args

Einstellung Ergebnis
nichts Die obersten Felder des JSON-Bodys sind die Args
Mapping {"email": "/customer/email", "betrag": "/total_price"} Jedes Arg per JSON Pointer aus dem Body; fehlende Pfade werden ausgelassen
Body-Arg payload Der ganze Body landet in args.payload (Tool deklariert payload als type: object)

Danach gilt dasselbe wie bei API-Keys: nur im parameters-Schema deklarierte Args kommen durch, Pflichtfelder und Typen werden geprüft, {{-Marker sind verboten. Das Tool bekommt zusätzlich args['_webhook'] (Hook, Delivery-ID, Zeitpunkt) — serverseitig gesetzt, nicht fälschbar. Formular-Bodys (application/x-www-form-urlencoded) werden wie JSON behandelt.

Bei einem Assistant als Ziel wird ein eigener Chat (hook_<id>_…) angelegt; der Assistant bekommt die (gemappten) Daten als Nachricht und läuft ohne Benutzer. Die Antwort steht im Log des Hooks.

Weil die Daten von außen kommen, sind sie für den Assistant ausdrücklich als nicht vertrauenswürdig markiert und eingezäunt (<untrusted_webhook_data>…</untrusted_webhook_data> mit dem Hinweis, darin enthaltenen Anweisungen nicht zu folgen). Das senkt das Risiko von Prompt-Injection, verhindert es aber nicht sicher — deshalb laufen seine Tools nur lesend, solange der Assistant nicht 'inbound_hook_write' => true trägt. Gib einem schreibenden Hook-Assistant nur die Tools, die er für genau diesen Zweck braucht.

Assistant-Ziele laufen in einer eigenen Warteschlange (Worker-Lane assistant): ein LLM-Lauf, der Minuten dauert, hält Zustellungen und Tool-Hooks nicht auf.

Antworten

Status Bedeutung
202 angenommen: {"accepted":true,"delivery_id":"…"} — das Ziel läuft in wenigen Sekunden im Hintergrund
200 Duplikat (schon angenommen): {"duplicate":true,"delivery_id":"<ursprüngliche>"}
400 Body ist kein gültiges JSON
401 Signatur/Token fehlt, falsch oder Zeitstempel außerhalb der Toleranz
403 Workspace gesperrt
404 Hook unbekannt oder deaktiviert (nicht unterscheidbar)
508 Schleife: X-TaskMonkey-Hop ist 2 oder höher
405 nicht POST
413 Body größer als 256 KB
429 Rate-Limit des Hooks (Default 60/min) — mit Retry-After

Weil die Antwort vor der Verarbeitung kommt, sieht der Absender Fehler des Tools nicht. Ergebnis, Fehler und Dauer stehen im Log des Hooks (Verwaltung → Webhooks → Hook), dort lässt sich ein Aufruf auch erneut verarbeiten. Einträge werden nach 30 Tagen gelöscht.

Sicherheit

Ausgehend:

  • Nur https://, keine Zugangsdaten in der URL.
  • Kein Zugriff auf interne Netze (SSRF-Schutz): Der Hostname wird aufgelöst, und jede Adresse muss öffentlich sein. Gesperrt sind u. a. 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 (Cloud-Metadaten), 100.64.0.0/10, 0.0.0.0/8, Multicast/reserviert, ::1, fc00::/7, fe80::/10 sowie IPv4-gemappte Varianten. Geprüft wird beim Speichern und vor jedem Versand; die geprüfte IP wird für die Verbindung festgenagelt, damit ein DNS-Wechsel dazwischen (DNS-Rebinding) nichts bringt.
  • TaskMonkey selbst ist kein Ziel: URLs auf den App-Host (z. B. app.taskmonkey.de) werden abgelehnt — ein ausgehender Webhook auf den eigenen /api/v1/hooks/… wäre eine Schleife.
  • Keine Redirects, Timeout 10 s, Antworten werden nach 64 KB abgeschnitten.
  • Payload max. 256 KB. Keine Secrets: das Signatur-Secret steht nie im Payload, Secret-artige Felder werden entfernt.
  • Secrets liegen verschlüsselt in der Datenbank.

Eingehend:

  • Jeder Aufruf braucht eine gültige Signatur bzw. einen Token — die unratbare URL allein reicht nicht.
  • Replay-Schutz über Zeitstempel und Delivery-ID, Rate-Limit pro Hook.
  • Der Workspace kommt aus dem Hook: ein Hook erreicht nie Tools oder Assistants eines anderen Workspaces.
  • Nur freigegebene Ziele, nur deklarierte Parameter, standardmäßig nur lesend.
  • Keine internen Fehlermeldungen nach außen.

Checkliste für jedes Ziel mit inbound_hook:

  • parameters eng (pattern, enum, maxLength) — der Absender kontrolliert den Body
  • inbound_hook_write nur, wo wirklich geschrieben wird, mit Begründung im Kommentar
  • Idempotent: derselbe Datensatz zweimal ergibt kein Duplikat (Absender wiederholen bei Timeouts)
  • HMAC statt Bearer, wo möglich
  • Rate-Limit passend zum erwarteten Volumen

Schleifen

Ein Kreis „eingehender Hook → Tool → ausgehender Webhook → (n8n) → derselbe Hook“ wird an drei Stellen unterbrochen:

  1. Läufe aus eingehenden Webhooks melden kein tool.completed.
  2. Hop-Header: Jede ausgehende Zustellung trägt X-TaskMonkey-Hop. Normal ist 1; stammt das Ereignis aus einem Lauf, den ein eingehender Webhook mit X-TaskMonkey-Hop: 1 ausgelöst hat (z. B. emitWebhookEvent im Hook-Tool), steht dort 2. Der eingehende Endpoint lehnt 2 und höher mit 508 ab. Reiche den Header in n8n/Make unverändert weiter, wenn ein Workflow ein TaskMonkey-Ereignis an einen TaskMonkey-Hook zurückspielt — dann greift der Schutz auch über Umwege.
  3. Endpoints auf den App-Host selbst sind gesperrt (siehe oben).

Das Rate-Limit pro Hook bremst zusätzlich.

Betrieb

Was Wie
Zustellung + Verarbeitung bin/cake run_webhooks — cronjobs.sh startet alle 5 min --loop 290 im Hintergrund (pollt alle 2 s, flock gegen Doppelstart)
Assistant-Ziele bin/cake run_webhooks --lane assistant --loop 290 — eigener Worker mit eigenem Lock, ebenfalls aus cronjobs.sh
Fairness Round-Robin pro Workspace (ausgehend max. 5 je Workspace und 2 je Endpoint pro Batch, eingehend max. 3 je Workspace), Zeitbudget 20 s pro Batch
Aufräumen bin/cake run_webhooks --prune 30, täglich 04:00 aus cronjobs.sh
Tool-Läufe erscheinen im Execution-Log mit Entry-Type webhook und Chat-ID hook_… (tm monitor, tm watch)
Log der Worker tmp/cron-webhooks.log

Technik

Was Wo
Event-Katalog src/Service/Webhook/WebhookEvents.php
Emit (nur Queue-Zeile) src/Service/Webhook/WebhookDispatcher.php
Zustellung, Retries, Auto-Deaktivierung src/Service/Webhook/WebhookDeliveryService.php, WebhookSender.php
Signatur src/Service/Webhook/WebhookSigner.php
SSRF-Schutz src/Service/Security/EgressGuard.php
Eingehend src/Controller/Api/InboundHookController.php, src/Service/Webhook/InboundHookService.php, ArgMapper.php
Worker src/Command/RunWebhooksCommand.php
Verwaltung src/Controller/Manage/WebhooksController.php
Eigene Events config/tenants/_shared/tools/webhooks/emitWebhookEvent.php
Tabellen hook_outbound_endpoints, hook_outbound_deliveries, hook_inbound_endpoints, hook_inbound_deliveries
Zuletzt aktualisiert: 2026-09-25