Documentation menu

Ausgehende Webhooks

Webhooks senden Manisma-Ereignisse an einen öffentlichen HTTPS-Endpunkt Ihrer Organisation.

Endpunkt einrichten

Öffnen Sie als Admin Verwaltung → Webhooks, erstellen Sie einen Endpunkt und wählen Sie Name, URL und Ereignisse. Das Signing-Geheimnis wird einmalig angezeigt. Bewahren Sie es sicher auf. Bei Rotation gilt das neue Geheimnis ab dem Speichern.

Die URL muss:

  • HTTPS verwenden;
  • öffentlich über DNS erreichbar sein;
  • Port 443 oder 8443 verwenden;
  • keinen Benutzernamen/Passwort in der URL enthalten;
  • nicht auf Localhost, private IP, Link-Local oder Cloud-Metadaten verweisen.

Verwenden Sie Test, um ein ping zu senden, bevor Sie echte Ereignisse aktivieren.

Ereignisse

Ereignis Auslöser
ping Manueller Endpunkttest.
client.created Kunde angelegt.
client.updated Kunde geändert.
job.created Auftrag angelegt.
job.planned Auftragsstatus auf gepland geändert.
job.completed Auftragsstatus auf Voltooid geändert.
job.cancelled Auftragsstatus auf geannuleerd geändert.
approval.approved Kunde hat einen Vorschlag genehmigt.
approval.declined Kunde hat einen Vorschlag abgelehnt.
workorder.completed Abrechnungsfähige Arbeitsauftragsdaten bei Abschluss.

Eine Änderung an einem anderen Feld eines bereits Voltoooiden Auftrags sendet job.completed nicht erneut; der Status selbst muss sich ändern.

Envelope und Header

{
  "event": "job.completed",
  "version": 1,
  "tenant_id": "tenant-uuid",
  "occurred_at": "2026-07-13T09:15:00.000Z",
  "data": {}
}

Header:

  • X-Manisma-Event: Ereignisname;
  • X-Manisma-Delivery: stabile ID dieser logischen Lieferung;
  • X-Manisma-Signature: sha256=<hex HMAC-SHA256> über den exakten Rohe-Body.

Verifizieren Sie den HMAC vor dem JSON-Parsing und verwenden Sie einen Timing-sicheren Vergleich:

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verify(rawBody, header, secret) {
  if (!header?.startsWith('sha256=')) return false
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
  const supplied = header.slice(7)
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(supplied, 'hex')
  return a.length === b.length && timingSafeEqual(a, b)
}

Payloads

Kundenereignisse enthalten id, customer_number, name, client_type, email und phone. Interne Kundennotizen fehlen.

Auftragsereignisse enthalten Auftrags-ID/-Nummer, Titel, Status, Priorität, Planung und kompakte Objekte für Kunde, Serviceadresse und Haupttechniker. Ein nicht zugewiesener oder adressloser Auftrag verwendet null.

Genehmigungsereignisse enthalten Genehmigungs-ID, job_id, Status, gewählten Slot und Antwortzeit. chosen_slot kann ein JSON-String oder älterer Freitext sein; parsen Sie defensiv.

workorder.completed enthält:

  • kompakten Auftrag und Kunde;
  • den zuletzt Voltoooiden Besuch, falls verfügbar;
  • verwendete Materialien mit SKU, Verkaufspreis, Menge und Zeilenbetrag;
  • Arbeitsdauer;
  • Materialsgesamtbetrag in EUR.

Der Materialpreis ist der Verkaufspreis zum Zeitpunkt der Erstellung der Webhook-Payload, nicht notwendigerweise ein historisch festgeschriebener Preis.

Lieferung und Retries

Webhooks sind at least once. Machen Sie Ihren Empfänger idempotent bezüglich X-Manisma-Delivery. Ein erfolgreicher Empfang ist jede 2xx-Antwort. Redirects werden nicht verfolgt und jeder Versuch hat ein Timeout von 10 Sekunden.

Bei einem vorübergehenden Fehler gibt es maximal drei Versuche: sofort, nach 60 Sekunden und erneut 300 Sekunden später. Dieselbe Delivery-ID bleibt erhalten. Retries laufen derzeit im Serviceprozess und überleben kein Redeploy; prüfen Sie daher auch das Lieferlog in Webhooks auf zurückgebliebene pending-Einträge.

Speichern Sie zuerst die Delivery-ID in derselben Transaktion wie Ihre Verarbeitung und antworten Sie danach schnell mit 2xx. Verlagern Sie langsame Verarbeitung in Ihre eigene Queue.