diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e993f44f..f8fe74b1 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,7 +1,6 @@
 version: 2
 updates:
-  -
-    package-ecosystem: github-actions
+  - package-ecosystem: github-actions
     directory: /
     schedule:
       interval: weekly
diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml
index f10f13af..e4c5bd91 100644
--- a/.github/linters/.markdown-lint.yml
+++ b/.github/linters/.markdown-lint.yml
@@ -18,19 +18,19 @@
 ###############
 # Rules by id #
 ###############
-MD004: false                  # Unordered list style
+MD004: false # Unordered list style
 MD007:
-  indent: 2                   # Unordered list indentation
+  indent: 2 # Unordered list indentation
 MD010:
   ignore_code_languages: [caddyfile]
 MD013: false
 MD026:
-  punctuation: ".,;:!。,;:"  # List of not allowed
-MD029: false                  # Ordered list item prefix
-MD033: false                  # Allow inline HTML
-MD036: false                  # Emphasis used instead of a heading
+  punctuation: ".,;:!。,;:" # List of not allowed
+MD029: false # Ordered list item prefix
+MD033: false # Allow inline HTML
+MD036: false # Emphasis used instead of a heading
 
 #################
 # Rules by tags #
 #################
-blank_lines: false  # Error on blank lines
+blank_lines: false # Error on blank lines
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index ff231e09..45b95197 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -22,4 +22,4 @@ Examples:
     docs: Add docs for X
 
     spec: Z disambiguation
--->
\ No newline at end of file
+-->
diff --git a/.github/workflows/cd-chart.yml b/.github/workflows/cd-chart.yml
index 332feae2..07cc7aa9 100644
--- a/.github/workflows/cd-chart.yml
+++ b/.github/workflows/cd-chart.yml
@@ -3,7 +3,7 @@ name: Release Chart
 on:
   push:
     tags:
-      - 'v*'
+      - "v*"
 
 jobs:
   release:
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index f92fad51..1c837d11 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -29,7 +29,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v5
         with:
-          go-version: '1.23'
+          go-version: "1.23"
           cache-dependency-path: |
             go.sum
             caddy/go.sum
@@ -68,7 +68,7 @@ jobs:
         if: startsWith(github.ref, 'refs/tags/v')
         uses: actions/attest-build-provenance@v1
         with:
-          subject-path: '${{ github.workspace }}/dist/**/mercure'
+          subject-path: "${{ github.workspace }}/dist/**/mercure"
 
       - name: Display version
         run: dist/caddy_linux_amd64_v1/mercure version
diff --git a/.github/workflows/ci-chart.yml b/.github/workflows/ci-chart.yml
index 541f2613..f5726202 100644
--- a/.github/workflows/ci-chart.yml
+++ b/.github/workflows/ci-chart.yml
@@ -26,7 +26,7 @@ jobs:
 
       - uses: actions/setup-python@v5
         with:
-          python-version: '3.10'
+          python-version: "3.10"
 
       - name: Set up chart-testing
         uses: helm/chart-testing-action@v2
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ce6d9df0..dc40f345 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,7 +11,7 @@ on:
       - v*.*.*
 
 env:
-  GO111MODULE: 'on'
+  GO111MODULE: "on"
 
 jobs:
   golangci:
@@ -23,7 +23,7 @@ jobs:
 
       - uses: actions/setup-go@v5
         with:
-          go-version: '1.23'
+          go-version: "1.23"
           cache-dependency-path: |
             go.sum
             caddy/go.sum
@@ -37,7 +37,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go: [ '1.21', '1.22', '1.23' ]
+        go: ["1.21", "1.22", "1.23"]
       fail-fast: false
     name: Test
     runs-on: ubuntu-latest
@@ -82,8 +82,8 @@ jobs:
 
       - uses: actions/setup-node@v4
         with:
-          node-version: '16'
-          cache: 'npm'
+          node-version: "16"
+          cache: "npm"
           cache-dependency-path: conformance-tests/package-lock.json
 
       - name: Install Playwrigth dependencies
diff --git a/.prettierignore b/.prettierignore
index 17bed166..da1b690f 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,5 +1,6 @@
 # We use standardjs for JavaScript and TypeScript files
 **/*.js
+**/*.ts
 
 # The spec is in the IETF flavor of Markdown
 spec/mercure.md
