Was das Gesetz ab 19.06.2026 verlangt
§ 356a BGB, verkündet im BGBl. I Nr. 31 vom 5.2.2026, verlangt für jeden B2C-Shop in der EU einen zweistufigen Widerrufsbutton. Stufe 1 heißt „Vertrag widerrufen“. Stufe 2 heißt „Widerruf bestätigen“. Dazwischen ein Formular mit Name plus Kontaktmittel als Pflichtfelder, danach eine Eingangsbestätigung per E-Mail auf einem dauerhaften Datenträger nach § 126b BGB.
Kein Login, keine Scroll-Barriere. Verfügbar während der gesamten Widerrufsfrist. Das Abmahn-Risiko bei Verstössen liegt nach RVG bei 500 bis 2.000 €, plus einer automatischen Fristverlängerung auf zwölf Monate und vierzehn Tage nach § 356 Abs. 3 BGB.
Die zwei Wege für Shopware 6
Weg A: Twig-Template-Extension direkt in deinem Theme. Schnell, aber gebunden an dein Theme. Bei Theme-Updates musst du aufpassen.
Weg B: Eigenes Storefront-Plugin, das ein Twig-Snippet registriert. Sauber, update-sicher, kann per Admin deaktiviert werden. Ist der empfohlene Shopware-Weg.
Weg A: Twig-Template-Extension im Theme
Shopware 6 nutzt Block-basierte Twig-Templates. Du kannst jeden Block überschreiben oder erweitern. Der Footer lebt in @Storefront/storefront/layout/footer/footer.html.twig. Wir hängen uns an den Block base_footer_inner_container.
{# Resources/views/storefront/layout/footer/footer.html.twig #}
{% sw_extends '@Storefront/storefront/layout/footer/footer.html.twig' %}
{% block base_footer_inner_container %}
{{ parent() }}
<script
src="https://widerrufbutton.net/widget/v1/wh.js"
data-shop-id="{{ config('WiderrufButton.config.shopId') }}"
data-position="footer"
data-lang="de"
async
defer
></script>
{% endblock %}Der Aufruf config(...) greift auf die Plugin-Konfiguration zu. Wenn du keine Konfiguration aufbauen willst, kannst du auch direkt den Widget-Key reinschreiben. Für eine saubere Lösung braucht es aber Weg B.
Weg B: Eigenes Plugin
Ein Shopware-6-Plugin ist im Wesentlichen ein Symfony-Bundle mit einer composer.json, einer Plugin-Klasse und einer services.xml für die Dependency-Injection.
Die Mindest-Struktur sieht so aus:
custom/plugins/WiderrufButton/
├── composer.json
├── src/
│ ├── WiderrufButton.php
│ ├── Resources/
│ │ ├── config/
│ │ │ ├── config.xml
│ │ │ └── services.xml
│ │ └── views/
│ │ └── storefront/
│ │ └── layout/
│ │ └── footer/
│ │ └── footer.html.twigDie config.xml definiert das Eingabefeld für den Widget-Key im Shopware-Admin:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
<card>
<title>WiderrufButton</title>
<input-field>
<name>shopId</name>
<label>Widget-Key</label>
<helpText>Aus dem WiderrufButton-Dashboard</helpText>
</input-field>
</card>
</config>Die services.xml bleibt leer, solange du keine eigenen Services brauchst. Das Twig-Template überschreibt wie in Weg A den Footer-Block, ruft aber die Plugin-Konfiguration via config('WiderrufButton.config.shopId') auf.
Plugin aktivieren und testen
Im Shopware-Verzeichnis die Befehle in dieser Reihenfolge:
bin/console plugin:refresh
bin/console plugin:install --activate WiderrufButton
bin/console theme:compile
bin/console cache:clearDanach erscheint das Plugin im Admin unter Erweiterungen. Dort den Widget-Key aus deinem WiderrufButton-Dashboard eintragen. Speichern. Shop öffnen, Footer prüfen.
HTTP-Cache und CSP beachten
Shopware 6 cached aggressiv. Nach Theme-Compile den HTTP-Cache leeren, sonst siehst du das Script nicht sofort. In der Shopware-Konfiguration ist das bin/console http:cache:clear.
Wenn du eine Content Security Policy aktiv hast, muss widerrufbutton.net in den script-src- und connect-src-Direktiven stehen. Der Browser lädt das Script von dort und schickt den Widerruf an die API-Route auf derselben Domain.
Warum Shadow DOM wichtig ist
Shopware-Themes bringen eigenes CSS mit. Ohne Kapselung würde der Button-Style mit dem Theme kollidieren, die Farben falsch laufen, das Modal an der falschen Stelle erscheinen. Das Widget läuft deshalb im Shadow DOM. Sein CSS ist komplett isoliert, Shopware-Styles können nichts überschreiben, und dein Theme bleibt unberührt.
Dasselbe gilt in die andere Richtung: Der Button kann deinem Theme nichts tun. Keine globalen Styles, keine Klassennamen, die kollidieren, keine Schriftarten, die ungewollt überschrieben werden.
Mehrsprachigkeit und Sales Channels
Shopware erlaubt mehrere Sales Channels mit unterschiedlichen Domänen. Für jede Domäne brauchst du im WiderrufButton-Dashboard einen eigenen Shop-Eintrag, damit die CORS-Prüfung gegen die korrekte Origin läuft. Die Widget-Keys unterscheiden sich, und du musst pro Sales Channel den richtigen Key an die Plugin-Konfiguration binden.
Details dazu stehen auf der Produktseite und in der Anleitung.
Checkliste vor dem Go-Live
- Widget-Key im Admin eingetragen und gespeichert
- Theme neu kompiliert, HTTP-Cache geleert
- Button im Footer sichtbar, ohne Scrollen erreichbar
- Formular-Submit mit Test-Daten, E-Mail kommt innerhalb von Sekunden
- Content Security Policy erlaubt die Widget-Domäne
- Im Dashboard unter Widerrufe erscheint der Test-Eintrag