Wie richtet man BigCommerce-Webhooks ein und überprüft sie?

Die meisten BigCommerce-Integrationen beginnen mit einer Abfrage. Sie richten ein Skript ein, das alle paar Minuten den „Orders“-Endpunkt, den „Products“-Endpunkt oder eine andere für Sie relevante Ressource überprüft, und die meisten dieser Überprüfungen liefern keine neuen Ergebnisse. Das funktioniert zwar, ist aber ineffizient und schränkt die Reaktionsgeschwindigkeit Ihres Systems auf Änderungen ein. Webhooks lösen dieses Problem auf elegante Weise: Anstatt dass Ihr Code bei BigCommerce nachfragt, ob sich etwas geändert hat, informiert BigCommerce Sie sofort, sobald dies der Fall ist. Die Registrierung eines Webhooks erfordert lediglich einen einzigen API-Aufruf. Das Vertrauen in die Daten, die anschließend an Ihrem Endpunkt ankommen, ist der Teil, der tatsächlich etwas Sorgfalt erfordert – und genau diesen Teil lassen die meisten Tutorials außer Acht.

Übersetzt mit DeepL.com (kostenlose Version)

Einen Webhook erstellen

Hierfür gibt es keine Umschaltfunktion im Dashboard; Webhooks sind ausschließlich über die API verfügbar. Sie können einen Webhook mit einer POST-Anfrage an den Endpunkt /v3/hooks erstellen.

POST https://api.bigcommerce.com/stores/{store_hash}/v3/hooks
X-Auth-Token: {access_token}
Content-Type: application/json
Accept: application/json

{
  "scope": "store/order/statusUpdated",
  "destination": "https://yourapp.example.com/webhooks",
  "is_active": true,
  "headers": {}
}

Als „scope“ gibt man das Ereignis an, über das man informiert werden möchte: „store/order/created“, „store/product/updated“, „store/cart/abandoned“ usw. Als „destination“ wird angegeben, wohin die Nutzdaten gesendet werden sollen; dabei muss es sich um eine HTTPS-Verbindung auf dem Standardport 443 handeln. Benutzerdefinierte Ports werden nicht unterstützt, versuchen Sie es also gar nicht erst. Das „headers“-Objekt ist optional, aber es lohnt sich trotzdem, es zu verwenden. Alle Schlüssel-Wert-Paare, die du dort hinterlegst, werden bei jedem Callback zurückgesendet. Das bietet dir eine einfache Möglichkeit, zusätzlich zur ordnungsgemäßen Signaturprüfung ein gemeinsames Geheimnis oder eine Basic-Authentifizierung einzubinden.

Einschränkungen, die man im Voraus kennen sollte

Einige Einschränkungen sind hier erst dann offensichtlich, wenn man bereits darauf gestoßen ist:

  • Es kann bis zu einer Minute dauern, bis ein neu erstellter Webhook tatsächlich zu funktionieren beginnt. Keine Panik, wenn Ihr erstes Testereignis nicht sofort angezeigt wird – der Webhook ist nicht defekt, sondern hat sich einfach noch nicht „aufgewärmt“.
  • Pro eindeutiger Kombination aus Shop, API-Client und Geltungsbereich stehen dir maximal 10 Webhooks zur Verfügung. Und pro Shop, Client, Geltungsbereich und Ziel ist jeweils nur ein Webhook zulässig – die doppelte Registrierung desselben Geltungsbereichs, der auf dieselbe URL verweist, funktioniert also nicht.
  • Webhooks sind nur für das Token sichtbar, das sie erstellt hat. Es gibt keine Möglichkeit, eine Liste aller Webhooks für alle Token eines Shops abzurufen, was wirklich ärgerlich ist, wenn du eine Multi-App-Konfiguration debugst und herausfinden willst, warum ein Ereignis nicht dort angezeigt wird, wo du es erwarten würdest.
  • Ein Abonnement wird nach 90 Tagen Inaktivität automatisch und ohne Benachrichtigung deaktiviert. Wenn du einen Bereich mit geringem Datenverkehr hast, lohnt es sich, eine regelmäßige Überprüfung einzubauen, ob der Webhook noch aktiv ist, anstatt erst drei Monate später festzustellen, dass er einfach aufgehört hat zu funktionieren.

