Die meisten Shopify-Webhook-Bugs in der lokalen Entwicklung laufen auf eine Zeile hinaus: HMAC ist base64-kodiert, nicht hex. Wenn du .digest('hex') aufrufst und mit X-Shopify-Hmac-Sha256 vergleichst, wird die Prüfung nie bestehen — selbst mit dem korrekten Shared Secret. Wir haben Senior-Engineers einen Nachmittag an diesem einen Zeichen verlieren sehen.
Die Base64-Falle
Stripe nutzt hex. GitHub nutzt hex (mit sha256=-Präfix). Shopify nutzt base64. Drei Anbieter, drei Kodierungen. Deine erste Version des Verifiers kopiert mit ziemlicher Sicherheit ein Snippet aus einem anderen Projekt und scheitert lautlos.
So sollte die Verifikation aussehen:
const hmac = crypto
.createHmac('sha256', SHOPIFY_API_SECRET)
.update(rawBody) // raw body, not JSON-parsed
.digest('base64'); // base64, not hex
const valid = crypto.timingSafeEqual(
Buffer.from(hmac),
Buffer.from(req.headers['x-shopify-hmac-sha256']),
);
Zwei Dinge zählen neben der Kodierung. Der Body müssen die rohen, ungeparsten Bytes sein. Und der Vergleich muss timing-safe sein — String-Gleichheit leakt Information Byte für Byte.
Warum ein Tunnel das einfacher macht
Du kannst auch Shopifys shopify app dev-Befehl nutzen, der einen internen Tunnel startet. Funktioniert. Aber er wickelt deinen Dev-Server in einen eigenen Prozess, verschluckt Logs auf bestimmte Weise und gibt dir keinen Replay-Button. Für App-Entwicklung jenseits von „Hello World“ spart eine stabile öffentliche URL plus ein localhost-Tunnel mit Request-Capture mehr Zeit, als die CLI beim Setup einspart.
npx portpreview 3000
Du fügst die HTTPS-URL in die Webhook-Konfiguration deiner App im Partners-Dashboard ein, triggerst ein Event aus einem Test-Store, und der Request landet in deinem lokalen Handler mit allen Headern intakt.
Test-Stores: der Teil, den die Shopify-Doku überfliegt
Zwei Dinge, die du wissen solltest, bevor du etwas verkabelst:
- Development-Stores feuern alle Webhook-Events. Bestellerstellung, Fulfillment, Inventar — alles. Du musst nichts faken; installiere deine App einfach auf einem Dev-Store und klick herum.
- Das Webhook-Secret unterscheidet sich pro App und pro Lieferkanal. Wenn du auch EventBridge oder Pub/Sub abonnierst, ist das HMAC-Verhalten anders. Hier reden wir über reine HTTPS-Webhook-Lieferung.
Schritt-für-Schritt-Setup
- Starte deine App lokal auf dem Port, den du nutzt (3000 ist üblich).
- Führe
npx portpreview 3000aus und kopiere die ausgegebene HTTPS-URL. - Öffne im Shopify-Partners-Dashboard deine App und gehe zu Configuration → Webhooks.
- Setze den Webhook-Endpoint auf
https://your-tunnel.portpreview.dev/api/webhooks/shopify(oder welchen Pfad deine App nutzt). - Installiere die App auf einem Dev-Store, dann triggere ein Event — erstelle eine Entwurfsbestellung, fulfille einen Artikel, ändere das Inventar.
- Beobachte, wie der Request landet. Inspiziere Header und Body. Replaye den erfassten Request nach jedem Handler-Fix.
Die Fehler, die wir immer wieder sehen
Body vor der Verifikation geparst
Wenn du app.use(express.json()) vor deine Webhook-Route setzt, sind die rohen Bytes weg, wenn du verifizieren willst. Mounte einen Raw-Body-Parser nur auf dem Webhook-Pfad, oder zieh den Body manuell aus dem Request-Stream, bevor JSON-Parsing passiert.
Verwechselte Secrets
Das Partners-App-Secret ist nicht dasselbe wie das Storefront-API-Token. Der Webhook-HMAC nutzt das App-Secret. Wenn du auf shp_xxx in deiner Env-Datei starrst, hast du das falsche genommen.
Test-Events, die identisch aussehen
Shopifys „Send test notification“-Button liefert ein synthetisches Event mit Platzhalter-Body. Die Signatur auf diesem Test-Event ist echt, aber das Payload ist fest. Für realistisches Payload-Shape-Testing triggere Events aus dem Dev-Store selbst.
Replay ist für App-Dev nicht verhandelbar
Shopify wiederholt fehlgeschlagene Webhooks bis zu 48 Stunden mit exponentiellem Backoff. Das ist großzügig, aber beim Iterieren willst du nicht auf den nächsten Retry warten. Erfasse die erste Lieferung in der Request-History deines Tunnels und replaye sie auf Abruf gegen deinen lokalen Handler.
Wann lokal nicht mehr reicht
Shop-Registrierungs-Flows, GDPR-Webhooks und Subscription-Billing-Änderungen sind leichter gegen eine deployte Umgebung zu validieren, weil sie mit Shopifys eigenem Account-State interagieren. Für alles andere — Payload-Parsing, Signatur-Verifikation, Business-Logik — ist lokal schneller.
Für die zugrunde liegende Signatur-Mathematik über alle Anbieter lies unseren Webhook-Signatur-Verifikations-Leitfaden. Tritt der PortPreview-Warteliste bei, wenn du einen Tunnel mit eingebautem Replay willst.