Agent skill

Shopware Webhooks Skill

Receive and verify Shopware app system webhooks. Use when setting up Shopware webhook handlers, debugging shopware-shop-signature verification, handling per-shop app secrets, or processing events like checkout.order.placed, product.written, order.written, and customer.written.

Install this skill

npx skills add hookdeck/webhook-skills --skill shopware-webhooks


When to Use This Skill

  • How do I receive Shopware webhooks in my app backend?
  • How do I verify the shopware-shop-signature header?
  • Why does Shopware webhook signature verification fail?
  • Which secret signs webhooks, and how do I store one secret per shop?
  • How do I handle checkout.order.placed, product.written or order.written events?

Verification (core)

Shopware signs the raw request body with HMAC-SHA256 and sends the lowercase hex digest in shopware-shop-signature (no prefix). The key is the per-shop app secret your app returned during the registration handshake, so you look the secret up by source.shopId before verifying. Compare timing-safe.

Node:

const crypto = require('crypto');

function verify(rawBody, signature, secret) {
  if (!signature || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false;
  }
}

Python:

import hmac, hashlib

def verify(raw_body: bytes, signature: str, secret: str) -> bool:
    if not signature or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature.encode(), expected.encode())

For complete handlers with route wiring, per-shop secret lookup, event dispatch, and tests, see:

Express must read the body with express.raw({ type: '*/*' }) so any content type verifies against the exact bytes.

Payload and Secret Handling

{
  "data": { "payload": [ ... ], "event": "product.written" },
  "source": { "url": "https://shop.example.com", "appVersion": "1.0.0", "shopId": "...", "eventId": "..." },
  "timestamp": 1700000000
}
  • Parse the JSON only to read source.shopId, pick that shop's secret, then verify against the raw bytes before trusting anything.
  • The secret is assigned during registration (the handshake goes to the app's registration URL, not the webhook URL). It rotates on re-registration; shopware-shop-signature-previous carries the old signature on the confirmation request only.
  • source.eventId is stable across retries: use it for idempotency.
  • timestamp is signed; Shopware suggests rejecting very old requests. Optional, and retries re-run verification, so measure any window carefully.

Common Event Types

EventDescription
checkout.order.placedAn order was placed
product.writtenProduct inserted or updated (payload has primaryKeys, fetch via API)
order.writtenOrder inserted or updated
customer.writtenCustomer inserted or updated
state_enter.order.state.completedOrder entered the completed state
state_enter.order_transaction.state.paidPayment transaction entered paid

For the full event list, see Webhook Event Reference. Also available: state_leave.*, *.deleted, checkout.customer.*.

Important Headers

HeaderDescription
shopware-shop-signatureHex HMAC-SHA256 of the raw body, keyed with the shop's app secret
sw-versionShopware version (6.4.1.0+)
sw-context-languageLanguage ID of the context (6.4.5.0+)
sw-user-languageLocale of the user or context language (6.4.5.0+)

Environment Variables

SHOPWARE_APP_SECRETS={"<shopId>":"<appSecret>"}   # JSON map of shop ID to app secret (use a database in production)
SHOPWARE_APP_SECRET=your_app_secret               # Optional single-shop fallback for local testing

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 shopware --path /webhooks/shopware

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →