Dokumentation

Webhooks

7 min Lesezeit · zuletzt aktualisiert 18. August 2026

Ein Webhook dreht die Richtung um: statt zu fragen, ob sich etwas getan hat, bekommst du es gesagt. Wir senden POST an deine Adresse, signiert, mit Wiederholungen — und mit einer Grenze, die hier wichtiger ist als der Rest.

Die Grenze zuerst

Ein Endpunkt hängt am Legacy-Zugang deines Projekts, nicht an einem OAuth-Grant. Er trägt damit die Einwilligung keiner Organisation und keines Teams. Daraus folgt die Regel, an der die ganze Ereignisliste hängt:

Ohne OAuth keine Produktdaten. Auch nicht über den Umweg Webhook.

Deshalb gibt es zwei Klassen von Ereignissen:

KlasseWas in der Nutzlast stehtBeispiel
VollDer Vorgang vollständig — es sind deine eigenen Daten.unit.state.changed, key.revoked, quota.exceeded
SignalRessource, Kennung, Aktion. Kein Name, kein Betrag, kein Feld.mc.members.changed, connection.unlinked

Ein Signal sagt dir nie mehr, als du durch Pollen ohnehin erführest — es erspart dir nur das Pollen. Was sich geändert hat, holst du danach mit deinem OAuth-Token über die API, mit demselben Scope wie sonst auch.

Was es zu abonnieren gibt

Die Konsole zeigt unter Webhooks genau die Ereignisse, die dein Projekt haben kann: freigeschaltete API, freigeschaltetes Ereignis. Die Gruppen:

GruppeKlasseWorum es geht
API KeysvollErstellt, rotiert, widerrufen, abgelaufen — und öffentlich gefunden.
OAuth ClientsvollAngelegt, Secret rotiert, abgeschaltet.
SandboxesvollAngelegt, zurückgesetzt, gelöscht.
Projekt & KontingentvollAPI an oder aus, Kontingent bei 80 %, Kontingent erschöpft, Zugang gewechselt.
TeamvollEinladung, Rollenwechsel, Entfernung.
PlattformvollStörungen der Statusseite und Abkündigungen von API-Versionen.
ZustellungvollEin Endpunkt wurde nach zu vielen Fehlschlägen stillgelegt.
Verbindung zum ProduktSignalKonto verknüpft, Verknüpfung gelöst, Freigabe geändert, Verlängerung gescheitert.
ÄnderungssignaleSignalJe Produkt-API eines: mc.members.changed, sb.servers.changed und so fort.

connection.unlinked ist das Ereignis, für das sich der ganze Aufwand lohnt: bis heute merkt eine Integration den Entzug einer Freigabe erst am nächsten 401 — mit Webhook in dem Moment, in dem er passiert.

Die Hülle

Jede Nutzlast sieht gleich aus. Was der Vorgang mitbringt, steht unter data.

{
  "id": "evt_9f2c81ab7d3e4405",
  "type": "unit.state.changed",
  "created_at": "2026-08-18T09:14:22.481Z",
  "environment": "production",
  "data": { "unit": "u_4471", "state": "wartung", "reason": "Drehmoment" }
}

Ein Signal trägt dieselbe Hülle und ein karges data:

{
  "id": "evt_2b90ce41d78a6612",
  "type": "mc.members.changed",
  "created_at": "2026-08-18T09:15:03.117Z",
  "environment": "production",
  "data": {
    "api": "methacore-members",
    "owner": "6654994a1f2b3c4d5e6f7a8b",
    "resource": "mc.members.profile",
    "id": "66bcfadc0a1b2c3d4e5f6071",
    "action": "update"
  }
}

Kein Name, keine Anschrift, kein Geburtsdatum. Für die Daten: GET /v1/members/66bcfadc… mit deinem Token.

Signatur prüfen

Jede Anfrage trägt Kopfzeilen:

HeaderInhalt
X-Artim-EventDer Ereignisname, z. B. unit.state.changed
X-Artim-DeliveryDie Kennung der Zustellung — dieselbe wie id im Körper
X-Artim-Webhook-IdWelcher deiner Endpunkte gemeint ist
X-Artim-Environmentproduction oder sandbox
X-Artim-Signaturet=<sekunden>,v1=<hmac>

Signiert wird "<sekunden>." + roher Körper mit HMAC-SHA256 und deinem Signing Secret. Der Zeitstempel steht mit im signierten Text: sonst ließe sich ein mitgeschnittener Aufruf später erneut abspielen. Wir verwerfen Signaturen, die älter als fünf Minuten sind — tu dasselbe.

import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t));
  if (!Number.isFinite(age) || age > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.` + rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Wichtig ist roh: der Körper, wie er ankam, Byte für Byte. Wer erst JSON parst und dann wieder serialisiert, prüft die Signatur einer anderen Zeichenkette.

Das Secret erzeugen wir beim Anlegen und zeigen es genau einmal. Es beginnt mit whsec_live_ oder whsec_sbx_ — daran siehst du, aus welcher Umgebung eine Nutzlast kommt, noch bevor du sie liest.

Content type

Zwei Verpackungen, derselbe Inhalt:

  • application/json — der Körper ist das JSON.
  • application/x-www-form-urlencoded — der Körper ist payload=<json>, für Empfänger, die nur Formulare kennen.

Signiert wird in beiden Fällen der rohe Körper, nicht das JSON darin.

Wiederholungen

Ein Versuch hat zehn Sekunden. Nimm die Nutzlast entgegen, antworte mit 2xx und arbeite danach — wer im Handler die halbe Buchhaltung durchrechnet, läuft in die Zeitüberschreitung.

Antwortet dein Endpunkt nicht mit 2xx, versuchen wir es erneut: nach einer Minute, nach fünf, nach dreißig, nach zwei Stunden, nach sechs. Danach steht die Zustellung als failed im Protokoll und lässt sich in der Konsole von Hand erneut senden.

Zwanzig fehlgeschlagene Zustellungen in Folge legen den Endpunkt still. Du erfährst es über webhook.endpoint.disabled — zugestellt an deine anderen Endpunkte, denn der stillgelegte ist ja gerade der, der nicht antwortet.

Dieselbe Kennung kann zweimal kommen: eine Wiederholung nach einer Zeitüberschreitung, obwohl dein Dienst schon verbucht hatte. Merke dir id und verwirf, was du kennst.

SSL-Prüfung

Standardmäßig prüfen wir das Zertifikat deines Endpunkts. Abschalten lässt sich das für eigene Testempfänger — mit der Folge, dass jeder in der Leitung die Nutzlast mitlesen und verändern kann. Die Signatur schützt dann noch die Echtheit, nicht die Vertraulichkeit.

Die Adresse muss https sein und aus dem Internet erreichbar: Endpunkte, die auf interne Adressen zeigen, lehnen wir beim Anlegen ab.

Ausprobieren

Ein frisch angelegter Endpunkt bekommt sofort einen ping — wer die Signaturprüfung falsch gebaut hat, merkt es in derselben Minute. Später jederzeit über Test senden im Zeilenmenü. ping ist nicht abonnierbar; er kommt immer.

Im Zustellprotokoll steht je Versuch der Status, die Dauer und der Fehlertext deines Servers — der bleibt gekürzt auf zwei Kilobyte, aber es reicht, um den eigenen Stacktrace wiederzuerkennen.