Skip to content

Webhooks

Gilt ab Work7 0.2.0 · Stand 12.09.2026

Webhooks benachrichtigen dein System, sobald in Work7 etwas passiert: ein Rapport wurde abgeschlossen, eine Rechnung bezahlt, eine Anfrage angelegt. Du registrierst eine https-URL und die Events, die dich interessieren. Work7 sendet dann einen signierten POST.

Einrichten

  1. Einstellungen › Integrationen › API & Webhooks, Abschnitt Ausgehende Webhooks.
  2. Name, Ziel-URL (nur https) und Events wählen. Wildcards wie rapport.* sind erlaubt.
  3. Webhook anlegen. Das Secret (whsec_…) wird einmalig angezeigt und wird zum Prüfen der Signatur gebraucht.

Webhooks lassen sich pausieren und wieder aktivieren. Ereignisse während einer Pause werden nicht nachgeliefert.

Events

Events sind nach Bereich benannt. Es werden nur Events von Bereichen gesendet, die für den Betrieb freigeschaltet sind. Die aktuell verfügbaren Events zeigt die Verwaltung beim Anlegen an.

BereichEvents (Auswahl)
Projekte, Kontakteproject.created, project.updated, kontakt.created, kontakt.updated
Anfragenanfrage.created, anfrage.updated, anfrage.status_changed
Rapportrapport.created, rapport.completed, rapport.sent
Angeboteoffer.created, offer.sent, offer.accepted
Rechnungeninvoice.created, invoice.sent, invoice.paid, invoice.exported
Zeiterfassungtime_entry.created, time_entry.updated
Termineappointment.created, appointment.updated

Ein Event-Name besteht aus <entität>.<aktion>. Neue Aktionen können hinzukommen, dein Empfänger sollte unbekannte Events ignorieren, nicht ablehnen.

Zustellung

http
POST https://deine-domain.example/work7-webhook
Content-Type: application/json
User-Agent: Work7-Webhooks/1.0
X-Work7-Event: rapport.completed
X-Work7-Delivery: 8123
X-Work7-Timestamp: 1757671200
X-Work7-Signature: sha256=5f1c…e9
json
{
  "id": "0d9a8f2e-3b6c-4e1a-9f21-6d1f0c4b7a10",
  "type": "rapport.completed",
  "companyId": "muster-malerei",
  "createdAt": "2026-09-12T10:00:00.000Z",
  "data": {
    "entity": "rapport",
    "action": "completed",
    "entityId": 4711,
    "userId": "18",
    "data": { "rapport_number": "RP-2026-0087" }
  }
}
  • id ist je Ereignis eindeutig. Mehrere Webhooks erhalten dasselbe id. Nutze es, um Doppelverarbeitung zu vermeiden.
  • Die Nutzlast enthält Kennungen und die geänderten Felder, keine vollständigen Dokumente. Details holst du bei Bedarf über die API, zum Beispiel GET /rapports/4711.
  • Antworte innerhalb von 10 Sekunden mit einem 2xx-Status. Alles andere gilt als Fehlschlag.

Signatur prüfen

Jede Zustellung ist mit HMAC-SHA256 über "<timestamp>.<body>" signiert, Schlüssel ist das Webhook-Secret. Prüfe immer die Signatur und das Alter des Zeitstempels (empfohlen: maximal fünf Minuten), bevor du das Ereignis verarbeitest.

js
import crypto from 'node:crypto'
import express from 'express'

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

// Rohen Body behalten: die Signatur gilt für die exakten Bytes.
app.post('/work7-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.header('X-Work7-Timestamp') ?? ''
  const signature = req.header('X-Work7-Signature') ?? ''
  const body = req.body.toString('utf8')

  const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp))
  if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return res.status(400).send('stale')

  const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(`${timestamp}.${body}`).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(signature)
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.status(401).send('bad signature')

  const event = JSON.parse(body)
  // Idempotenz: event.id merken, bereits gesehene Events überspringen.
  if (event.type === 'rapport.completed') {
    // ... eigene Verarbeitung, z. B. Rapport per API laden
  }
  res.status(204).end()
})
php
<?php
$secret = getenv('WORK7_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WORK7_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WORK7_SIGNATURE'] ?? '';

if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(400);
    exit('stale');
}

$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('bad signature');
}

$event = json_decode($body, true);
// Idempotenz: $event['id'] speichern, Duplikate überspringen.
if ($event['type'] === 'invoice.paid') {
    // ... eigene Verarbeitung
}
http_response_code(204);

Wiederholungen

Antwortet dein Endpunkt nicht mit 2xx oder nicht innerhalb von 10 Sekunden, versucht Work7 es erneut, insgesamt bis zu fünf Mal mit wachsendem Abstand:

VersuchWartezeit danach
11 Minute
25 Minuten
315 Minuten
460 Minuten
5keine, Status dead

Den Zustellstatus je Ereignis siehst du in der Verwaltung unter Letzte Zustellungen (Status, HTTP-Code, Fehlertext, Versuche). Ereignisse mit Status dead werden nicht automatisch erneut gesendet.

Testen

  • Lokal lässt sich der Endpunkt mit einem Tunnel wie ngrok oder cloudflared erreichbar machen. Die URL muss https sein.
  • Ein erstes Ereignis erzeugst du am einfachsten in der App, etwa durch Abschließen eines Test-Rapports.
  • Für Unit-Tests der Signaturprüfung reicht es, "<timestamp>.<body>" mit dem Secret zu signieren, wie oben gezeigt.

Work7 · Software für Handwerksbetriebe · Doku-Stand Version 0.2.1 (Build fb6311f-dirty)