> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Interna des Mercure-Broadcast-Treibers

> Ein Rundgang auf Quellcode-Ebene durch den neu in das Laravel-Framework aufgenommenen Mercure-Broadcast-Treiber: seine HTTP/SSE-Architektur, die getrennten JWTs für Publish und Subscribe, die Kanalautorisierung über ein einziges Cookie sowie Ende-zu-Ende-verschlüsselte Kanäle.

<Warning>
  Der hier beschriebene `MercureBroadcaster` wurde mit [laravel/framework PR #61474](https://github.com/laravel/framework/pull/61474) hinzugefügt (gemergt am 10. September 2026). Zum Zeitpunkt der Erstellung ist er noch in keinem getaggten Release enthalten und auf der [offiziellen Dokumentationsseite](https://laravel.com/docs/broadcasting) nicht dokumentiert. Diese Seite ist eine Vorabbetrachtung auf Basis des Quellcodes und der Docblocks — prüfen Sie das Changelog von `laravel/framework`, bevor Sie ihn produktiv einsetzen.
</Warning>

## Überblick

Die Broadcasting-Schicht von Laravel liefert seit Langem [Reverb](/de/broadcasting), Pusher und Ably als „WebSocket-native" Treiber. Neu hinzugekommen ist der Treiberwert `mercure` in `config/broadcasting.php`.

[Mercure](https://mercure.rocks) ist ein Echtzeitprotokoll, das auf Server-Sent Events (SSE) statt auf einer bidirektionalen WebSocket-Verbindung aufsetzt. Es verhält sich eher wie Long Polling über HTTP/1.1 oder HTTP/2, woraus sich einige besondere Eigenschaften ergeben:

* Es passiert reguläre HTTP-Infrastruktur (Reverse Proxies, CDNs, Load Balancer) transparent.
* Clients lassen sich allein mit der `EventSource`-API des Browsers implementieren — eine dedizierte Client-Bibliothek ist nicht erforderlich.
* [FrankenPHP](/de/blog/laravel-cloud) bringt einen eingebauten Mercure-Hub mit, sodass zum Ausprobieren keine zusätzliche Infrastruktur nötig ist.

```mermaid theme={null}
flowchart LR
    A["Server<br>broadcast(event)"] --> B["MercureBroadcaster"]
    B --> C["Mercure-Hub<br>(Hub / FrankenPhpHub)"]
    C -->|"SSE (EventSource)"| D["Browser<br>Laravel Echo"]
    D --> E["Echtzeit-<br>UI-Aktualisierung"]
```

## Der neue `driver`-Wert

Der Kommentar mit den unterstützten Treibern am Anfang von `config/broadcasting.php` listet nun auch `mercure` auf:

```php theme={null}
// Supported: "reverb", "pusher", "ably", "mercure", "redis", "log", "null"
```

Eine Beispielkonfiguration für die Verbindung wird ebenfalls mitgeliefert:

```php theme={null}
'mercure' => [
    'driver' => 'mercure',
    'url' => env('MERCURE_URL'),
    'public_url' => env('MERCURE_PUBLIC_URL'),
    'secret' => env('MERCURE_JWT_SECRET'),
    'encryption_key' => env('MERCURE_ENCRYPTION_KEY'),
    'claims' => [
        'iss' => env('MERCURE_JWT_ISSUER'),
        'client_id' => env('APP_NAME'),
    ],
    'cookie_name' => env('MERCURE_COOKIE_NAME'),
    'subscribe_expiration' => (int) env('MERCURE_SUBSCRIBE_EXPIRATION', 5),
],
```

Wird `url` weggelassen, fällt der Treiber auf den in FrankenPHP eingebauten Mercure-Hub (die Funktion `mercure_publish()`) zurück. Diese Entscheidung fällt in `CreatesMercureDrivers::mercure()`:

```php theme={null}
public function mercure(array $config)
{
    if (empty($config['url'])) {
        return $this->frankenPhpMercure($config);
    }

    // ...
}
```

Betreiben Sie einen externen Hub, sollte `url` auf die Management-API zum Publizieren zeigen, während `public_url` die URL ist, mit der sich der Browser verbindet. Diese Trennung unterstützt Setups, in denen das Publizieren über ein internes Netzwerk (etwa Docker Compose) läuft, während nur die öffentliche URL für den Browser sichtbar ist.

## Getrennte Publish- und Subscribe-Tokens

Mercure ist ein JWT-basiertes Protokoll zur Zugriffskontrolle. Der Trait `CreatesMercureDrivers` baut **getrennte Token-Factories** für das Publizieren (Server → Hub) und das Abonnieren (Browser → Hub) auf:

* `secret`, `publish_secret` und `subscribe_secret` lassen sich jeweils unabhängig konfigurieren und fallen auf `secret` zurück.
* Analog können `algorithm`, `publish_algorithm` und `subscribe_algorithm` seitengetrennt gesetzt werden (Standard: `HS256`).
* HS256 verlangt ein Secret von mindestens 32 Byte, HS384 mindestens 48 und HS512 mindestens 64; andernfalls wird eine `InvalidArgumentException` geworfen.

```php theme={null}
protected function mercureSecret(array $config, string $side)
{
    $secret = ($config[$side.'_secret'] ?? null) ?: ($config['secret'] ?? null);

    // ...

    $minimumLength = ['HS256' => 32, 'HS384' => 48, 'HS512' => 64][$algorithm] ?? 0;

    if (strlen($secret) < $minimumLength) {
        throw new InvalidArgumentException(/* ... */);
    }

    return $secret;
}
```

Das Publish-Token trägt `Grant::ACTION_PUBLISH` für alle Topics (`*`) und wird vom `CachingTokenProvider` memoisiert, sodass nicht bei jedem einzelnen Broadcast-Aufruf ein neues JWT erzeugt wird.

## Kanalautorisierung über ein einziges Cookie

Der größte architektonische Unterschied zu den WebSocket-Treibern besteht darin, dass die Autorisierung der Abonnenten über **ein einziges Cookie** abgewickelt wird. `MercureBroadcaster::auth()` nimmt ein `channel_names`-Array entgegen (begrenzt auf 100 Einträge pro Anfrage), prüft die Autorisierung pro Kanal und stellt anschließend ein einzelnes Autorisierungs-Cookie aus, das alle Kanäle abdeckt:

```php theme={null}
public function auth($request)
{
    $channelNames = (array) $request->input('channel_names', []);

    if ($channelNames === [] ||
        count($channelNames) > 100 ||
        $channelNames !== array_filter($channelNames, 'is_string')) {
        throw new AccessDeniedHttpException;
    }

    // Autorisierung pro Kanal auswerten und Grants aufsammeln ...

    return (new JsonResponse([/* ... */]))
        ->cookie($this->makeAuthorizationCookie($request, $grants, $user));
}
```

Entscheidend ist: Ein einzelner abgelehnter Kanal lässt die gesamte Antwort nicht scheitern — er wird in der Antwortnutzlast lediglich mit `denied: true` markiert, während die übrigen autorisierten Kanäle weiterhin funktionieren. Das ist wichtig, weil Mercure viele Topics über eine einzige EventSource-Verbindung multiplext, sodass Kanäle mitten in einer Sitzung hinzugefügt oder entfernt werden können, ohne die Verbindung abbauen zu müssen.

Presence-Kanäle erhalten eine andere `Grant`-Form als reguläre `private`- oder `private-encrypted`-Kanäle; sie zielen auf das Subscribe-URL-Muster ab, das die [Subscription API](https://mercure.rocks/docs/hub/concepts/active-subscriptions) von Mercure verwendet.

## Ende-zu-Ende-verschlüsselte Kanäle

Kanäle mit dem Präfix `private-encrypted-` werden als Ende-zu-Ende-verschlüsselt (E2EE) behandelt — das bedeutet, dass selbst der Mercure-Hub die Nutzdaten nie zu sehen bekommt. Diese Fähigkeit bieten die bestehenden Pusher- und Reverb-Treiber nicht.

Wird ein base64-kodierter, 32 Byte langer `encryption_key` gesetzt, aktiviert das den `ChannelEncrypter`. Dieser verpackt Eventnamen, Nutzdaten und Socket-ID zu einer JWE (JSON Web Encryption), bevor `broadcast()` sie versendet. Der Hub leitet ausschließlich verschlüsselte Bytes weiter.

```php theme={null}
'encryption_key' => env('MERCURE_ENCRYPTION_KEY'),
```

```shell theme={null}
php -r "echo base64_encode(random_bytes(32));"
```

Abonnenten entschlüsseln im Browser mithilfe des `jwk`-Feldes (JSON Web Key), das die `auth()`-Antwort zurückliefert — ein von der Mercure-Spezifikation empfohlener Out-of-Band-Schlüsselaustausch, bei dem der Hub den Schlüssel niemals erfährt.

<Info>
  Presence-Kanäle können nicht verschlüsselt werden, da ihre Mitgliederliste konstruktionsbedingt über die Subscription-API des Hubs läuft — und das ist mit E2EE nicht vereinbar.
</Info>

## Whispers (direkte Nachrichten zwischen Clients)

Ist `client_events` aktiviert (Standardwert), erhält jeder geschützte Kanal ein eigenes „Whisper-Topic", auf dem Abonnenten publizieren dürfen:

```php theme={null}
if ($this->clientEvents && $whisperTopics !== []) {
    $grants[] = new Grant([Grant::ACTION_SUBSCRIBE, Grant::ACTION_PUBLISH], $whisperTopics);
}
```

Das eigentliche Topic des Kanals bleibt serverseitig, sodass ein Client kein vom Server ausgehendes Event fälschen kann. Das Prinzip ähnelt dem „Client Events"-Feature von Pusher, hält die Autorisierung durch die getrennten Topics aber sauber voneinander getrennt.

## Topic-Benennung

Da Mercure-Hubs häufig von mehreren Anwendungen gemeinsam genutzt werden, werden Topics unter einem `topic_prefix` in einem Namensraum abgelegt, um Kollisionen zu vermeiden. Der Standardwert lautet:

```php theme={null}
protected string $topicPrefix = 'https://laravel.alt/echo/',
```

`.alt` ist ein reservierter, nicht auflösbarer DNS-Suffix ([RFC 9476](https://www.rfc-editor.org/rfc/rfc9476.html)) und kann daher niemals mit einer realen Domain kollidieren. Kanalnamen werden gemäß [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986.html) URL-kodiert in ein einzelnes Pfadsegment gepackt:

```php theme={null}
protected function channelTopic($channelName)
{
    return $this->topicPrefix.'channel/'.rawurlencode($channelName);
}
```

## Cookie-Domain und Fehlermeldungen

Da ein Mercure-Hub häufig auf einer anderen Subdomain läuft als die Anwendung (etwa `mercure.example.com` gegenüber `app.example.com`), wirft der Treiber bei einer misslungenen Auflösung einer gemeinsamen Cookie-Domain eine aussagekräftige Exception:

```php theme={null}
throw new BroadcastException(sprintf(
    'Mercure error: %s. Adjust the Mercure "public_url" configuration value so the hub [%s] shares a registrable domain with the application host [%s].',
    rtrim($e->getMessage(), '.'), $this->hub->getPublicUrl(), $request->getHost()
), 0, $e);
```

Verwenden Sie einen Cookie-Namen mit dem Präfix `__Secure-` oder `__Host-`, prüft der Treiber beim Booten außerdem, ob `public_url` per HTTPS ausgeliefert wird:

```php theme={null}
if (str_starts_with($hub->getCookieName(), '__') &&
    parse_url($hub->getPublicUrl(), PHP_URL_SCHEME) === 'http') {
    throw new InvalidArgumentException(/* ... */);
}
```

## Wahl zwischen Reverb, Pusher/Ably und Mercure

| Treiber       | Transport  | Infrastruktur                                      | E2E-Verschlüsselung       |
| ------------- | ---------- | -------------------------------------------------- | ------------------------- |
| Reverb        | WebSocket  | selbst gehosteter Server erforderlich              | nein                      |
| Pusher / Ably | WebSocket  | SaaS                                               | nein                      |
| Mercure       | SSE (HTTP) | selbst gehosteter Hub oder in FrankenPHP eingebaut | ja (`private-encrypted-`) |

Mercure eignet sich gut, wenn Sie keine dauerhafte bidirektionale Verbindung benötigen — für Benachrichtigungen, Fortschrittsanzeigen oder chatartige Anwendungsfälle, die vorwiegend vom Server zum Client fließen. Wenn Sie bereits FrankenPHP einsetzen, funktioniert Mercure ganz ohne zusätzliche Infrastruktur.

## Verwandte Seiten

* [Grundlagen des Broadcastings](/de/broadcasting)
* [Laravel Cloud](/de/blog/laravel-cloud) (FrankenPHP-basierte Laufzeitumgebung)


## Related topics

- [Broadcasting](/de/broadcasting.md)
- [Weiterführende Themen](/de/advanced/index.md)
- [Illuminate\Support\Manager — Anatomie des Treibersystems](/de/advanced/manager.md)
- [Core-Paket und eigene Treiber - Feedable](/de/packages/feedable/core.md)
- [Amazon-Bedrock-Treiber für das Laravel AI SDK](/de/packages/laravel-amazon-bedrock.md)