Die Nutzlast gibt absichtlich nicht viele Informationen preis.

Wenn ein Ereignis ausgelöst wird, übergibt BigCommerce Ihnen nicht die vollständige Ressource. Es reicht gerade aus, um zu erkennen, dass sich etwas geändert hat und wo Sie danach suchen müssen:

{
  "store_id": "1000",
  "producer": "stores/abc123",
  "scope": "store/order/statusUpdated",
  "data": {
    "type": "order",
    "id": 173331
  },
  "hash": "...",
  "created_at": 1561479335
}

Wenn Sie die Bestelldetails tatsächlich benötigen, erfolgt dies über einen separaten Aufruf der REST-API unter Verwendung der in „data“ enthaltenen ID. Das überrascht viele beim ersten Mal; man hat das Gefühl, der Webhook sollte einem einfach alles auf Anhieb liefern, doch die geringe Datenmenge ist beabsichtigt. Dadurch bleibt die Übertragung schnell, und es wird sichergestellt, dass die Daten, auf deren Grundlage Sie handeln, nicht bereits veraltet sind, wenn Sie dazu kommen, den Webhook-Text zu lesen.

Die meisten Menschen verzichten darauf, die Signatur zu überprüfen

Die Überprüfung der Signatur ist der Schritt, den viele überspringen – und ehrlich gesagt ist genau dieser Schritt am wichtigsten. Jeder, der zufällig auf Ihre Ziel-URL stößt, kann eine gefälschte Nutzlast per POST an diese senden, wenn Sie nicht überprüfen, woher die Anfrage tatsächlich stammt. BigCommerce signiert seine Webhook-Anfragen gemäß der Standard-Webhooks-Spezifikation, sodass sich die Überprüfung auf drei Header beschränkt: „webhook-id“, „webhook-timestamp“ und „webhook-signature“.

Die Signatur selbst ist ein HMAC-SHA256, der über {webhook-id}.{webhook-timestamp}.{raw request body} berechnet und mit dem Client-Secret Ihrer App signiert wird. Auf Ihrer Seite berechnen Sie denselben HMAC mit Ihrem Client-Secret neu und vergleichen ihn mit der von BigCommerce übermittelten Signatur. Der Zeitstempel dient speziell dem Schutz vor Replay-Angriffen; überprüfen Sie daher auch, ob er tatsächlich aktuell ist – eine Übereinstimmung der Signaturen allein reicht nicht aus.

So können Sie die Signatur überprüfen:

import crypto from "crypto";

function verifyWebhookSignature(rawBody, headers, clientSecret) {
  const webhookId = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const receivedSignature = headers["webhook-signature"]; // format: "v1,<base64>"

  // reject anything more than a few minutes old, guards against replay
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (age > 300) return false;

  const signedContent = `${webhookId}.${timestamp}.${rawBody}`;
  const secretBytes = Buffer.from(clientSecret, "base64");
  let expectedSignature = crypto
    .createHmac("sha256", secretBytes)
    .update(signedContent)
    .digest("base64");

  const receivedSigValue = receivedSignature.split(",")[1] || "";

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(receivedSigValue)
  );
}

Zwei Dinge sollten hier gesondert hervorgehoben werden, da dies die beiden Fehlerquellen sind, die beim ersten Versuch häufig auftreten. Damit dies überhaupt funktioniert, benötigen Sie den rohen, noch nicht geparsten Body. Wenn der Body-Parser Ihres Frameworks die Anfrage zum Zeitpunkt der Überprüfung bereits in ein JSON-Objekt umgewandelt hat, stimmt der Hash nicht überein, da sowohl Leerzeichen als auch die Reihenfolge der Schlüssel einen Einfluss darauf haben. Rufen Sie den rohen Body ab, bevor irgendeine Parsing-Middleware darauf zugreift. Und verwenden Sie „timingSafeEqual“ anstelle eines einfachen „===“ – ein normaler String-Vergleich gibt Zeitinformationen preis, die es theoretisch ermöglichen, die Signatur Byte für Byte zu erraten.

