Alle Artikel
Shopifywebhook debugginglocal testinge-commerce

Shopify-Webhooks lokal testen ohne Überraschungen

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

  1. Starte deine App lokal auf dem Port, den du nutzt (3000 ist üblich).
  2. Führe npx portpreview 3000 aus und kopiere die ausgegebene HTTPS-URL.
  3. Öffne im Shopify-Partners-Dashboard deine App und gehe zu Configuration → Webhooks.
  4. Setze den Webhook-Endpoint auf https://your-tunnel.portpreview.dev/api/webhooks/shopify (oder welchen Pfad deine App nutzt).
  5. Installiere die App auf einem Dev-Store, dann triggere ein Event — erstelle eine Entwurfsbestellung, fulfille einen Artikel, ändere das Inventar.
  6. 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.

Häufig gestellte Fragen

Wie teste ich Shopify-Webhooks auf localhost?
Starte einen Tunnel wie PortPreview, hol dir eine öffentliche HTTPS-URL und registriere sie als Webhook-Endpoint im Shopify-Partners-Dashboard. Installiere deine App auf einem Development-Store, triggere Events, und die Lieferungen erreichen deinen lokalen Handler.
Warum schlägt meine Shopify-HMAC-Verifikation immer fehl?
Meistens ist es die Kodierung. Shopify signiert mit HMAC SHA-256 und base64-kodiert das Ergebnis. Wenn dein Code .digest('hex') statt .digest('base64') nutzt, schlägt jeder Vergleich fehl, selbst wenn das Secret korrekt ist.
Brauche ich shopify app dev oder kann ich jeden Tunnel nutzen?
Jeder Tunnel funktioniert. shopify app dev enthält einen eigenen Tunnel zur Bequemlichkeit, aber ein universeller Tunnel mit Request-Capture und Replay ist für anhaltende Entwicklung nützlicher.