shopware/app-bundle
A Symfony bundle for building Shopware apps, providing the core infrastructure for app manifests, lifecycle events, webhooks, and platform integration. It streamlines app registration, configuration, and communication so developers can add custom features to Shopware 6.
First decision: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/app.php entry, and no Artisan command. If your Shopware app backend is (or can be) a Symfony application, this bundle is the batteries-included path: it wires the registration handshake, signature verification, webhook routing, payment endpoints, and lifecycle events for you. If your backend must stay in Laravel, do not try to boot this bundle inside Laravel — use the lower-level shopware/app-php-sdk directly and port the handler logic. A common compromise is a small Symfony service that owns the /shopware/app/* routes and calls your Laravel API/queue.
Minimal setup in a Symfony host app:
composer require shopware/app-bundle
// config/bundles.php
return [
// ...
Shopware\AppBundle\ShopwareAppBundle::class => ['all' => true],
];
# config/packages/shopware_app.yaml — verify exact keys in
# src/DependencyInjection/Configuration.php, they are the source of truth
shopware_app:
app_name: '%env(SHOPWARE_APP_NAME)%'
app_secret: '%env(SHOPWARE_APP_SECRET)%'
Then set SHOPWARE_APP_NAME / SHOPWARE_APP_SECRET, clear the cache, and confirm the bundle's routes are loaded (bin/console debug:router | grep -i shopware).
Where to look first, in order: the README, src/DependencyInjection/Configuration.php (all config keys), src/Resources/config/routes.* (the URLs Shopware will call), and src/Attribute / src/Handler (how handlers are discovered). Ignore the vendor docs until you've read those four.
First use case: get the registration confirmation working, then one webhook. Point a dev store at your backend via the Shopware Admin (App → your app → registration URL), install the app, and watch the confirmation request arrive. Once shopSecret is persisted and a product.written webhook round-trips, the rest is repetition.
Handler-per-topic, discovered by attribute/interface. The idiomatic pattern is one small class per webhook topic or payment method, with the bundle's DI doing the dispatch. Keep them thin: validate, normalize, push to a queue, return. This also maps cleanly to Laravel instincts — a handler is a listener, the bundle's registration is the EventServiceProvider.
Registration handshake. The install flow is: Shopware POSTs the registration with a shopware-app-signature over the query string; you verify it with the app secret, then respond with a JSON payload containing the per-shop shopSecret (plus your app's name/version and secret). Persist shopId, shopUrl, and that returned shopSecret before responding — every subsequent request is verified with the per-shop secret, not the global one. This is the single most common integration bug.
Lifecycle + shop repository. Implement the bundle/SDK shop repository against your own table (or Doctrine entity). installed, updated, and deleted lifecycle handlers should be idempotent: install may be retried, and delete should soft-delete rather than hard-delete so you retain audit history.
Webhook flow for scale. Verify signature on the raw body, then enqueue. Return 200 in milliseconds; Shopware retries on non-2xx and will eventually mark the app as failing. In a Symfony host, that's Messenger with a Doctrine/Redis transport. If Laravel owns the work, the handler should do an authenticated HTTP POST or push to a shared Redis queue.
Payment handlers. Treat these as state machines, not controllers: create/authorize/capture/refund are separate calls, and Shopware will call your return URL in the browser. Keep the redirect URL in config and derive it per shop, not from the request host.
Local development. Expose the backend with a tunnel (ngrok, symfony serve + proxy) and register the tunnel URL in the Shopware Admin. Keep a scratch dev store; app re-registration is cheap and lets you test the handshake repeatedly. For tests, use Symfony's KernelBrowser and generate a valid signature with a fixture secret so you can assert 200/401 paths without a real store.
Laravel coexistence patterns. Two workable shapes: (1) Symfony app at app.example.com/shopware/* behind the same load balancer as the Laravel app, sharing a database or calling internal APIs; (2) Laravel-only, using shopware/app-php-sdk's handler interfaces and reimplementing the ~5 routes yourself. The second is more code but avoids two frameworks in one deployment.
config() — is absent. Budget for Doctrine or a PDO layer, Symfony Messenger, and the Symfony DI config format. If the team is Laravel-only, the SDK route is usually cheaper than learning the bundle's conventions.shopware-app-signature against the untouched request body (webhooks) or the URL-decoded query string (registration) before any middleware, JSON decoding, or body parsing rewrites it. HMAC over a re-encoded array will fail intermittently.shopSecret per shop and use it for all later signatures.shopId + topic + payload hash or an event id, and make handlers retry-safe./shopware/app (or similar) on day one and version it if you must change.bin/console cache:clear after config changes; quote env values that contain special characters in YAML.composer.json, read src/ when behavior surprises you, and check the release tags — the metadata's last-release date is ahead of the current date, so confirm what's actually installable before planning around it.401 on bad signature, 400 on malformed payload, 200 on accepted-and-queued. Repeated 5xx responses cause Shopware to disable the app.shopId, source, and the nested payload envelope that handlers actually rely on.How can I help you explore Laravel packages today?