Schnell antworten, die eigentliche Arbeit später erledigen

BigCommerce erwartet eine schnelle HTTP-200-Antwort – und das ist auch so gemeint. Wenn Ihr Endpunkt zu lange braucht, um zu antworten, wird die Übermittlung als fehlgeschlagen markiert und in die Wiederholungswarteschlange verschoben, selbst wenn Ihr Code eigentlich einwandfrei funktioniert hat – nur eben langsam. Die Lösung besteht darin, den Webhook sofort zu bestätigen und die eigentliche Verarbeitung an eine andere Stelle zu verlagern – eine Warteschlange, einen Hintergrundjob oder was auch immer Ihr Stack dafür bereits verwendet:

app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const isValid = verifyWebhookSignature(req.body, req.headers, process.env.BC_CLIENT_SECRET);
  if (!isValid) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  res.sendStatus(200);
  processEventAsync(event);
});

Wiederholungsversuche verhalten sich nicht ganz so, wie man vermuten würde

Hier ist ein Detail, das man leicht übersieht, bis es einem zum Verhängnis wird: BigCommerce entscheidet anhand der Reaktion Ihrer gesamten Domain, ob ein Wiederholungsversuch unternommen wird – nicht für jeden einzelnen Webhook. Wenn Sie also zwei separate Webhooks haben, die auf yourapp.com/webhook-1 und yourapp.com/webhook-2 verweisen, kann ein Fehler bei einem davon das Wiederholungsverhalten des anderen beeinflussen, da BigCommerce die Domain als Ganzes überwacht und nicht jeden Endpunkt einzeln. Das sollten Sie bedenken, wenn Sie mehrere Abonnements betreiben und davon ausgehen, dass diese jeweils unabhängig voneinander ausfallen und wiederhergestellt werden. Das ist jedoch nicht der Fall.

Fehlgeschlagene Übermittlungen werden etwa 48 Stunden lang erneut versucht. Nach Ablauf dieses Zeitfensters deaktiviert sich der Webhook von selbst. Ein versteckter Fehler, der Ihren Handler zwei Tage lang lahmlegt, hinterlässt also nicht nur einen Rückstand, den Sie aufarbeiten müssen, sondern führt letztendlich dazu, dass die Ereignisse gänzlich ausbleiben. Es lohnt sich, auf Ihrer Seite eine Art Warnung für eine Reihe aufeinanderfolgender 4xx- oder 5xx-Antworten einzurichten, anstatt erst durch ein Support-Ticket zu erfahren, dass die Auftragssynchronisierung bereits vor drei Wochen unbemerkt eingestellt wurde.

Rauschen mit Datenfiltern reduzieren

Wenn Sie sich nur für einen Teil der Ereignisse innerhalb eines bestimmten Bereichs interessieren, unterstützt BigCommerce Datenfilter, sodass Sie nicht selbst irrelevante Ereignisse aussortieren müssen, nachdem diese bereits eingegangen sind. Anstatt jedes einzelne „store/order/statusUpdated“-Ereignis zu erhalten, unabhängig davon, wie sich der Status geändert hat, können Sie nach Werten im Datenobjekt der Nutzlast filtern und lassen sich nur diejenigen zusenden, auf die Sie tatsächlich reagieren möchten. Es lohnt sich, dies von Anfang an einzurichten, anstatt Filterlogik in Ihren Handler zu schreiben, die die API eigentlich für Sie hätte übernehmen können.

Kontaktieren Sie uns: wargis@bay20.com/manish@bay20.com | +91-9582784309/+91-8800519185 oder besuchen Sie Bay20 noch heute!