diff --git a/.vscode/launch.json b/.vscode/launch.json
index f410a5c4..de748d38 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,21 +1,21 @@
 {
-   "version": "0.2.0",
-    "configurations": [
-        {
-            "name": "Launch the hub",
-            "type": "go",
-            "request": "launch",
-            "mode": "auto",
-            "program": "${workspaceFolder}/caddy/mercure",
-            "env": {
-                "MERCURE_PUBLISHER_JWT_KEY": "!ChangeThisMercureHubJWTSecretKey!",
-                "MERCURE_SUBSCRIBER_JWT_KEY": "!ChangeThisMercureHubJWTSecretKey!",
-                "MERCURE_EXTRA_DIRECTIVES": "anonymous\nwrite_timeout 10s",
-                "GLOBAL_OPTIONS": "debug",
-                "SERVER_NAME": "localhost, host.docker.internal",
-                "CADDY_SERVER_EXTRA_DIRECTIVES": "tls internal"
-            },
-            "args": ["run", "--config", "../../dev.Caddyfile"]
-        }
-    ]
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Launch the hub",
+      "type": "go",
+      "request": "launch",
+      "mode": "auto",
+      "program": "${workspaceFolder}/caddy/mercure",
+      "env": {
+        "MERCURE_PUBLISHER_JWT_KEY": "!ChangeThisMercureHubJWTSecretKey!",
+        "MERCURE_SUBSCRIBER_JWT_KEY": "!ChangeThisMercureHubJWTSecretKey!",
+        "MERCURE_EXTRA_DIRECTIVES": "anonymous\nwrite_timeout 10s",
+        "GLOBAL_OPTIONS": "debug",
+        "SERVER_NAME": "localhost, host.docker.internal",
+        "CADDY_SERVER_EXTRA_DIRECTIVES": "tls internal"
+      },
+      "args": ["run", "--config", "../../dev.Caddyfile"]
+    }
+  ]
 }
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b180394a..2bc2dd93 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,13 +14,13 @@ If you include code from another project, please mention it in the Pull Request
 The commit message must follow the [Conventional Commits specification](https://www.conventionalcommits.org/).
 The following types are allowed:
 
-* `fix`: bugfix
-* `feat`: new feature
-* `docs`: change in the documentation
-* `spec`: spec change
-* `test`: test-related change
-* `perf`: performance optimization
-* `ci`: CI-related change
+- `fix`: bugfix
+- `feat`: new feature
+- `docs`: change in the documentation
+- `spec`: spec change
+- `test`: test-related change
+- `perf`: performance optimization
+- `ci`: CI-related change
 
 Examples:
 
@@ -61,10 +61,10 @@ Go to `http://localhost:3000` and enjoy!
 
 When you send a PR, make sure that:
 
-* You add valid test cases.
-* Tests are green.
-* You make a PR on the related documentation.
-* You make the PR on the same branch you based your changes on. If you see commits
+- You add valid test cases.
+- Tests are green.
+- You make a PR on the related documentation.
+- You make the PR on the same branch you based your changes on. If you see commits
   that you did not make in your PR, you're doing it wrong.
 
 ### Configuring Visual Studio Code
@@ -88,10 +88,10 @@ It is then converted in the [the "xml2rfc" Version 3 Vocabulary](https://tools.i
 
 To contribute to the protocol itself:
 
-* Make your changes
-* [Download Mmark](https://github.com/mmarkdown/mmark/releases)
-* [Download `xml2rfc` using pip](https://pypi.org/project/xml2rfc/): `pip install xml2rfc`
-* Generate the XML file: `mmark spec/mercure.md > spec/mercure.xml`
-* Validate the generated XML file and generate the text file: `xml2rfc --text --v3 spec/mercure.xml`
-* Remove non-ASCII characters from the generated `mercure.txt` file (example: K**é**vin)
-* If appropriate, be sure to update the reference implementation accordingly
+- Make your changes
+- [Download Mmark](https://github.com/mmarkdown/mmark/releases)
+- [Download `xml2rfc` using pip](https://pypi.org/project/xml2rfc/): `pip install xml2rfc`
+- Generate the XML file: `mmark spec/mercure.md > spec/mercure.xml`
+- Validate the generated XML file and generate the text file: `xml2rfc --text --v3 spec/mercure.xml`
+- Remove non-ASCII characters from the generated `mercure.txt` file (example: K**é**vin)
+- If appropriate, be sure to update the reference implementation accordingly
diff --git a/README.md b/README.md
index d2e742cd..69ad0c36 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 <h1 align="center"><a href="https://mercure.rocks"><img src="public/mercure.svg" alt="Mercure: Real-time Made Easy" title="Live Updates Made Easy"></a></h1>
 
-*Protocol and Reference Implementation*
+_Protocol and Reference Implementation_
 
 Mercure is a protocol for pushing data updates to web browsers and other HTTP clients in a convenient, fast, reliable, and battery-efficient way.
 It is especially useful to publish async and real-time updates of resources served through web APIs, to reactive web and mobile apps.
@@ -14,9 +14,9 @@ It is especially useful to publish async and real-time updates of resources serv
 
 ![Subscriptions Schema](spec/subscriptions.png)
 
-* [Getting started](https://mercure.rocks/docs/getting-started)
-* [Full documentation](https://mercure.rocks/docs)
-* [Demo](https://demo.mercure.rocks/)
+- [Getting started](https://mercure.rocks/docs/getting-started)
+- [Full documentation](https://mercure.rocks/docs)
+- [Demo](https://demo.mercure.rocks/)
 
 [The protocol](https://mercure.rocks/spec) is maintained in this repository and is also available as [an Internet-Draft](https://datatracker.ietf.org/doc/draft-dunglas-mercure/).
 
diff --git a/cmd/mercure/mercure.yaml b/cmd/mercure/mercure.yaml
index 2f4fbf6d..59f1677b 100644
--- a/cmd/mercure/mercure.yaml
+++ b/cmd/mercure/mercure.yaml
@@ -3,10 +3,22 @@ debug: true
 allow_anonymous: true
 #cert_file: fixtures/tls/server.crt
 #key_file: fixtures/tls/server.key
-cors_allowed_origins: [http://localhost:3000, http://localhost:3001, http://localhost:5000, http://localhost:8000]
+cors_allowed_origins:
+  [
+    http://localhost:3000,
+    http://localhost:3001,
+    http://localhost:5000,
+    http://localhost:8000,
+  ]
 transport_url: bolt://update.db
-jwt_key: '!ChangeThisMercureHubJWTSecretKey!'
-publish_allowed_origins: [http://localhost:3000, http://localhost:3001, http://localhost:5000, http://localhost:8000]
+jwt_key: "!ChangeThisMercureHubJWTSecretKey!"
+publish_allowed_origins:
+  [
+    http://localhost:3000,
+    http://localhost:3001,
+    http://localhost:5000,
+    http://localhost:8000,
+  ]
 subscriptions: true
 
 metrics_enabled: false
diff --git a/docs/README.md b/docs/README.md
index 9734c02a..a134f36a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,33 +1,33 @@
 # Mercure Documentation
 
-* [Mercure in a Few Words](mercure.md)
-* [Getting Started](getting-started.md)
+- [Mercure in a Few Words](mercure.md)
+- [Getting Started](getting-started.md)
 
 ## Protocol Specification
 
-* [The Specification](../spec/mercure.md) (also available as an [IETF's Internet Draft](https://datatracker.ietf.org/doc/draft-dunglas-mercure/) designed to be published as an RFC)
-* [Case studies and use cases](spec/use-cases.md)
-* [Frequently Asked Questions](spec/faq.md)
-* [OpenAPI spec](https://github.com/dunglas/mercure/blob/master/spec/openapi.yaml)
+- [The Specification](../spec/mercure.md) (also available as an [IETF's Internet Draft](https://datatracker.ietf.org/doc/draft-dunglas-mercure/) designed to be published as an RFC)
+- [Case studies and use cases](spec/use-cases.md)
+- [Frequently Asked Questions](spec/faq.md)
+- [OpenAPI spec](https://github.com/dunglas/mercure/blob/master/spec/openapi.yaml)
 
 ## Mercure.rocks Hub
 
-* [Installing the Mercure.rocks Hub](hub/install.md)
-* [Configuration](hub/config.md)
-* [The Cloud version](hub/cloud.md)
-* [Creating a cluster of hubs](hub/cluster.md)
-* [Cookbooks](hub/cookbooks.md)
-* [Running behind NGINX](hub/nginx.md)
-* [Running behind Traefik Proxy](hub/traefik.md)
-* [Troubleshooting](hub/troubleshooting.md)
-* [Debug the Mercure.rocks Hub](hub/debug.md)
-* [Upgrade to new versions](UPGRADE.md)
-* [Load Testing](hub/load-test.md)
+- [Installing the Mercure.rocks Hub](hub/install.md)
+- [Configuration](hub/config.md)
+- [The Cloud version](hub/cloud.md)
+- [Creating a cluster of hubs](hub/cluster.md)
+- [Cookbooks](hub/cookbooks.md)
+- [Running behind NGINX](hub/nginx.md)
+- [Running behind Traefik Proxy](hub/traefik.md)
+- [Troubleshooting](hub/troubleshooting.md)
+- [Debug the Mercure.rocks Hub](hub/debug.md)
+- [Upgrade to new versions](UPGRADE.md)
+- [Load Testing](hub/load-test.md)
 
 ## Ecosystem
 
-* [Awesome Mercure: Libraries, Examples and Learning Resources](ecosystem/awesome.md)
-* [Using a Mercure Service in Your GitHub Actions](ecosystem/github-actions.md)
-* [Using Mercure and Hotwire to Stream Page Changes](ecosystem/hotwire.md)
-* [Getting Help](ecosystem/help.md)
-* [Conformance Tests](ecosystem/conformance-tests.md)
+- [Awesome Mercure: Libraries, Examples and Learning Resources](ecosystem/awesome.md)
+- [Using a Mercure Service in Your GitHub Actions](ecosystem/github-actions.md)
+- [Using Mercure and Hotwire to Stream Page Changes](ecosystem/hotwire.md)
+- [Getting Help](ecosystem/help.md)
+- [Conformance Tests](ecosystem/conformance-tests.md)
diff --git a/docs/UPGRADE.md b/docs/UPGRADE.md
index 667f6b29..faf45aac 100644
--- a/docs/UPGRADE.md
+++ b/docs/UPGRADE.md
@@ -11,7 +11,7 @@ Before:
 ```json
 {
   "mercure": {
-    "payload": {"foo": "bar"}
+    "payload": { "foo": "bar" }
   }
 }
 ```
@@ -22,7 +22,7 @@ After:
 {
   "mercure": {
     "payloads": {
-      "*": {"foo": "bar"}
+      "*": { "foo": "bar" }
     }
   }
 }
@@ -117,18 +117,18 @@ Before switching to the Caddy build, be sure to [migrate your configuration](hub
 
 This version is in sync with the latest version of the specification, which changed a lot. Upgrading to 0.10 **requires to change your code**. Carefully read this guide before upgrading the hub.
 
-* Private updates are now handled differently. *Targets* don't exist anymore. They have been superseded by the concept of *topic selectors*.
+- Private updates are now handled differently. _Targets_ don't exist anymore. They have been superseded by the concept of _topic selectors_.
   To send a private update, the publisher must now set the new `private` field to `on` when sending the `POST` request. The topics of the update must also match at least one selector (a URI Template, a raw string or `*` to match all topics) provided in the `mercure.publish` claim of the JWT.
   To receive a private update, at least one topic of this update must match at least one selector provided in the `mercure.subscribe` claim of the JWT.
-* The structure of the JSON-LD document included in subscription events changed. Especially, `"@type": "https://mercure.rocks/Subscription"` is now `"type": "Subscription"` and `"@id": "/.well-known/mercure/subscriptions/foo/bar"` is now `"id": "/.well-known/mercure/subscriptions/foo/bar"`.
-* The `dispatch_subscriptions` config option has been renamed `subscriptions`.
-* The `subscriptions_include_ip` config option doesn't exist anymore. To include the subscriber IP (or any other value) in subscription events, use the new `mercure.payload` property of the JWT.
-* All IDs generated by the hub (updates ID, subscriptions IDs...) are now URN following the template `urn:uuid:{the-uuid}` (it was `{the-uuid}` before). You may need to update your code if you deal with these IDs.
-* The topic `*` is now reserved and allows to subscribe to all topics.
+- The structure of the JSON-LD document included in subscription events changed. Especially, `"@type": "https://mercure.rocks/Subscription"` is now `"type": "Subscription"` and `"@id": "/.well-known/mercure/subscriptions/foo/bar"` is now `"id": "/.well-known/mercure/subscriptions/foo/bar"`.
+- The `dispatch_subscriptions` config option has been renamed `subscriptions`.
+- The `subscriptions_include_ip` config option doesn't exist anymore. To include the subscriber IP (or any other value) in subscription events, use the new `mercure.payload` property of the JWT.
+- All IDs generated by the hub (updates ID, subscriptions IDs...) are now URN following the template `urn:uuid:{the-uuid}` (it was `{the-uuid}` before). You may need to update your code if you deal with these IDs.
+- The topic `*` is now reserved and allows to subscribe to all topics.
 
 ## 0.8
 
-* According to the new version of the spec, the URL of the Hub changed moved from `/hub` to `/.well-known/mercure`
-* `HISTORY_CLEANUP_FREQUENCY`, `HISTORY_SIZE` and `DB_PATH` environment variables have been replaced by the new `TRANSPORT_URL` environment variable
-* Lists in `ACME_HOSTS`, `CORS_ALLOWED_ORIGINS`, `PUBLISH_ALLOWED_ORIGINS` must now be space separated
-* The public API of the Go library has been totally revamped
+- According to the new version of the spec, the URL of the Hub changed moved from `/hub` to `/.well-known/mercure`
+- `HISTORY_CLEANUP_FREQUENCY`, `HISTORY_SIZE` and `DB_PATH` environment variables have been replaced by the new `TRANSPORT_URL` environment variable
+- Lists in `ACME_HOSTS`, `CORS_ALLOWED_ORIGINS`, `PUBLISH_ALLOWED_ORIGINS` must now be space separated
+- The public API of the Go library has been totally revamped
diff --git a/docs/ecosystem/awesome.md b/docs/ecosystem/awesome.md
index e9084b73..5bcfe131 100644
--- a/docs/ecosystem/awesome.md
+++ b/docs/ecosystem/awesome.md
@@ -2,89 +2,89 @@
 
 ## Demos
 
-* [Demo hub and debug UI](https://demo.mercure.rocks) ([source code](https://github.com/dunglas/mercure/tree/master/public)): a managed demo hub and the official debugging tools (written in JavaScript)
-* [Chat](https://demo-chat.mercure.rocks/) ([source code](https://github.com/dunglas/mercure/tree/master/examples/chat)): a chat, including the list of currently connected users (written in JavaScript and Python)
+- [Demo hub and debug UI](https://demo.mercure.rocks) ([source code](https://github.com/dunglas/mercure/tree/master/public)): a managed demo hub and the official debugging tools (written in JavaScript)
+- [Chat](https://demo-chat.mercure.rocks/) ([source code](https://github.com/dunglas/mercure/tree/master/examples/chat)): a chat, including the list of currently connected users (written in JavaScript and Python)
 
 ## Examples
 
-* [JavaScript (publish, subscribe and presence API)](https://github.com/dunglas/mercure/blob/master/public/app.js)
-* [JavaScript (subscribe and presence API)](https://github.com/dunglas/mercure/blob/master/examples/chat/static/chat.js)
-* [Node.js (publish)](https://github.com/dunglas/mercure/tree/master/examples/publish/node.js)
-* [PHP (publish)](https://github.com/dunglas/mercure/tree/master/examples/publish/php.php)
-* [Ruby (publish)](https://github.com/dunglas/mercure/tree/master/examples/publish/ruby.rb)
-* [Python (subscribe)](https://github.com/dunglas/mercure/tree/master/examples/subscribe/python.py)
-* [Python (cookie authorization)](https://github.com/dunglas/mercure/blob/master/examples/chat/chat.py)
-* [API Platform (publish and subscribe)](https://github.com/api-platform/demo): a book catalog updated in real-time using Mercure
+- [JavaScript (publish, subscribe and presence API)](https://github.com/dunglas/mercure/blob/master/public/app.js)
+- [JavaScript (subscribe and presence API)](https://github.com/dunglas/mercure/blob/master/examples/chat/static/chat.js)
+- [Node.js (publish)](https://github.com/dunglas/mercure/tree/master/examples/publish/node.js)
+- [PHP (publish)](https://github.com/dunglas/mercure/tree/master/examples/publish/php.php)
+- [Ruby (publish)](https://github.com/dunglas/mercure/tree/master/examples/publish/ruby.rb)
+- [Python (subscribe)](https://github.com/dunglas/mercure/tree/master/examples/subscribe/python.py)
+- [Python (cookie authorization)](https://github.com/dunglas/mercure/blob/master/examples/chat/chat.py)
+- [API Platform (publish and subscribe)](https://github.com/api-platform/demo): a book catalog updated in real-time using Mercure
 
 ## Documentation and Code Generation
 
-* The Mercure protocol is natively supported by [the AsyncAPI ecosystem](https://www.asyncapi.com/)
+- The Mercure protocol is natively supported by [the AsyncAPI ecosystem](https://www.asyncapi.com/)
 
 ## Hubs and Server Libraries
 
-* [Go Hub and Server library](https://mercure.rocks)
-* [Node.js Hub and Server library](https://github.com/Ilshidur/node-mercure)
+- [Go Hub and Server library](https://mercure.rocks)
+- [Node.js Hub and Server library](https://github.com/Ilshidur/node-mercure)
 
 ## Client Libraries
 
-* [PHP (publish)](https://github.com/symfony/mercure)
-* [Python (publish and subscribe)](https://github.com/vitorluis/python-mercure)
-* [Dart (publish and subscribe)](https://github.com/wallforfry/dart_mercure)
-* [Amphp (publish)](https://github.com/eislambey/amp-mercure-publisher)
-* [Java (publish)](https://github.com/vitorluis/java-mercure)
+- [PHP (publish)](https://github.com/symfony/mercure)
+- [Python (publish and subscribe)](https://github.com/vitorluis/python-mercure)
+- [Dart (publish and subscribe)](https://github.com/wallforfry/dart_mercure)
+- [Amphp (publish)](https://github.com/eislambey/amp-mercure-publisher)
+- [Java (publish)](https://github.com/vitorluis/java-mercure)
 
 ## Frameworks and Services Integrations
 
-* [Official Mercure support for the Symfony framework](https://symfony.com/doc/current/mercure.html)
-* [Official Mercure support for the API Platform framework](https://api-platform.com/docs/core/mercure/)
-* [Using Mercure and Hotwire to stream page changes](hotwire.md)
-* [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster)
-* [Yii Mercure Behavior](https://github.com/bizley/mercure-behavior)
-* [Add a Mercure service in GitHub Actions](github-actions.md)
-* [Send a Mercure publish event from GitHub Actions](https://github.com/Ilshidur/action-mercure)
+- [Official Mercure support for the Symfony framework](https://symfony.com/doc/current/mercure.html)
+- [Official Mercure support for the API Platform framework](https://api-platform.com/docs/core/mercure/)
+- [Using Mercure and Hotwire to stream page changes](hotwire.md)
+- [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster)
+- [Yii Mercure Behavior](https://github.com/bizley/mercure-behavior)
+- [Add a Mercure service in GitHub Actions](github-actions.md)
+- [Send a Mercure publish event from GitHub Actions](https://github.com/Ilshidur/action-mercure)
 
 ## Useful Related Libraries
 
-* [Microsoft's Fetch Event Source, a better JavaScript API for making Event Source requests](https://github.com/Azure/fetch-event-source)
-* [`EventSource` polyfill for Edge/IE and old browsers](https://github.com/Yaffle/EventSource)
-* [`EventSource` polyfill for React Native](https://github.com/jordanbyron/react-native-event-source)
-* [`EventSource` implementation for Node](https://github.com/EventSource/eventsource)
-* [Server-Sent Events client for Go](https://github.com/donovanhide/eventsource)
-* [Server-Sent Events client for Android and Java](https://github.com/heremaps/oksse)
-* [Server-Sent Events client for Swift](https://github.com/inaka/EventSource)
-* [JavaScript library to parse `Link` headers](https://github.com/thlorenz/parse-link-header)
-* [JavaScript library to decrypt JWE using the WebCrypto API](https://github.com/square/js-jose)
+- [Microsoft's Fetch Event Source, a better JavaScript API for making Event Source requests](https://github.com/Azure/fetch-event-source)
+- [`EventSource` polyfill for Edge/IE and old browsers](https://github.com/Yaffle/EventSource)
+- [`EventSource` polyfill for React Native](https://github.com/jordanbyron/react-native-event-source)
+- [`EventSource` implementation for Node](https://github.com/EventSource/eventsource)
+- [Server-Sent Events client for Go](https://github.com/donovanhide/eventsource)
+- [Server-Sent Events client for Android and Java](https://github.com/heremaps/oksse)
+- [Server-Sent Events client for Swift](https://github.com/inaka/EventSource)
+- [JavaScript library to parse `Link` headers](https://github.com/thlorenz/parse-link-header)
+- [JavaScript library to decrypt JWE using the WebCrypto API](https://github.com/square/js-jose)
 
 ## Projects Using Mercure
 
-* [HTTP Broadcast: a scalable and fault resilient HTTP broadcaster](https://github.com/jderusse/http-broadcast)
+- [HTTP Broadcast: a scalable and fault resilient HTTP broadcaster](https://github.com/jderusse/http-broadcast)
 
 ## Learning Resources
 
 ### English 🇺🇸
 
-* 📺 [API Updates in Real Time w. Mercure.rocks](https://www.youtube.com/watch?v=odNsxoHSkT4)
-* 📺 [Building async public APIs using HTTP/2+ and the Mercure protocol](https://www.youtube.com/watch?v=IUx47Tx0O8E)
-* 📺 [Real-time Notifications with Symfony and Mercure (Basics)](https://www.youtube.com/watch?v=kYNC47V7R_0)
-* 📺 [Real-time Chat App with Symfony and Mercure](https://www.youtube.com/watch?v=wnr2A4aKnPU)
-* [Official Push and Real-Time Capabilities for Symfony and API Platform using Mercure (Symfony blog)](https://dunglas.fr/2019/03/official-push-and-real-time-capabilities-for-symfony-and-api-platform-mercure-protocol/)
-* [Tech Workshop: Mercure by Kévin Dunglas at SensioLabs (SensioLabs)](https://blog.sensiolabs.com/2019/01/24/tech-workshop-mercure-kevin-dunglas-sensiolabs/)
-* [Real-time messages with Mercure using Laravel](http://thedevopsguide.com/real-time-notifications-with-mercure/)
-* [Using Mercure on Stackhero](https://www.stackhero.io/en/documentations/mercure-hub/getting-started)
-* [Mercure - install and run](https://mysiar.github.io/dev/2020/04/12/mercure-part1.html)
+- 📺 [API Updates in Real Time w. Mercure.rocks](https://www.youtube.com/watch?v=odNsxoHSkT4)
+- 📺 [Building async public APIs using HTTP/2+ and the Mercure protocol](https://www.youtube.com/watch?v=IUx47Tx0O8E)
+- 📺 [Real-time Notifications with Symfony and Mercure (Basics)](https://www.youtube.com/watch?v=kYNC47V7R_0)
+- 📺 [Real-time Chat App with Symfony and Mercure](https://www.youtube.com/watch?v=wnr2A4aKnPU)
+- [Official Push and Real-Time Capabilities for Symfony and API Platform using Mercure (Symfony blog)](https://dunglas.fr/2019/03/official-push-and-real-time-capabilities-for-symfony-and-api-platform-mercure-protocol/)
+- [Tech Workshop: Mercure by Kévin Dunglas at SensioLabs (SensioLabs)](https://blog.sensiolabs.com/2019/01/24/tech-workshop-mercure-kevin-dunglas-sensiolabs/)
+- [Real-time messages with Mercure using Laravel](http://thedevopsguide.com/real-time-notifications-with-mercure/)
+- [Using Mercure on Stackhero](https://www.stackhero.io/en/documentations/mercure-hub/getting-started)
+- [Mercure - install and run](https://mysiar.github.io/dev/2020/04/12/mercure-part1.html)
 
 ### French 🇫🇷
 
-* 📺 [Notifications instantanées avec Mercure (Grafikart)](https://www.grafikart.fr/tutoriels/symfony-mercure-1151)
-* 📺 [Live Coding : Notifications temps réel avec Mercure](https://www.youtube.com/watch?v=tqqJ1ul2M-E)
-* 📺 [Explication des Server Sent Events (SSE) avec Mercure](https://www.youtube.com/watch?v=Q4LRN2wXuIc)
-* 📺 [Mercure : des UIs toujours synchronisées avec les données en BDD](https://www.youtube.com/watch?v=UcBa4AugNTE)
-* 📺 [Mercure, et PHP s'enamoure enfin du temps réel](https://www.youtube.com/watch?v=GugURP88Rgg)
-* 📺 [Async avec Messenger, AMQP et Mercure](https://www.youtube.com/watch?v=cHPbcuydJiA)
-* [Mercure, un protocole pour pousser des mises à jour vers des navigateurs et app mobiles en temps réel (Les-Tilleuls.coop)](https://les-tilleuls.coop/blog/mercure-un-protocole-pour-pousser-des-mises-a-jour-vers-des-navigateurs-et-app-mobiles-en-temps-reel)
-* [Symfony et Mercure](https://afsy.fr/avent/2019/21-symfony-et-mercure)
-* [À la découverte de Mercure](https://blog.eleven-labs.com/fr/a-la-decouverte-de-mercure/)
+- 📺 [Notifications instantanées avec Mercure (Grafikart)](https://www.grafikart.fr/tutoriels/symfony-mercure-1151)
+- 📺 [Live Coding : Notifications temps réel avec Mercure](https://www.youtube.com/watch?v=tqqJ1ul2M-E)
+- 📺 [Explication des Server Sent Events (SSE) avec Mercure](https://www.youtube.com/watch?v=Q4LRN2wXuIc)
+- 📺 [Mercure : des UIs toujours synchronisées avec les données en BDD](https://www.youtube.com/watch?v=UcBa4AugNTE)
+- 📺 [Mercure, et PHP s'enamoure enfin du temps réel](https://www.youtube.com/watch?v=GugURP88Rgg)
+- 📺 [Async avec Messenger, AMQP et Mercure](https://www.youtube.com/watch?v=cHPbcuydJiA)
+- [Mercure, un protocole pour pousser des mises à jour vers des navigateurs et app mobiles en temps réel (Les-Tilleuls.coop)](https://les-tilleuls.coop/blog/mercure-un-protocole-pour-pousser-des-mises-a-jour-vers-des-navigateurs-et-app-mobiles-en-temps-reel)
+- [Symfony et Mercure](https://afsy.fr/avent/2019/21-symfony-et-mercure)
+- [À la découverte de Mercure](https://blog.eleven-labs.com/fr/a-la-decouverte-de-mercure/)
 
 ### German 🇩🇪
 
-* [Neue Symfony-Komponente: Mercure ermöglicht Echtzeitübertragung](https://entwickler.de/online/php/symfony-mercure-komponente-579885243.html)
+- [Neue Symfony-Komponente: Mercure ermöglicht Echtzeitübertragung](https://entwickler.de/online/php/symfony-mercure-komponente-579885243.html)
diff --git a/docs/ecosystem/conformance-tests.md b/docs/ecosystem/conformance-tests.md
index 08ce25e8..41e8d9f0 100644
--- a/docs/ecosystem/conformance-tests.md
+++ b/docs/ecosystem/conformance-tests.md
@@ -15,9 +15,9 @@ This test suite is based on [Playwright](https://playwright.dev/).
 
 The test suite can be configured by setting environment variables:
 
-* `BASE_URL`: the URL of the hub to test
-* `CUSTOM_ID`: enable or disable tests related to custom IDs support
+- `BASE_URL`: the URL of the hub to test
+- `CUSTOM_ID`: enable or disable tests related to custom IDs support
 
 ## See Also
 
-* [The load test](../hub/load-test.md)
+- [The load test](../hub/load-test.md)
diff --git a/docs/ecosystem/github-actions.md b/docs/ecosystem/github-actions.md
index e76c65f6..884dd3ef 100644
--- a/docs/ecosystem/github-actions.md
+++ b/docs/ecosystem/github-actions.md
@@ -9,24 +9,24 @@ name: Create a Mercure service
 on: push
 
 jobs:
-    my-job-using-mercure:
-        runs-on: ubuntu-latest
+  my-job-using-mercure:
+    runs-on: ubuntu-latest
 
-        services:
-            mercure:
-                image: dunglas/mercure
-                env:
-                    SERVER_NAME: :1337
-                    MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
-                    MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
-                    MERCURE_EXTRA_DIRECTIVES: |
-                        # Custom directives, see https://mercure.rocks/docs/hub/config
-                        anonymous
-                        cors_origins *
-                ports:
-                    - 1337:1337
-        steps:
-            # ...
+    services:
+      mercure:
+        image: dunglas/mercure
+        env:
+          SERVER_NAME: :1337
+          MERCURE_PUBLISHER_JWT_KEY: "!ChangeThisMercureHubJWTSecretKey!"
+          MERCURE_SUBSCRIBER_JWT_KEY: "!ChangeThisMercureHubJWTSecretKey!"
+          MERCURE_EXTRA_DIRECTIVES: |
+            # Custom directives, see https://mercure.rocks/docs/hub/config
+            anonymous
+            cors_origins *
+        ports:
+          - 1337:1337
+    steps:
+      # ...
 ```
 
 A Mercure hub is available at the address `http://localhost:1337/.well-known/mercure`.
diff --git a/docs/ecosystem/help.md b/docs/ecosystem/help.md
index 0f92aced..82dacf54 100644
--- a/docs/ecosystem/help.md
+++ b/docs/ecosystem/help.md
@@ -6,8 +6,8 @@ For support requests related to the [Cloud](../hub/cloud.md) or the [On Premise]
 
 ## Community Support
 
-* [Stack Overflow: ask questions using the `mercure` tag](https://stackoverflow.com/questions/tagged/mercure)
-* [Slack: chat with the community on the `#mercure` channel on Symfony's Slack](https://symfony.com/slack)
+- [Stack Overflow: ask questions using the `mercure` tag](https://stackoverflow.com/questions/tagged/mercure)
+- [Slack: chat with the community on the `#mercure` channel on Symfony's Slack](https://symfony.com/slack)
 
 ## Commercial Support
 
diff --git a/docs/ecosystem/hotwire.md b/docs/ecosystem/hotwire.md
index 062f2229..dfe75f08 100644
--- a/docs/ecosystem/hotwire.md
+++ b/docs/ecosystem/hotwire.md
@@ -11,7 +11,9 @@ Using Mercure to power a Turbo Stream is straightforward, and doesn't require an
 import { connectStreamSource } from "@hotwired/turbo";
 
 // The "topic" parameter can be any string or URI
-const es = new EventSource("https://example.com/.well-known/mercure?topic=my-stream");
+const es = new EventSource(
+  "https://example.com/.well-known/mercure?topic=my-stream",
+);
 connectStreamSource(es);
 ```
 
@@ -30,9 +32,9 @@ curl \
   https://example.com/.well-known/mercure
 ```
 
-* `topic` must be the same topic we used in the JavaScript code ;
-* `data` contains the [Turbo Streams messages](https://turbo.hotwire.dev/handbook/streams#stream-messages-and-actions) to broadcast ;
-* the `Authorization` header must contain [a valid publisher JWT](../../spec/mercure.md#publication).
+- `topic` must be the same topic we used in the JavaScript code ;
+- `data` contains the [Turbo Streams messages](https://turbo.hotwire.dev/handbook/streams#stream-messages-and-actions) to broadcast ;
+- the `Authorization` header must contain [a valid publisher JWT](../../spec/mercure.md#publication).
 
 [Other Mercure features](../../spec/mercure.md#publication), including broadcasting private updates to authorized subscribers, are also supported.
 
@@ -75,7 +77,10 @@ export default class extends Controller {
 ```
 
 ```html
-<div data-controller="turbo-stream" data-turbo-stream-url-value="https://example.com/.well-known/mercure?topic=my-stream">
+<div
+  data-controller="turbo-stream"
+  data-turbo-stream-url-value="https://example.com/.well-known/mercure?topic=my-stream"
+>
   <!-- ... -->
 </div>
 ```
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 36c8e71f..c8680282 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -16,15 +16,15 @@ Subscribing to updates from a web browser or any other platform supporting [Serv
 ```javascript
 // The subscriber subscribes to updates for the https://example.com/users/dunglas topic
 // and to any topic matching https://example.com/books/{id}
-const url = new URL('https://localhost/.well-known/mercure');
-url.searchParams.append('topic', 'https://example.com/books/{id}');
-url.searchParams.append('topic', 'https://example.com/users/dunglas');
+const url = new URL("https://localhost/.well-known/mercure");
+url.searchParams.append("topic", "https://example.com/books/{id}");
+url.searchParams.append("topic", "https://example.com/users/dunglas");
 // The URL class is a convenient way to generate URLs such as https://localhost/.well-known/mercure?topic=https://example.com/books/{id}&topic=https://example.com/users/dunglas
 
 const eventSource = new EventSource(url);
 
 // The callback will be called every time an update is published
-eventSource.onmessage = e => console.log(e); // do something with the payload
+eventSource.onmessage = (e) => console.log(e); // do something with the payload
 ```
 
 The `EventSource` class is available [in all modern web browsers](https://caniuse.com/eventsource).
@@ -54,12 +54,14 @@ Also optionally, the hub URL can be automatically discovered:
 Here is a snippet to extract the URL of the hub from the `Link` HTTP header.
 
 ```javascript
-fetch('https://example.com/books/1') // Has this header `Link: <https://localhost/.well-known/mercure>; rel="mercure"`
-    .then(response => {
-        // Extract the hub URL from the Link header
-        const hubUrl = response.headers.get('Link').match(/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1];
-        // Subscribe to updates using the first snippet, do something with response's body...
-    });
+fetch("https://example.com/books/1") // Has this header `Link: <https://localhost/.well-known/mercure>; rel="mercure"`
+  .then((response) => {
+    // Extract the hub URL from the Link header
+    const hubUrl = response.headers
+      .get("Link")
+      .match(/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1];
+    // Subscribe to updates using the first snippet, do something with response's body...
+  });
 ```
 
 ## Publishing
@@ -84,27 +86,30 @@ Example using [Node.js](https://nodejs.org/) / [Serverless](https://serverless.c
 ```javascript
 // Handle a POST, PUT, PATCH or DELETE request or finish an async job...
 // and notify the hub
-const http = require('http');
-const querystring = require('querystring');
+const http = require("http");
+const querystring = require("querystring");
 
 const postData = querystring.stringify({
-    'topic': 'https://example.com/books/1',
-    'data': JSON.stringify({ foo: 'updated value' }),
+  topic: "https://example.com/books/1",
+  data: JSON.stringify({ foo: "updated value" }),
 });
 
-const req = http.request({
-    hostname: 'localhost',
-    port: '3000',
-    path: '/.well-known/mercure',
-    method: 'POST',
+const req = http.request(
+  {
+    hostname: "localhost",
+    port: "3000",
+    path: "/.well-known/mercure",
+    method: "POST",
     headers: {
-        Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM',
-        // the JWT must have a mercure.publish key containing an array of topic selectors (can contain "*" for all topics, and be empty for public updates)
-        // the JWT key must be shared between the hub and the server
-        'Content-Type': 'application/x-www-form-urlencoded',
-        'Content-Length': Buffer.byteLength(postData),
-    }
-}, /* optional response handler */);
+      Authorization:
+        "Bearer eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM",
+      // the JWT must have a mercure.publish key containing an array of topic selectors (can contain "*" for all topics, and be empty for public updates)
+      // the JWT key must be shared between the hub and the server
+      "Content-Type": "application/x-www-form-urlencoded",
+      "Content-Length": Buffer.byteLength(postData),
+    },
+  } /* optional response handler */,
+);
 req.write(postData);
 req.end();
 
@@ -124,6 +129,6 @@ Mercure dispatches events every time a new subscription is created or terminated
 
 ## Going Further
 
-* [Read the full specification](../spec/mercure.md)
-* [Install the hub](hub/install.md)
-* [Checkout the examples](ecosystem/awesome.md#examples)
+- [Read the full specification](../spec/mercure.md)
+- [Install the hub](hub/install.md)
+- [Checkout the examples](ecosystem/awesome.md#examples)
diff --git a/docs/hub/cloud.md b/docs/hub/cloud.md
index 409d7f52..13555666 100644
--- a/docs/hub/cloud.md
+++ b/docs/hub/cloud.md
@@ -15,10 +15,10 @@ After purchase, your hub will be instantly provisionned and available under a `m
 
 You'll have access to an administration interface allowing you to:
 
-* configure your hub (the same [configuration settings](config.md) as for the free and open-source hub are available)
-* access to the logs of your hub
-* [set a custom domain name](#custom-domain)
-* [switch to another plan](#switching-between-plans)
+- configure your hub (the same [configuration settings](config.md) as for the free and open-source hub are available)
+- access to the logs of your hub
+- [set a custom domain name](#custom-domain)
+- [switch to another plan](#switching-between-plans)
 
 ## Custom Domain
 
diff --git a/docs/hub/cluster.md b/docs/hub/cluster.md
index 5365f32b..8e64628f 100644
--- a/docs/hub/cluster.md
+++ b/docs/hub/cluster.md
@@ -27,10 +27,10 @@ The High Availability On Premise Mercure.rocks Hub is a drop-in replacement for
 The HA version is shipped with transports having node synchronization capabilities.
 These transports can rely on:
 
-* Redis
-* Postgres `LISTEN`/`NOTIFY`
-* Apache Kafka
-* Apache Pulsar
+- Redis
+- Postgres `LISTEN`/`NOTIFY`
+- Apache Kafka
+- Apache Pulsar
 
 We can help you to decide which synchronization mechanism will be the best suited for your needs, and help you to install and configure it on your infrastructure.
 
@@ -71,7 +71,7 @@ To install Redis, [read the documentation](https://redis.io/topics/quickstart).
 Most Cloud Computing platforms also provide managed versions of Redis.
 
 | Feature         | Supported |
-|-----------------|-----------|
+| --------------- | --------- |
 | History         | ✅        |
 | Presence API    | ✅        |
 | Custom event ID | ✅        |
@@ -91,11 +91,10 @@ To use Redis, the `MERCURE_TRANSPORT_URL` environment variable must be set like
 The following options can be passed as query parameters of the URL set in `transport_url`:
 
 | Parameter        | Description                                                                                            | Default |
-|------------------|--------------------------------------------------------------------------------------------------------|---------|
+| ---------------- | ------------------------------------------------------------------------------------------------------ | ------- |
 | `tls`            | set to `1` to enable TLS support                                                                       | `0`     |
 | `max_len_approx` | the approximative maximum number of messages to store in the history, set to `0` to store all messages | `0`     |
 
-
 #### PostgreSQL Transport
 
 The PostgreSQL Transport allows to store permanently the event and to query them using the full power of SQL.
@@ -105,7 +104,7 @@ To install PostgreSQL, [read the documentation](https://www.postgresql.org/docs/
 Most Cloud Computing platforms also provide managed versions of PostgreSQL.
 
 | Feature         | Supported    |
-|-----------------|--------------|
+| --------------- | ------------ |
 | History         | ✅           |
 | Presence API    | ❌ (planned) |
 | Custom event ID | ✅           |
@@ -133,16 +132,16 @@ To install Apache Kafka, [read the quickstart guide](https://kafka.apache.org/qu
 Most Cloud Computing platforms also provide managed versions of Kafka.
 The Mercure.rocks hub has been tested with:
 
-* Bitnami's Kafka Docker images (Kubernetes and the like)
-* Amazon Managed Streaming for Apache Kafka (Amazon MSK)
-* IBM Event Streams for IBM Cloud
-* Heroku Kafka
+- Bitnami's Kafka Docker images (Kubernetes and the like)
+- Amazon Managed Streaming for Apache Kafka (Amazon MSK)
+- IBM Event Streams for IBM Cloud
+- Heroku Kafka
 
-| Feature         | Supported    |
-|-----------------|--------------|
-| History         | ✅           |
-| Presence API    | ❌           |
-| Custom event ID | ✅           |
+| Feature         | Supported |
+| --------------- | --------- |
+| History         | ✅        |
+| Presence API    | ❌        |
+| Custom event ID | ✅        |
 
 ##### Kafka Configuration
 
@@ -159,10 +158,10 @@ To use Kafka, the `MERCURE_TRANSPORT_URL` environment variable must be set like
 The following options can be passed as query parameters of the URL set in `transport_url`:
 
 | Parameter        | Description                                                                                                                                 |
-|------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
+| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
 | `addr`           | addresses of the Kafka servers, you can pass several `addr` parameters to use several Kafka servers (ex: `addr=host1:9092&addr=host2:9092`) |
-| `topic`          | the name of the Kafka topic to use (ex: `topic=mercure-ha`), **all Mercure.rocks hub instances must use the same topic**                          |
-| `consumer_group` | consumer group, **must be different for every instance of the Mercure.rocks hub** (ex: `consumer_group=<random-string>`)                          |
+| `topic`          | the name of the Kafka topic to use (ex: `topic=mercure-ha`), **all Mercure.rocks hub instances must use the same topic**                    |
+| `consumer_group` | consumer group, **must be different for every instance of the Mercure.rocks hub** (ex: `consumer_group=<random-string>`)                    |
 | `user`           | Kafka SASL user (optional, ex: `user=kevin`)                                                                                                |
 | `password`       | Kafka SASL password (optional, ex: `password=maman`)                                                                                        |
 | `tls`            | Set to `1` to enable TLS (ex: `tls=1`)                                                                                                      |
@@ -174,7 +173,7 @@ The Pulsar transport should only be used when Pulsar is already part of your sta
 To install Apache Pulsar, [read the documentation](https://pulsar.apache.org/docs/en/standalone/).
 
 | Feature         | Supported    |
-|-----------------|--------------|
+| --------------- | ------------ |
 | History         | ✅           |
 | Presence API    | ❌           |
 | Custom event ID | ❌ (planned) |
@@ -193,10 +192,10 @@ To use Pulsar, the `MERCURE_TRANSPORT_URL` environment variable must be set like
 
 The following options can be passed as query parameters of the URL set in `transport_url`:
 
-| Parameters          | Description                                                                                                                                |   |
-|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------|---|
-| `topic`             | the name of the Pulsar topic to use (ex: `topic=mercure`), **all Mercure.rocks hub instances must use the same topic**                           |   |
-| `subscription_name` | the subscription name for this node, **must be different for every instance of the Mercure.rocks hub** (ex: `subscription_name=<random-string>`) |   |
+| Parameters          | Description                                                                                                                                      |     |
+| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --- |
+| `topic`             | the name of the Pulsar topic to use (ex: `topic=mercure`), **all Mercure.rocks hub instances must use the same topic**                           |     |
+| `subscription_name` | the subscription name for this node, **must be different for every instance of the Mercure.rocks hub** (ex: `subscription_name=<random-string>`) |     |
 
 ### Docker Images and Kubernetes Chart
 
diff --git a/docs/hub/config.md b/docs/hub/config.md
index c0167354..b7573654 100644
--- a/docs/hub/config.md
+++ b/docs/hub/config.md
@@ -39,7 +39,7 @@ Note that HTTPS is automatically disabled if you set the server port to 80.
 The following Mercure-specific directives are available:
 
 | Directive                             | Description                                                                                                                                                                                                                                     | Default                |
-|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------|
+| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
 | `publisher_jwt <key> [<algorithm>]`   | the JWT key and algorithm to use for publishers, can contain [placeholders](https://caddyserver.com/docs/conventions#placeholders)                                                                                                              |                        |
 | `subscriber_jwt <key> [<algorithm>]`  | the JWT key and algorithm to use for subscribers, can contain [placeholders](https://caddyserver.com/docs/conventions#placeholders)                                                                                                             |                        |
 | `publisher_jwks_url`                  | the URL of the JSON Web Key Set (JWK Set) URL (provided by identity providers such as Keycloak or AWS Cognito) to use for validating publishers JWT (take precedence over `publisher_jwt`)                                                      |                        |
@@ -65,24 +65,25 @@ See also [the list of built-in Caddyfile directives](https://caddyserver.com/doc
 
 The provided `Caddyfile` and the Docker image provide convenient environment variables:
 
-| Environment variable           | Description                                                                                                                                                                                                          | Default value       |
-| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
-| `GLOBAL_OPTIONS`               | the [global options block](https://caddyserver.com/docs/caddyfile/options#global-options) to inject in the `Caddyfile`, one per line                                                                                 |                     |
-| `CADDY_EXTRA_CONFIG`           | the [snippet](https://caddyserver.com/docs/caddyfile/concepts#snippets) or the [named-routes](https://caddyserver.com/docs/caddyfile/concepts#named-routes) options block to inject in the `Caddyfile`, one per line |                     |
-| `CADDY_SERVER_EXTRA_DIRECTIVES`| [`Caddyfile` directives](https://caddyserver.com/docs/caddyfile/concepts#directives)                                                                                                                                 |                     |
-| `SERVER_NAME`                  | the server name or address                                                                                                                                                                                           | `localhost`         |
-| `MERCURE_PUBLISHER_JWT_KEY`    | the JWT key to use for publishers                                                                                                                                                                                    |                     |
-| `MERCURE_PUBLISHER_JWT_ALG`    | the JWT algorithm to use for publishers                                                                                                                                                                              | `HS256`             |
-| `MERCURE_SUBSCRIBER_JWT_KEY`   | the JWT key to use for subscribers                                                                                                                                                                                   |                     |
-| `MERCURE_SUBSCRIBER_JWT_ALG`   | the JWT algorithm to use for subscribers                                                                                                                                                                             | `HS256`             |
-| `MERCURE_EXTRA_DIRECTIVES`     | a list of extra [Mercure directives](#directives) inject in the Caddy file, one per line                                                                                                                             |                     |
-| `MERCURE_LICENSE`              | the license to use ([only applicable for the HA version](cluster.md))                                                                                                                                                |                     |
+| Environment variable            | Description                                                                                                                                                                                                          | Default value |
+| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
+| `GLOBAL_OPTIONS`                | the [global options block](https://caddyserver.com/docs/caddyfile/options#global-options) to inject in the `Caddyfile`, one per line                                                                                 |               |
+| `CADDY_EXTRA_CONFIG`            | the [snippet](https://caddyserver.com/docs/caddyfile/concepts#snippets) or the [named-routes](https://caddyserver.com/docs/caddyfile/concepts#named-routes) options block to inject in the `Caddyfile`, one per line |               |
+| `CADDY_SERVER_EXTRA_DIRECTIVES` | [`Caddyfile` directives](https://caddyserver.com/docs/caddyfile/concepts#directives)                                                                                                                                 |               |
+| `SERVER_NAME`                   | the server name or address                                                                                                                                                                                           | `localhost`   |
+| `MERCURE_PUBLISHER_JWT_KEY`     | the JWT key to use for publishers                                                                                                                                                                                    |               |
+| `MERCURE_PUBLISHER_JWT_ALG`     | the JWT algorithm to use for publishers                                                                                                                                                                              | `HS256`       |
+| `MERCURE_SUBSCRIBER_JWT_KEY`    | the JWT key to use for subscribers                                                                                                                                                                                   |               |
+| `MERCURE_SUBSCRIBER_JWT_ALG`    | the JWT algorithm to use for subscribers                                                                                                                                                                             | `HS256`       |
+| `MERCURE_EXTRA_DIRECTIVES`      | a list of extra [Mercure directives](#directives) inject in the Caddy file, one per line                                                                                                                             |               |
+| `MERCURE_LICENSE`               | the license to use ([only applicable for the HA version](cluster.md))                                                                                                                                                |               |
 
 ## HealthCheck
 
 The Mercure.rocks Hub provides a `/healthz` endpoint that returns a `200 OK` status code if the server is healthy.
 
 Here is an example of how to use the health check in a Docker Compose file:
+
 ```yaml
 # compose.yaml
 services:
@@ -127,7 +128,7 @@ MERCURE_SUBSCRIBER_JWT_ALG=RS256 \
 ## Bolt Adapter
 
 | Option              | Description                                                                                                                                                         |
-|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `path`              | path of the database file (default: `mercure.db`)                                                                                                                   |
 | `bucket_name`       | name of the bolt bucket to store events. default to `updates`                                                                                                       |
 | `cleanup_frequency` | chances to trigger history cleanup when an update occurs, must be a number between `0` (never cleanup) and `1` (cleanup after every publication), default to `0.3`. |
@@ -165,16 +166,16 @@ Run `./mercure -h` to see all available command-line flags.
 
 Configuration files must be named `mercure.<format>` (ex: `mercure.yaml`) and stored in one of the following directories:
 
-* the current directory (`$PWD`)
-* `~/.config/mercure/` (or any other XDG configuration directory set with the `XDG_CONFIG_HOME` environment variable)
-* `/etc/mercure`
+- the current directory (`$PWD`)
+- `~/.config/mercure/` (or any other XDG configuration directory set with the `XDG_CONFIG_HOME` environment variable)
+- `/etc/mercure`
 
 Most configuration parameters are hot reloaded: changes made to environment variables or configuration files are immediately taken into account, without having to restart the hub.
 
 When using environment variables, list must be space separated. As flags parameters, they must be comma separated.
 
 | Parameter                  | Description                                                                                                                                                                                                                                                                                                                                                                                  | Default                                                  |
-|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
+| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
 | `acme_cert_dir`            | the directory where to store Let's Encrypt certificates                                                                                                                                                                                                                                                                                                                                      |                                                          |
 | `acme_hosts`               | a list of hosts for which Let's Encrypt certificates must be issued                                                                                                                                                                                                                                                                                                                          |                                                          |
 | `acme_http01_addr`         | the address used by the acme server to listen on (example: `0.0.0.0:8080`)                                                                                                                                                                                                                                                                                                                   | `:http`                                                  |
diff --git a/docs/hub/cookbooks.md b/docs/hub/cookbooks.md
index 9931ff95..a39b5dd3 100644
--- a/docs/hub/cookbooks.md
+++ b/docs/hub/cookbooks.md
@@ -12,11 +12,10 @@ You may also be interested in spreading the load across several servers using [t
 
 To reproduce the problem, we provide [a load test](load-test.md) that you can use to stress your infrastructure.
 
-
 ## Using Mercure Behind a Reverse Proxy
 
 Mercure hubs run perfectly well behind reverse proxies.
 Here are some configuration for popular proxies:
 
-* [NGINX](nginx.md)
-* [Traefik](traefik.md)
+- [NGINX](nginx.md)
+- [Traefik](traefik.md)
diff --git a/docs/hub/debug.md b/docs/hub/debug.md
index c4a26a9d..c5e50ca1 100644
--- a/docs/hub/debug.md
+++ b/docs/hub/debug.md
@@ -14,7 +14,7 @@ To enable the profiler, add the `debug` global directive to your `Caddyfile`:
 # ...
 ```
 
-If you use the default `Caddyfile`,  you can also set the `GLOBAL_OPTIONS` environment variable to `debug`.
+If you use the default `Caddyfile`, you can also set the `GLOBAL_OPTIONS` environment variable to `debug`.
 
 The route exposing profiling data is now available at `http://localhost:2019/debug/pprof/`.
 You can use [the `pprof` tool](https://golang.org/pkg/net/http/pprof/) to visualize it.
diff --git a/docs/hub/install.md b/docs/hub/install.md
index 110f1275..852dc91c 100644
--- a/docs/hub/install.md
+++ b/docs/hub/install.md
@@ -11,7 +11,7 @@ The Mercure.rocks hub is available as a custom build of the [Caddy web server](h
 
 First, download the archive corresponding to your operating system and architecture [from the release page](https://github.com/dunglas/mercure/releases), extract the archive and open a shell in the resulting directory.
 
-*Note:* macOS users must download the `Darwin` binary, then run `xattr -d com.apple.quarantine ./mercure` [to release the hub from quarantine](troubleshooting.md#macos-localhost-installation-error).
+_Note:_ macOS users must download the `Darwin` binary, then run `xattr -d com.apple.quarantine ./mercure` [to release the hub from quarantine](troubleshooting.md#macos-localhost-installation-error).
 
 To start the Mercure.rocks Hub in development mode on Linux and macOS, run:
 
@@ -27,13 +27,13 @@ On Windows, start PowerShell, go into the extracted directory and run:
 $env:MERCURE_PUBLISHER_JWT_KEY='!ChangeThisMercureHubJWTSecretKey!'; $env:MERCURE_SUBSCRIBER_JWT_KEY='!ChangeThisMercureHubJWTSecretKey!'; .\mercure.exe run --config dev.Caddyfile
 ```
 
-*Note:* The Windows Defender Firewall will ask you if you want to allow `mercure.exe` to communicate through it.
+_Note:_ The Windows Defender Firewall will ask you if you want to allow `mercure.exe` to communicate through it.
 Allow it for both public and private networks. If you use an antivirus, or another firewall software, be sure to whitelist `mercure.exe`.
 
 The server is now available on `https://localhost` (TLS is automatically enabled, [learn how to disable it](config.md)).
 In development mode, anonymous subscribers are allowed and the debug UI is available on `https://localhost/.well-known/mercure/ui/`.
 
-*Note:* if you get an error similar to `bind: address already in use`, it means that the port `80` or `443` is already used by another service (the usual suspects are Apache and NGINX). Before starting Mercure, stop the service using the port(s) first, or set the `SERVER_NAME` environment variable to use a free port (ex: `SERVER_NAME=:3000`).
+_Note:_ if you get an error similar to `bind: address already in use`, it means that the port `80` or `443` is already used by another service (the usual suspects are Apache and NGINX). Before starting Mercure, stop the service using the port(s) first, or set the `SERVER_NAME` environment variable to use a free port (ex: `SERVER_NAME=:3000`).
 
 To run the server in production mode, run this command:
 
@@ -48,8 +48,8 @@ To change these default settings, [learn how to configure the Mercure.rocks hub]
 
 When the server is up and running, the following endpoints are available:
 
-* `POST https://localhost/.well-known/mercure`: to publish updates
-* `GET https://localhost/.well-known/mercure`: to subscribe to updates
+- `POST https://localhost/.well-known/mercure`: to publish updates
+- `GET https://localhost/.well-known/mercure`: to subscribe to updates
 
 See [the protocol](../../spec/mercure.md) for more details about these endpoints.
 
@@ -122,8 +122,8 @@ services:
     environment:
       # Uncomment the following line to disable HTTPS
       #SERVER_NAME: ':80'
-      MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
-      MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
+      MERCURE_PUBLISHER_JWT_KEY: "!ChangeThisMercureHubJWTSecretKey!"
+      MERCURE_SUBSCRIBER_JWT_KEY: "!ChangeThisMercureHubJWTSecretKey!"
     # Uncomment the following line to enable the development mode
     #command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
     healthcheck:
@@ -132,8 +132,8 @@ services:
       retries: 5
       start_period: 60s
     ports:
-      - '80:80'
-      - '443:443'
+      - "80:80"
+      - "443:443"
     volumes:
       - mercure_data:/data
       - mercure_config:/config
@@ -152,6 +152,7 @@ Mercure.rocks is available [on the AUR](https://aur.archlinux.org/packages/mercu
 ```console
 yay -S mercure
 ```
+
 Or download the `PKGBUILD` and compile and install it: `makepkg -sri`.
 
 ## Custom Caddy Build
diff --git a/docs/hub/license.md b/docs/hub/license.md
index 5128570b..8eff1557 100644
--- a/docs/hub/license.md
+++ b/docs/hub/license.md
@@ -2,10 +2,10 @@
 
 tl;dr:
 
-* proprietary software **can** implement the Mercure specification
-* proprietary publishers and subscribers **can** be used with the Mercure.rocks Hub without having to share their sources
-* modifications made to the Mercure.rocks Hub **must** be shared
-* alternatively, [commercial licenses are available for the Mercure.rocks Hub](https://mercure.rocks/pricing)
+- proprietary software **can** implement the Mercure specification
+- proprietary publishers and subscribers **can** be used with the Mercure.rocks Hub without having to share their sources
+- modifications made to the Mercure.rocks Hub **must** be shared
+- alternatively, [commercial licenses are available for the Mercure.rocks Hub](https://mercure.rocks/pricing)
 
 [The specification](../../spec/mercure.md) is available under [the IETF copyright policy](https://trustee.ietf.org/copyright-faq.html). The Mercure **specification** can be implemented by any software, including proprietary software.
 
diff --git a/docs/hub/load-test.md b/docs/hub/load-test.md
index 788ec5ed..ae3ae1f5 100644
--- a/docs/hub/load-test.md
+++ b/docs/hub/load-test.md
@@ -15,19 +15,19 @@ To test your own infrastructure, we provide a [Gatling](https://gatling.io)-base
 
 Available environment variables (all are optional):
 
-* `HUB_URL`: the URL of the hub to test
-* `JWT`: the JWT to use for authenticating publishers
-* `SUBSCRIBER_JWT`: the JWT to use for authenticating subscribers, fallbacks to `JWT` not set and `PRIVATE_UPDATES` set
-* `INITIAL_SUBSCRIBERS`: the number of concurrent subscribers initially connected
-* `SUBSCRIBERS_RATE_FROM`: minimum rate (per second) of additional subscribers to connect
-* `SUBSCRIBERS_RATE_TO`: maximum rate (per second) of additional subscribers to connect
-* `PUBLISHERS_RATE_FROM`: minimum rate (per second) of publications
-* `PUBLISHERS_RATE_TO`: maximum rate (per second) of publications
-* `INJECTION_DURATION`: duration of the publishers injection
-* `CONNECTION_DURATION`: duration of subscribers' connection
-* `RANDOM_CONNECTION_DURATION`: to randomize the connection duration (will longs `CONNECTION_DURATION` at max)
-* `PRIVATE_UPDATES`: to send private updates with random topics instead of public updates always with the same topic
+- `HUB_URL`: the URL of the hub to test
+- `JWT`: the JWT to use for authenticating publishers
+- `SUBSCRIBER_JWT`: the JWT to use for authenticating subscribers, fallbacks to `JWT` not set and `PRIVATE_UPDATES` set
+- `INITIAL_SUBSCRIBERS`: the number of concurrent subscribers initially connected
+- `SUBSCRIBERS_RATE_FROM`: minimum rate (per second) of additional subscribers to connect
+- `SUBSCRIBERS_RATE_TO`: maximum rate (per second) of additional subscribers to connect
+- `PUBLISHERS_RATE_FROM`: minimum rate (per second) of publications
+- `PUBLISHERS_RATE_TO`: maximum rate (per second) of publications
+- `INJECTION_DURATION`: duration of the publishers injection
+- `CONNECTION_DURATION`: duration of subscribers' connection
+- `RANDOM_CONNECTION_DURATION`: to randomize the connection duration (will longs `CONNECTION_DURATION` at max)
+- `PRIVATE_UPDATES`: to send private updates with random topics instead of public updates always with the same topic
 
 ## See Also
 
-* [Conformance tests](../ecosystem/conformance-tests.md)
+- [Conformance tests](../ecosystem/conformance-tests.md)
diff --git a/docs/hub/traefik.md b/docs/hub/traefik.md
index c468c24c..9cbea484 100644
--- a/docs/hub/traefik.md
+++ b/docs/hub/traefik.md
@@ -1,6 +1,6 @@
 # Use the Mercure.rocks Hub with Traefik Proxy
 
-[Traefik](https://doc.traefik.io/traefik/) is a free and open source *edge router* poular in the Docker and Kubernetes ecosystems.
+[Traefik](https://doc.traefik.io/traefik/) is a free and open source _edge router_ poular in the Docker and Kubernetes ecosystems.
 
 The following Docker Compose file exposes a Mercure.rocks hub through Traefik:
 
@@ -13,9 +13,9 @@ services:
     command: --api.insecure=true --providers.docker
     ports:
       # The HTTP port
-      - '80:80'
+      - "80:80"
       # The Web UI (enabled by --api.insecure=true)
-      - '8080:8080'
+      - "8080:8080"
     volumes:
       # So that Traefik can listen to the Docker events
       - /var/run/docker.sock:/var/run/docker.sock
@@ -26,13 +26,21 @@ services:
     restart: unless-stopped
     environment:
       # Disables Mercure.rocks auto-HTTPS feature, HTTPS must be handled at edge by Traefik or another proxy in front of it
-      SERVER_NAME: ':80'
-      MERCURE_PUBLISHER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
-      MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeThisMercureHubJWTSecretKey!'
+      SERVER_NAME: ":80"
+      MERCURE_PUBLISHER_JWT_KEY: "!ChangeThisMercureHubJWTSecretKey!"
+      MERCURE_SUBSCRIBER_JWT_KEY: "!ChangeThisMercureHubJWTSecretKey!"
     # Enables the development mode, comment the following line to run the hub in prod mode
     command: /usr/bin/caddy run --config /etc/caddy/dev.Caddyfile
     healthcheck:
-      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/healthz"]
+      test:
+        [
+          "CMD",
+          "wget",
+          "--no-verbose",
+          "--tries=1",
+          "--spider",
+          "http://localhost/healthz",
+        ]
       timeout: 5s
       retries: 5
       start_period: 60s
diff --git a/docs/hub/troubleshooting.md b/docs/hub/troubleshooting.md
index 55c413e2..d32dbce4 100644
--- a/docs/hub/troubleshooting.md
+++ b/docs/hub/troubleshooting.md
@@ -2,13 +2,13 @@
 
 ## 401 Unauthorized
 
-* Double-check that the request to the hub includes an authorization cookie (the default name is  `mercureAuthorization`) or an `Authorization` HTTP header
-* If the cookie isn't set, you may have to explicitly include [the request credentials](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) (`new EventSource(url, {withCredentials: true})` and `fetch(url, {credentials: 'include'})`)
-* Check the logs written by the hub on `stderr`, they contain the exact reason why the token has been rejected
-* Be sure to set a **secret key** (and not a JWT) in `JWT_KEY` (or in `SUBSCRIBER_JWT_KEY` and `PUBLISHER_JWT_KEY`)
-* If the secret key contains special characters, be sure to escape them properly, especially if you set the environment variable in a shell, or in a YAML file (Kubernetes...)
-* The publisher always needs a valid JWT, even if the `anonymous` directive is present in the `Caddyfile`, this JWT **must** have a property named `publish`. To dispatch private updates, the `publish` property must contain the list of topic selectors this publisher can use ([example](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM))
-* The subscriber needs a valid JWT only if the `anonymous` directive isn't present in the `Caddyfile`, or to subscribe to private updates, in this case the JWT **must** have a property named `subscribe` and containing an array of topic selectors ([example](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM))
+- Double-check that the request to the hub includes an authorization cookie (the default name is `mercureAuthorization`) or an `Authorization` HTTP header
+- If the cookie isn't set, you may have to explicitly include [the request credentials](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) (`new EventSource(url, {withCredentials: true})` and `fetch(url, {credentials: 'include'})`)
+- Check the logs written by the hub on `stderr`, they contain the exact reason why the token has been rejected
+- Be sure to set a **secret key** (and not a JWT) in `JWT_KEY` (or in `SUBSCRIBER_JWT_KEY` and `PUBLISHER_JWT_KEY`)
+- If the secret key contains special characters, be sure to escape them properly, especially if you set the environment variable in a shell, or in a YAML file (Kubernetes...)
+- The publisher always needs a valid JWT, even if the `anonymous` directive is present in the `Caddyfile`, this JWT **must** have a property named `publish`. To dispatch private updates, the `publish` property must contain the list of topic selectors this publisher can use ([example](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM))
+- The subscriber needs a valid JWT only if the `anonymous` directive isn't present in the `Caddyfile`, or to subscribe to private updates, in this case the JWT **must** have a property named `subscribe` and containing an array of topic selectors ([example](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM))
 
 For both the `publish` property, the array can be empty to publish only public updates. For both `publish` and `subscribe`, you can use `["*"]` to match all topics.
 
@@ -18,8 +18,8 @@ If the app connecting to the Mercure hub and the hub itself are not served from
 
 The usual symptoms of a CORS misconfiguration are errors about missing CORS HTTP headers in the console of the browser:
 
-* Chrome: `Refused to connect to 'https://hub.example.com/.well-known/mercure?topic=foo' because it violates the following Content Security Policy directive`
-* Firefox: `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://hub.example.com/.well-known/mercure?topic=foo. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)`
+- Chrome: `Refused to connect to 'https://hub.example.com/.well-known/mercure?topic=foo' because it violates the following Content Security Policy directive`
+- Firefox: `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://hub.example.com/.well-known/mercure?topic=foo. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)`
 
 To fix these errors, set the list of domains allowed to connect to the hub as value of the `cors_origins` in the `Caddyfile`. Example: `cors_origins https://example.com https://example.net`. Don't forget the `https://` prefix before the domain name!
 
@@ -60,9 +60,9 @@ The attribute only needs to be deleted once.
 
 On macOS Catalina and later versions, follow these steps:
 
-* In the Finder on your Mac, locate the app that you want to open
-* Control-click on the app icon, then choose "Open" from the shortcut menu
-* Click "Open"
+- In the Finder on your Mac, locate the app that you want to open
+- Control-click on the app icon, then choose "Open" from the shortcut menu
+- Click "Open"
 
 Then you will have a warning, ignore it and close the Terminal.
 
diff --git a/docs/mercure.md b/docs/mercure.md
index 6c3a4e65..c9735250 100644
--- a/docs/mercure.md
+++ b/docs/mercure.md
@@ -10,27 +10,27 @@ A free (as in beer, and as in speech) reference server, a commercial High Availa
 
 ## The Protocol
 
-* native browser support, no lib nor SDK required (built on top of HTTP and [server-sent events](https://www.smashingmagazine.com/2018/02/sse-websockets-data-flow-http2/))
-* compatible with all existing servers, even those who don't support persistent connections (serverless architecture, PHP, FastCGI...)
-* built-in connection re-establishment and state reconciliation
-* [JWT](https://jwt.io/)-based authorization mechanism (securely dispatch an update to some selected subscribers)
-* performant, leverages [HTTP multiplexing](https://web.dev/performance-http2/#request-and-response-multiplexing)
-* designed with [hypermedia in mind](https://en.wikipedia.org/wiki/HATEOAS), also supports [GraphQL](https://graphql.org/)
-* auto-discoverable through [web linking](https://tools.ietf.org/html/rfc5988)
-* message encryption support
-* can work with old browsers (IE7+) using [an `EventSource` polyfill](ecosystem/awesome.md#useful-related-libraries)
-* [connection-less push](https://html.spec.whatwg.org/multipage/server-sent-events.html#eventsource-push) in controlled environments (e.g. browsers on mobile handsets tied to specific carriers)
+- native browser support, no lib nor SDK required (built on top of HTTP and [server-sent events](https://www.smashingmagazine.com/2018/02/sse-websockets-data-flow-http2/))
+- compatible with all existing servers, even those who don't support persistent connections (serverless architecture, PHP, FastCGI...)
+- built-in connection re-establishment and state reconciliation
+- [JWT](https://jwt.io/)-based authorization mechanism (securely dispatch an update to some selected subscribers)
+- performant, leverages [HTTP multiplexing](https://web.dev/performance-http2/#request-and-response-multiplexing)
+- designed with [hypermedia in mind](https://en.wikipedia.org/wiki/HATEOAS), also supports [GraphQL](https://graphql.org/)
+- auto-discoverable through [web linking](https://tools.ietf.org/html/rfc5988)
+- message encryption support
+- can work with old browsers (IE7+) using [an `EventSource` polyfill](ecosystem/awesome.md#useful-related-libraries)
+- [connection-less push](https://html.spec.whatwg.org/multipage/server-sent-events.html#eventsource-push) in controlled environments (e.g. browsers on mobile handsets tied to specific carriers)
 
 [Read the specification](../spec/mercure.md)
 
 ## The Hub
 
-* Fast, written in Go
-* Works everywhere: [static binaries](hub/install.md#prebuilt-binary), [Docker image](hub/install.md#docker-image) and [Kubernetes chart](hub/install.md#kubernetes)
-* Automatic HTTP/2, HTTP/3 and HTTPS (using Let's Encrypt) support
-* [Clustering and High Availability support](hub/cluster.md)
-* CORS support, CSRF protection mechanism
-* Cloud Native, follows [the Twelve-Factor App](https://12factor.net) methodology
-* Free and Open source (AGPL), SaaS, and commercial versions available
+- Fast, written in Go
+- Works everywhere: [static binaries](hub/install.md#prebuilt-binary), [Docker image](hub/install.md#docker-image) and [Kubernetes chart](hub/install.md#kubernetes)
+- Automatic HTTP/2, HTTP/3 and HTTPS (using Let's Encrypt) support
+- [Clustering and High Availability support](hub/cluster.md)
+- CORS support, CSRF protection mechanism
+- Cloud Native, follows [the Twelve-Factor App](https://12factor.net) methodology
+- Free and Open source (AGPL), SaaS, and commercial versions available
 
 [Get your hub](hub/install.md)
diff --git a/docs/spec/faq.md b/docs/spec/faq.md
index b6774593..b652f2b9 100644
--- a/docs/spec/faq.md
+++ b/docs/spec/faq.md
@@ -56,7 +56,10 @@ Also, Mercure can easily be integrated with Apollo GraphQL by creating [a dedica
 Cookies are automatically sent by the browser when opening an `EventSource` connection if the `withCredentials` property is set to `true`:
 
 ```javascript
-const eventSource = new EventSource('https://example.com/.well-known/mercure?topic=foo', {
-    withCredentials: true
-});
+const eventSource = new EventSource(
+  "https://example.com/.well-known/mercure?topic=foo",
+  {
+    withCredentials: true,
+  },
+);
 ```
diff --git a/docs/spec/use-cases.md b/docs/spec/use-cases.md
index dbf6fe75..fe295379 100644
--- a/docs/spec/use-cases.md
+++ b/docs/spec/use-cases.md
@@ -1,13 +1,12 @@
-
 # Case Studies and Use Cases
 
 From building a chat room to expose a fully-featured event store, Mercure covers a broad spectrum of use cases related to realtime and async APIs.
 
 ## Case Studies
 
-* [How Raven Controls is using Mercure to power major events such as Cop 21 and Euro 2020](https://api-platform.com/con/2022/conferences/real-time-and-beyond-with-mercure/)
-* [Pushing 8 million of Mercure notifications per day to run mail.tm](https://les-tilleuls.coop/en/blog/mail-tm-mercure-rocks-and-api-platform)
-* [100,000 simultaneous Mercure users to power iGraal](https://speakerdeck.com/dunglas/mercure-real-time-for-php-made-easy?slide=52)
+- [How Raven Controls is using Mercure to power major events such as Cop 21 and Euro 2020](https://api-platform.com/con/2022/conferences/real-time-and-beyond-with-mercure/)
+- [Pushing 8 million of Mercure notifications per day to run mail.tm](https://les-tilleuls.coop/en/blog/mail-tm-mercure-rocks-and-api-platform)
+- [100,000 simultaneous Mercure users to power iGraal](https://speakerdeck.com/dunglas/mercure-real-time-for-php-made-easy?slide=52)
 
 Example of usage: the Mercure integration in [API Platform](https://api-platform.com/docs/client-generator):
 
@@ -19,20 +18,20 @@ Here are some popular use cases:
 
 ### Live Availability
 
-* a webapp retrieves the availability status of a product from a REST API and displays it: only one is still available
-* 3 minutes later, the last product is bought by another customer
-* the webapp's view instantly shows that this product isn't available anymore
+- a webapp retrieves the availability status of a product from a REST API and displays it: only one is still available
+- 3 minutes later, the last product is bought by another customer
+- the webapp's view instantly shows that this product isn't available anymore
 
 ### Asynchronous Jobs
 
-* a webapp tells the server to compute a report, this task is costly and will take some time to complete
-* the server delegates the computation of the report to an asynchronous worker (using message queue), and closes the connection with the webapp
-* the worker sends the report to the webapp when it is computed
+- a webapp tells the server to compute a report, this task is costly and will take some time to complete
+- the server delegates the computation of the report to an asynchronous worker (using message queue), and closes the connection with the webapp
+- the worker sends the report to the webapp when it is computed
 
 ### Collaborative Editing
 
-* a webapp allows several users to edit the same document concurrently
-* changes made are immediately broadcasted to all connected users
+- a webapp allows several users to edit the same document concurrently
+- changes made are immediately broadcasted to all connected users
 
 **Mercure gets you covered!**
 
diff --git a/examples/chat/chart/mercure-example-chat/README.md b/examples/chat/chart/mercure-example-chat/README.md
index b24dfe30..60a40953 100644
--- a/examples/chat/chart/mercure-example-chat/README.md
+++ b/examples/chat/chart/mercure-example-chat/README.md
@@ -6,40 +6,41 @@ A minimalist chat system, using Mercure and the Flask microframework to handle c
 
 ## Values
 
-| Key | Type | Default | Description |
-|-----|------|---------|-------------|
-| affinity | object | `{}` |  |
-| autoscaling.enabled | bool | `false` |  |
-| autoscaling.maxReplicas | int | `100` |  |
-| autoscaling.minReplicas | int | `1` |  |
-| autoscaling.targetCPUUtilizationPercentage | int | `80` |  |
-| cookieDomain | string | `".mercure.rocks"` |  |
-| fullnameOverride | string | `""` |  |
-| hubUrl | string | `"https://demo.mercure.rocks/.well-known/mercure"` |  |
-| image.pullPolicy | string | `"Always"` |  |
-| image.repository | string | `"dunglas/mercure-example-chat"` |  |
-| image.tag | string | `"latest"` |  |
-| imagePullSecrets | list | `[]` |  |
-| ingress.annotations | object | `{}` |  |
-| ingress.enabled | bool | `false` |  |
-| ingress.hosts[0].host | string | `"chart-example.local"` |  |
-| ingress.hosts[0].paths | list | `[]` |  |
-| ingress.tls | list | `[]` |  |
-| jwtKey | string | `"!ChangeThisMercureHubJWTSecretKey!"` |  |
-| messageUriTemplate | string | `"https://chat.example.com/messages/{id}"` |  |
-| nameOverride | string | `""` |  |
-| nodeSelector | object | `{}` |  |
-| podAnnotations | object | `{}` |  |
-| podSecurityContext | object | `{}` |  |
-| replicaCount | int | `1` |  |
-| resources | object | `{}` |  |
-| securityContext | object | `{}` |  |
-| service.port | int | `80` |  |
-| service.type | string | `"ClusterIP"` |  |
-| serviceAccount.annotations | object | `{}` |  |
-| serviceAccount.create | bool | `true` |  |
-| serviceAccount.name | string | `""` |  |
-| tolerations | list | `[]` |  |
+| Key                                        | Type   | Default                                            | Description |
+| ------------------------------------------ | ------ | -------------------------------------------------- | ----------- |
+| affinity                                   | object | `{}`                                               |             |
+| autoscaling.enabled                        | bool   | `false`                                            |             |
+| autoscaling.maxReplicas                    | int    | `100`                                              |             |
+| autoscaling.minReplicas                    | int    | `1`                                                |             |
+| autoscaling.targetCPUUtilizationPercentage | int    | `80`                                               |             |
+| cookieDomain                               | string | `".mercure.rocks"`                                 |             |
+| fullnameOverride                           | string | `""`                                               |             |
+| hubUrl                                     | string | `"https://demo.mercure.rocks/.well-known/mercure"` |             |
+| image.pullPolicy                           | string | `"Always"`                                         |             |
+| image.repository                           | string | `"dunglas/mercure-example-chat"`                   |             |
+| image.tag                                  | string | `"latest"`                                         |             |
+| imagePullSecrets                           | list   | `[]`                                               |             |
+| ingress.annotations                        | object | `{}`                                               |             |
+| ingress.enabled                            | bool   | `false`                                            |             |
+| ingress.hosts[0].host                      | string | `"chart-example.local"`                            |             |
+| ingress.hosts[0].paths                     | list   | `[]`                                               |             |
+| ingress.tls                                | list   | `[]`                                               |             |
+| jwtKey                                     | string | `"!ChangeThisMercureHubJWTSecretKey!"`             |             |
+| messageUriTemplate                         | string | `"https://chat.example.com/messages/{id}"`         |             |
+| nameOverride                               | string | `""`                                               |             |
+| nodeSelector                               | object | `{}`                                               |             |
+| podAnnotations                             | object | `{}`                                               |             |
+| podSecurityContext                         | object | `{}`                                               |             |
+| replicaCount                               | int    | `1`                                                |             |
+| resources                                  | object | `{}`                                               |             |
+| securityContext                            | object | `{}`                                               |             |
+| service.port                               | int    | `80`                                               |             |
+| service.type                               | string | `"ClusterIP"`                                      |             |
+| serviceAccount.annotations                 | object | `{}`                                               |             |
+| serviceAccount.create                      | bool   | `true`                                             |             |
+| serviceAccount.name                        | string | `""`                                               |             |
+| tolerations                                | list   | `[]`                                               |             |
+
+---
 
-----------------------------------------------
 Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2)
diff --git a/examples/chat/templates/chat.html b/examples/chat/templates/chat.html
index b18ce3e0..6a87ba0d 100644
--- a/examples/chat/templates/chat.html
+++ b/examples/chat/templates/chat.html
@@ -1,54 +1,62 @@
-{% extends "layout.html" %}
-{% block content %}
+{% extends "layout.html" %} {% block content %}
 <section class="section" id="chat">
-    <div class="columns">
-        <div class="column is-three-quarters">
-            <div class="box">
-                <template id="message">
-                    <div><strong class="username">Kévin</strong>: <span class="msg"></span></div>
-                </template>
-                <div id="messages"></div>
+  <div class="columns">
+    <div class="column is-three-quarters">
+      <div class="box">
+        <template id="message">
+          <div>
+            <strong class="username">Kévin</strong>: <span class="msg"></span>
+          </div>
+        </template>
+        <div id="messages"></div>
 
-                <form>
-                    <div class="field has-addons">
-                        <div class="control is-expanded">
-                            <input name="message" placeholder="Your message..." autocomplete="off" required
-                                class="input is-small is-fullwidth">
-                        </div>
-                        <div class="control">
-                            <button class="button is-primary is-small">Post</button>
-                        </div>
-                    </div>
-                </form>
+        <form>
+          <div class="field has-addons">
+            <div class="control is-expanded">
+              <input
+                name="message"
+                placeholder="Your message..."
+                autocomplete="off"
+                required
+                class="input is-small is-fullwidth"
+              />
             </div>
-        </div>
-
-        <div class="column">
-            <div class="panel">
-                <p class="panel-heading">Online</p>
+            <div class="control">
+              <button class="button is-primary is-small">Post</button>
+            </div>
+          </div>
+        </form>
+      </div>
+    </div>
 
-                <template id="online-user">
-                    <div class="panel-block">
-                        <span class="panel-icon">
-                            <i class="fas fa-user" aria-hidden="true"></i>
-                        </span>
-                        <span class="username"></span>
-                    </div>
-                </template>
+    <div class="column">
+      <div class="panel">
+        <p class="panel-heading">Online</p>
 
-                <div class="panel-block is-active">
-                    <span class="panel-icon">
-                        <i class="fas fa-smile" aria-hidden="true"></i>
-                    </span>
-                    <span id="username" title="This is you"></span>
-                </div>
+        <template id="online-user">
+          <div class="panel-block">
+            <span class="panel-icon">
+              <i class="fas fa-user" aria-hidden="true"></i>
+            </span>
+            <span class="username"></span>
+          </div>
+        </template>
 
-                <div id="user-list"></div>
-            </div>
+        <div class="panel-block is-active">
+          <span class="panel-icon">
+            <i class="fas fa-smile" aria-hidden="true"></i>
+          </span>
+          <span id="username" title="This is you"></span>
         </div>
+
+        <div id="user-list"></div>
+      </div>
     </div>
+  </div>
 </section>
 
-<script type="application/json" id="config">{{ config|tojson|safe }}</script>
+<script type="application/json" id="config">
+  {{ config|tojson|safe }}
+</script>
 <script src="/static/chat.js"></script>
 {% endblock %}
diff --git a/examples/chat/templates/join.html b/examples/chat/templates/join.html
index 8d6be4c7..a5beaa2b 100644
--- a/examples/chat/templates/join.html
+++ b/examples/chat/templates/join.html
@@ -1,19 +1,24 @@
-{% extends "layout.html" %}
-{% block content %}
+{% extends "layout.html" %} {% block content %}
 <section class="section has-text-centered">
-    <h1 class="title">Join the chat</h1>
-    <form method="POST" action="{{ url_for('chat') }}">
-        <div class="field is-grouped is-grouped-centered">
-            <div class="control has-icons-left">
-                <input name="username" placeholder="Username" size="20" required class="input">
-                <span class="icon is-small is-left">
-                    <i class="fas fa-smile"></i>
-                </span>
-            </div>
-            <div class="control">
-                <button class="button is-primary">Join!</button>
-            </div>
-        </div>
-    </form>
+  <h1 class="title">Join the chat</h1>
+  <form method="POST" action="{{ url_for('chat') }}">
+    <div class="field is-grouped is-grouped-centered">
+      <div class="control has-icons-left">
+        <input
+          name="username"
+          placeholder="Username"
+          size="20"
+          required
+          class="input"
+        />
+        <span class="icon is-small is-left">
+          <i class="fas fa-smile"></i>
+        </span>
+      </div>
+      <div class="control">
+        <button class="button is-primary">Join!</button>
+      </div>
+    </div>
+  </form>
 </section>
 {% endblock %}
diff --git a/examples/chat/templates/layout.html b/examples/chat/templates/layout.html
index 141c1bf3..3650d3fd 100644
--- a/examples/chat/templates/layout.html
+++ b/examples/chat/templates/layout.html
@@ -1,40 +1,54 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+    <title>A chat using Mercure</title>
+    <link
+      rel="stylesheet"
+      href="https://cdn.jsdelivr.net/npm/bulma@0.8/css/bulma.min.css"
+      crossorigin="anonymous"
+    />
+    <link
+      rel="stylesheet"
+      href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5/css/all.min.css"
+      crossorigin="anonymous"
+    />
+    <link rel="stylesheet" href="/static/app.css" />
+  </head>
 
-<head>
-  <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <meta http-equiv="X-UA-Compatible" content="ie=edge">
-  <title>A chat using Mercure</title>
-  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8/css/bulma.min.css" crossorigin="anonymous">
-  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5/css/all.min.css"
-    crossorigin="anonymous">
-  <link rel="stylesheet" href="/static/app.css">
-</head>
-
-<body>
-  <div class="hero is-light is-small">
-    <div class="hero-body has-text-centered">
-      <div class="container">
-        <div class="column is-three-fifths is-offset-one-fifth">
-          <h1 class="title">
-            <a href="https://mercure.rocks"><img src="https://mercure.rocks/static/logo.svg" alt="Mercure"></a>
-          </h1>
-          <h2 class="subtitle">Demo Chat App (<a
-              href="https://github.com/dunglas/mercure/tree/master/examples/chat">sources</a>)</h2>
+  <body>
+    <div class="hero is-light is-small">
+      <div class="hero-body has-text-centered">
+        <div class="container">
+          <div class="column is-three-fifths is-offset-one-fifth">
+            <h1 class="title">
+              <a href="https://mercure.rocks"
+                ><img src="https://mercure.rocks/static/logo.svg" alt="Mercure"
+              /></a>
+            </h1>
+            <h2 class="subtitle">
+              Demo Chat App (<a
+                href="https://github.com/dunglas/mercure/tree/master/examples/chat"
+                >sources</a
+              >)
+            </h2>
+          </div>
         </div>
       </div>
     </div>
-  </div>
-  <main role="main" class="container">{% block content %}{% endblock %}</main>
-  <footer class="footer">
-    <div class="content has-text-centered">
-      <p>
-        This is a demo chat using <a href="https://mercure.rocks">Mercure</a>.<br><a
-          href="https://github.com/dunglas/mercure/tree/master/examples/chat">Source code</a>
-      </p>
-    </div>
-  </footer>
-</body>
-
+    <main role="main" class="container">{% block content %}{% endblock %}</main>
+    <footer class="footer">
+      <div class="content has-text-centered">
+        <p>
+          This is a demo chat using
+          <a href="https://mercure.rocks">Mercure</a>.<br /><a
+            href="https://github.com/dunglas/mercure/tree/master/examples/chat"
+            >Source code</a
+          >
+        </p>
+      </div>
+    </footer>
+  </body>
 </html>
diff --git a/public/index.html b/public/index.html
index 4c5f02e2..356fa893 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,278 +1,324 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
-
-<head>
+  <head>
     <meta charset="UTF-8" />
     <title>Mercure</title>
-    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9/css/bulma.min.css" crossorigin="anonymous" />
+    <link
+      rel="stylesheet"
+      href="https://cdn.jsdelivr.net/npm/bulma@0.9/css/bulma.min.css"
+      crossorigin="anonymous"
+    />
     <link rel="stylesheet" href="app.css" />
-</head>
+  </head>
 
-<body>
+  <body>
     <div class="hero is-light is-small">
-        <div class="hero-body has-text-centered">
-            <div class="container">
-                <div class="column is-three-fifths is-offset-one-fifth">
-                    <h1 class="title">
-                        <a href="https://mercure.rocks"><img src="https://mercure.rocks/static/logo.svg"
-                                alt="Mercure" /></a>
-                    </h1>
-                    <h2 class="subtitle">Debugging Tools</h2>
-                </div>
-            </div>
+      <div class="hero-body has-text-centered">
+        <div class="container">
+          <div class="column is-three-fifths is-offset-one-fifth">
+            <h1 class="title">
+              <a href="https://mercure.rocks"
+                ><img src="https://mercure.rocks/static/logo.svg" alt="Mercure"
+              /></a>
+            </h1>
+            <h2 class="subtitle">Debugging Tools</h2>
+          </div>
         </div>
+      </div>
     </div>
 
     <div class="container">
-        <div class="columns">
-            <div class="column is-half">
-                <section>
-                    <h2 class="title">Subscribe</h2>
-                    <form name="subscribe">
-                        <div class="field">
-                            <label class="label" for="subscribeTopics">Topics to get updates for*</label>
-                            <div class="control">
-                                <textarea class="textarea" name="topics" id="subscribeTopics" required>
-https://example.com/my-private-topic</textarea>
-                            </div>
-
-                            <div class="help">
-                                <p>
-                                    One
-                                    <a href="https://tools.ietf.org/html/rfc6570">URI template</a>
-                                    or string per line (<a href="https://uri-template-tester.mercure.rocks/">try the
-                                        tester</a>).
-                                </p>
-                                <p>Use <code>*</code> to subscribe to all topics.</p>
-                                <p>Examples:</p>
-                                <pre><code id="subscribeTopicsExamples"></code></pre>
-                            </div>
-                        </div>
-
-                        <div class="field">
-                            <label class="label" for="lastEventId">Last Event ID</label>
-                            <div class="control">
-                                <input class="input is-small" name="lastEventId" id="lastEventId" />
-                            </div>
-                        </div>
-
-                        <button class="button is-primary" name="subscribe">
-                            Subscribe
-                        </button>
-                        <button class="button is-warning" name="unsubscribe" disabled>
-                            Unsubscribe
-                        </button>
-                    </form>
+      <div class="columns">
+        <div class="column is-half">
+          <section>
+            <h2 class="title">Subscribe</h2>
+            <form name="subscribe">
+              <div class="field">
+                <label class="label" for="subscribeTopics"
+                  >Topics to get updates for*</label
+                >
+                <div class="control">
+                  <textarea
+                    class="textarea"
+                    name="topics"
+                    id="subscribeTopics"
+                    required
+                  >
+https://example.com/my-private-topic</textarea
+                  >
+                </div>
 
-                    <div id="updates">Not subscribed.</div>
+                <div class="help">
+                  <p>
+                    One
+                    <a href="https://tools.ietf.org/html/rfc6570"
+                      >URI template</a
+                    >
+                    or string per line (<a
+                      href="https://uri-template-tester.mercure.rocks/"
+                      >try the tester</a
+                    >).
+                  </p>
+                  <p>Use <code>*</code> to subscribe to all topics.</p>
+                  <p>Examples:</p>
+                  <pre><code id="subscribeTopicsExamples"></code></pre>
+                </div>
+              </div>
+
+              <div class="field">
+                <label class="label" for="lastEventId">Last Event ID</label>
+                <div class="control">
+                  <input
+                    class="input is-small"
+                    name="lastEventId"
+                    id="lastEventId"
+                  />
+                </div>
+              </div>
+
+              <button class="button is-primary" name="subscribe">
+                Subscribe
+              </button>
+              <button class="button is-warning" name="unsubscribe" disabled>
+                Unsubscribe
+              </button>
+            </form>
+
+            <div id="updates">Not subscribed.</div>
+
+            <template id="update">
+              <li>
+                <article>
+                  <h2></h2>
+                  <pre></pre>
+                </article>
+              </li>
+            </template>
+          </section>
+        </div>
 
-                    <template id="update">
-                        <li>
-                            <article>
-                                <h2></h2>
-                                <pre></pre>
-                            </article>
-                        </li>
-                    </template>
-                </section>
+        <section class="column is-half">
+          <h2 class="title">Publish</h2>
+
+          <form name="publish">
+            <div class="field">
+              <label class="label" for="publishTopics">Topics*</label>
+              <div class="control">
+                <textarea
+                  class="textarea"
+                  name="topics"
+                  id="publishTopics"
+                  required
+                >
+https://example.com/my-private-topic</textarea
+                >
+              </div>
+
+              <p class="help">
+                First line: canonical
+                <a href="https://tools.ietf.org/html/rfc4622">IRI</a> or
+                string.<br />
+                Next lines: alternate IRIs or strings.
+              </p>
             </div>
 
-            <section class="column is-half">
-                <h2 class="title">Publish</h2>
-
-                <form name="publish">
-                    <div class="field">
-                        <label class="label" for="publishTopics">Topics*</label>
-                        <div class="control">
-                            <textarea class="textarea" name="topics" id="publishTopics" required>
-https://example.com/my-private-topic</textarea>
-                        </div>
-
-                        <p class="help">
-                            First line: canonical
-                            <a href="https://tools.ietf.org/html/rfc4622">IRI</a> or
-                            string.<br />
-                            Next lines: alternate IRIs or strings.
-                        </p>
-                    </div>
-
-                    <div class="field">
-                        <label class="checkbox">
-                            <input type="checkbox" name="priv" id="priv" />
-                            <b>Private</b>
-                        </label>
-                    </div>
-
-                    <div class="field">
-                        <label class="label" for="data">Data</label>
-                        <div class="control">
-                            <textarea class="textarea" name="data" id="data"></textarea>
-                        </div>
-                    </div>
-
-                    <div class="field">
-                        <label class="label" for="eventId">Event ID</label>
-                        <div class="control">
-                            <input class="input is-small" name="id" id="eventId" />
-                        </div>
-                    </div>
-
-                    <div class="field">
-                        <label class="label" for="eventType">Event Type</label>
-                        <div class="control">
-                            <input class="input is-small" name="type" id="eventType" />
-                        </div>
-                    </div>
-
-                    <div class="field">
-                        <label class="label" for="eventRetry">Event Retry</label>
-                        <div class="control">
-                            <input class="input is-small" name="retry" id="eventRetry" />
-                        </div>
-                    </div>
+            <div class="field">
+              <label class="checkbox">
+                <input type="checkbox" name="priv" id="priv" />
+                <b>Private</b>
+              </label>
+            </div>
 
-                    <button class="button is-primary">Publish</button>
-                </form>
-            </section>
-        </div>
-        <hr />
-        <div class="columns">
-            <section class="column is-half">
-                <h2 class="title">Settings</h2>
+            <div class="field">
+              <label class="label" for="data">Data</label>
+              <div class="control">
+                <textarea class="textarea" name="data" id="data"></textarea>
+              </div>
+            </div>
 
-                <form name="settings">
-                    <div class="field">
-                        <label class="label" for="hubUrl">Hub URL*</label>
-                        <input class="input" type="url" name="hubUrl" id="hubUrl" required />
+            <div class="field">
+              <label class="label" for="eventId">Event ID</label>
+              <div class="control">
+                <input class="input is-small" name="id" id="eventId" />
+              </div>
+            </div>
 
-                        <p class="help">
-                            Will be filled automatically if you
-                            <a href="#discover">discover</a> a resource.
-                        </p>
-                    </div>
+            <div class="field">
+              <label class="label" for="eventType">Event Type</label>
+              <div class="control">
+                <input class="input is-small" name="type" id="eventType" />
+              </div>
+            </div>
 
-                    <div class="field">
-                        <label class="label">Authorization type</label>
+            <div class="field">
+              <label class="label" for="eventRetry">Event Retry</label>
+              <div class="control">
+                <input class="input is-small" name="retry" id="eventRetry" />
+              </div>
+            </div>
 
-                        <div class="control">
-                            <label class="radio">
-                                <input type="radio" name="authorization" value="cookie" />
-                                Authorization cookie
-                            </label>
-                            <label class="radio">
-                                <input type="radio" name="authorization" value="header" checked />
-                                <code>Authorization</code> HTTP Header
-                            </label>
-                        </div>
+            <button class="button is-primary">Publish</button>
+          </form>
+        </section>
+      </div>
+      <hr />
+      <div class="columns">
+        <section class="column is-half">
+          <h2 class="title">Settings</h2>
+
+          <form name="settings">
+            <div class="field">
+              <label class="label" for="hubUrl">Hub URL*</label>
+              <input
+                class="input"
+                type="url"
+                name="hubUrl"
+                id="hubUrl"
+                required
+              />
+
+              <p class="help">
+                Will be filled automatically if you
+                <a href="#discover">discover</a> a resource.
+              </p>
+            </div>
 
-                        <p class="help">
-                            The authorization cookie will be set
-                            automatically by the server only if you
-                            <a href="#discover">discover</a> a <b>demo</b> endpoint.
-                        </p>
-                    </div>
+            <div class="field">
+              <label class="label">Authorization type</label>
+
+              <div class="control">
+                <label class="radio">
+                  <input type="radio" name="authorization" value="cookie" />
+                  Authorization cookie
+                </label>
+                <label class="radio">
+                  <input
+                    type="radio"
+                    name="authorization"
+                    value="header"
+                    checked
+                  />
+                  <code>Authorization</code> HTTP Header
+                </label>
+              </div>
+
+              <p class="help">
+                The authorization cookie will be set automatically by the server
+                only if you
+                <a href="#discover">discover</a> a <b>demo</b> endpoint.
+              </p>
+            </div>
 
-                    <div class="field">
-                        <label class="label" for="jwt">JWT</label>
-                        <input class="input" type="text" name="jwt" id="jwt" required />
+            <div class="field">
+              <label class="label" for="jwt">JWT</label>
+              <input class="input" type="text" name="jwt" id="jwt" required />
 
-                        <div class="help">
-                            <p>
-                                Required to publish, or to subscribe to private updates.<br />
-                                Claim structure to use:
-                            </p>
-                            <pre><code>{
+              <div class="help">
+                <p>
+                  Required to publish, or to subscribe to private updates.<br />
+                  Claim structure to use:
+                </p>
+                <pre><code>{
   "mercure": {
     "subscribe": ["list of topic selectors, * for all, omit for public only"],
     "publish": ["list of topic selectors, * for all, omit to not allow to publish"]
   }
 }</code></pre>
-                            <br />
-                            <a href="https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM"
-                                target="_blank" rel="noopener noreferrer" class="button is-link is-small">Create a
-                                token</a>
-                            (demo key: <code>!ChangeThisMercureHubJWTSecretKey!</code>)
-                        </div>
-                    </div>
-                </form>
-            </section>
-
-            <section id="discover" class="column is-half">
-                <h2 class="title">Discover</h2>
-
-                <form name="discover">
-                    <div class="field">
-                        <label class="label" for="topic">Topic*</label>
-                        <div class="control">
-                            <input class="input" type="url" name="topic" id="topic" required />
-                        </div>
-
-                        <p class="help">
-                            URL returning a <code>Link rel="mercure"</code> HTTP header to
-                            init the discovery.<br />
-                            Demo endpoints: any subpath of <code>/demo</code>.
-                        </p>
-                    </div>
-
-                    <div class="field">
-                        <label class="label" for="body">Demo body</label>
-                        <div class="control">
-                            <textarea class="textarea" name="body" id="body"> </textarea>
-                        </div>
+                <br />
+                <a
+                  href="https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.KKPIikwUzRuB3DTpVw6ajzwSChwFw5omBMmMcWKiDcM"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  class="button is-link is-small"
+                  >Create a token</a
+                >
+                (demo key: <code>!ChangeThisMercureHubJWTSecretKey!</code>)
+              </div>
+            </div>
+          </form>
+        </section>
+
+        <section id="discover" class="column is-half">
+          <h2 class="title">Discover</h2>
+
+          <form name="discover">
+            <div class="field">
+              <label class="label" for="topic">Topic*</label>
+              <div class="control">
+                <input
+                  class="input"
+                  type="url"
+                  name="topic"
+                  id="topic"
+                  required
+                />
+              </div>
+
+              <p class="help">
+                URL returning a <code>Link rel="mercure"</code> HTTP header to
+                init the discovery.<br />
+                Demo endpoints: any subpath of <code>/demo</code>.
+              </p>
+            </div>
 
-                        <p class="help">
-                            Data to return. Supported only by <b>demo</b> endpoints.
-                        </p>
-                    </div>
+            <div class="field">
+              <label class="label" for="body">Demo body</label>
+              <div class="control">
+                <textarea class="textarea" name="body" id="body"> </textarea>
+              </div>
 
-                    <button class="button is-primary" name="discover">Discover</button>
-                </form>
-            </section>
-        </div>
-        <hr />
-        <div class="columns">
-            <div class="column is-full">
-                <h2 class="title">Active subscriptions</h2>
-                <form name="subscriptions">
-                    <button class="button is-primary" name="subscribe">
-                        Subscribe
-                    </button>
-                    <button class="button is-warning" name="unsubscribe" disabled>
-                        Unsubscribe
-                    </button>
-                </form>
+              <p class="help">
+                Data to return. Supported only by <b>demo</b> endpoints.
+              </p>
             </div>
+
+            <button class="button is-primary" name="discover">Discover</button>
+          </form>
+        </section>
+      </div>
+      <hr />
+      <div class="columns">
+        <div class="column is-full">
+          <h2 class="title">Active subscriptions</h2>
+          <form name="subscriptions">
+            <button class="button is-primary" name="subscribe">
+              Subscribe
+            </button>
+            <button class="button is-warning" name="unsubscribe" disabled>
+              Unsubscribe
+            </button>
+          </form>
         </div>
-        <div class="columns is-multiline" id="subscriptions">
-            <template id="subscription">
-                <div class="column is-two-fifths">
-                    <div class="card">
-                        <header class="card-header">
-                            <p class="card-header-title"></p>
-                        </header>
-                        <div class="card-content">
-                            <div class="content">
-                                <dl>
-                                    <dt>Topic</dt>
-                                    <dd class="topic"></dd>
-                                    <dt>Subscriber</dt>
-                                    <dd class="subscriber"></dd>
-                                    <dt>payload</dt>
-                                    <dd>
-                                        <pre><code></code></pre>
-                                    </dd>
-                                </dl>
-                            </div>
-                        </div>
-                    </div>
+      </div>
+      <div class="columns is-multiline" id="subscriptions">
+        <template id="subscription">
+          <div class="column is-two-fifths">
+            <div class="card">
+              <header class="card-header">
+                <p class="card-header-title"></p>
+              </header>
+              <div class="card-content">
+                <div class="content">
+                  <dl>
+                    <dt>Topic</dt>
+                    <dd class="topic"></dd>
+                    <dt>Subscriber</dt>
+                    <dd class="subscriber"></dd>
+                    <dt>payload</dt>
+                    <dd>
+                      <pre><code></code></pre>
+                    </dd>
+                  </dl>
                 </div>
-            </template>
-        </div>
+              </div>
+            </div>
+          </div>
+        </template>
+      </div>
     </div>
 
     <!-- Only necessary to use the Authorization header with EventSource (discouraged in a browser) -->
     <script src="https://cdn.jsdelivr.net/npm/event-source-polyfill@1"></script>
     <script src="app.js"></script>
-</body>
-
+  </body>
 </html>
diff --git a/spec/openapi.yaml b/spec/openapi.yaml
index 79b7ae08..92e51409 100644
--- a/spec/openapi.yaml
+++ b/spec/openapi.yaml
@@ -86,7 +86,7 @@ paths:
             "text/plain": {}
         "401":
           $ref: "#/components/responses/401"
-        '400':
+        "400":
           description: Invalid request
   "/.well-known/mercure/subscriptions":
     get:
@@ -174,7 +174,7 @@ components:
         subscriptions:
           type: array
           items:
-            $ref: '#/components/schemas/Subscription'
+            $ref: "#/components/schemas/Subscription"
     Subscription:
       type: object
       required: ["id", "type", "topic", "subscriber", "active"]