Skip to main content

The Mercure Protocol
draft-dunglas-mercure-08

Document Type Active Internet-Draft (individual)
Author Kévin Dunglas
Last updated 2026-08-11
RFC stream (None)
Intended RFC status (None)
Formats
Additional resources GitHub Repository
GitHub Username: dunglas
Related Implementations
Additional Web Page
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-dunglas-mercure-08
Network Working Group                                    K. Dunglas, Ed.
Internet-Draft                                         Les-Tilleuls.coop
Intended status: Standards Track                          11 August 2026
Expires: 12 February 2027

                          The Mercure Protocol
                        draft-dunglas-mercure-08

Abstract

   Mercure provides a common publish-subscribe mechanism for public and
   private web resources.  It pushes any web content to web browsers and
   other clients over a single long-lived HTTP connection, avoiding
   polling and its associated latency and power cost.  Mercure is
   especially useful for delivering real-time updates of resources
   served through sites and web APIs to web and mobile applications, and
   can also be used as a general-purpose publish-subscribe system.

   Subscription requests are relayed through hubs, which validate them.
   When new or updated content becomes available, hubs check whether
   subscribers are authorized to receive it and then distribute it.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on 12 February 2027.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.

Dunglas                 Expires 12 February 2027                [Page 1]
Internet-Draft                   Mercure                     August 2026

   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.  Code Components
   extracted from this document must include Revised BSD License text as
   described in Section 4.e of the Trust Legal Provisions and are
   provided without warranty as described in the Revised BSD License.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   3
   2.  Terminology . . . . . . . . . . . . . . . . . . . . . . . . .   4
   3.  Subscription  . . . . . . . . . . . . . . . . . . . . . . . .   5
   4.  Matcher Types . . . . . . . . . . . . . . . . . . . . . . . .   9
     4.1.  Exact Matching  . . . . . . . . . . . . . . . . . . . . .  10
     4.2.  URL Pattern . . . . . . . . . . . . . . . . . . . . . . .  10
     4.3.  Summary of Matcher Types  . . . . . . . . . . . . . . . .  11
   5.  Publication . . . . . . . . . . . . . . . . . . . . . . . . .  11
   6.  Authorization . . . . . . . . . . . . . . . . . . . . . . . .  14
     6.1.  Presenting the Access Token . . . . . . . . . . . . . . .  16
       6.1.1.  Authorization HTTP Header . . . . . . . . . . . . . .  16
       6.1.2.  Cookie  . . . . . . . . . . . . . . . . . . . . . . .  17
     6.2.  Error Responses . . . . . . . . . . . . . . . . . . . . .  18
     6.3.  Token Validation  . . . . . . . . . . . . . . . . . . . .  19
     6.4.  Authorization Details . . . . . . . . . . . . . . . . . .  20
     6.5.  Publishers  . . . . . . . . . . . . . . . . . . . . . . .  21
     6.6.  Subscribers . . . . . . . . . . . . . . . . . . . . . . .  22
     6.7.  Topic Matcher List  . . . . . . . . . . . . . . . . . . .  23
     6.8.  Payloads  . . . . . . . . . . . . . . . . . . . . . . . .  24
   7.  Reconnection, State Reconciliation, and Event Sourcing  . . .  25
   8.  Active Subscriptions  . . . . . . . . . . . . . . . . . . . .  28
     8.1.  Subscription Events . . . . . . . . . . . . . . . . . . .  28
     8.2.  Subscription API  . . . . . . . . . . . . . . . . . . . .  30
   9.  Discovery . . . . . . . . . . . . . . . . . . . . . . . . . .  35
     9.1.  Hub Discovery . . . . . . . . . . . . . . . . . . . . . .  35
     9.2.  Protected Resource Metadata . . . . . . . . . . . . . . .  37
     9.3.  Topic Discovery . . . . . . . . . . . . . . . . . . . . .  38
       9.3.1.  Content Negotiation . . . . . . . . . . . . . . . . .  39
   10. Encryption  . . . . . . . . . . . . . . . . . . . . . . . . .  40
   11. Using HTTP  . . . . . . . . . . . . . . . . . . . . . . . . .  41
   12. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  41
     12.1.  Well-Known URIs Registry . . . . . . . . . . . . . . . .  41
     12.2.  Link Relation Types Registry . . . . . . . . . . . . . .  41
     12.3.  OAuth Protected Resource Metadata Registry . . . . . . .  42
     12.4.  HTTP Field Name Registry . . . . . . . . . . . . . . . .  42
     12.5.  Mercure Topic Matcher Types Registry . . . . . . . . . .  43
     12.6.  Mercure Actions Registry . . . . . . . . . . . . . . . .  44
   13. Security Considerations . . . . . . . . . . . . . . . . . . .  44
     13.1.  Token Validation . . . . . . . . . . . . . . . . . . . .  45
     13.2.  Server-Sent Events Field Injection . . . . . . . . . . .  46

Dunglas                 Expires 12 February 2027                [Page 2]
Internet-Draft                   Mercure                     August 2026

     13.3.  Reserved Hub Namespace . . . . . . . . . . . . . . . . .  46
     13.4.  Authorization on Event Replay  . . . . . . . . . . . . .  47
     13.5.  Subscriber Identifier Assignment . . . . . . . . . . . .  47
     13.6.  Private Update Audience  . . . . . . . . . . . . . . . .  47
     13.7.  URL-Pattern Denial of Service  . . . . . . . . . . . . .  48
     13.8.  Payload Privacy  . . . . . . . . . . . . . . . . . . . .  48
     13.9.  Topic Normalization  . . . . . . . . . . . . . . . . . .  48
     13.10. Resource Limits  . . . . . . . . . . . . . . . . . . . .  49
     13.11. Hub Trust  . . . . . . . . . . . . . . . . . . . . . . .  49
     13.12. Protected Resource Metadata and Authorization Server
             Selection . . . . . . . . . . . . . . . . . . . . . . .  49
     13.13. Bearer Tokens and Sender Constraint  . . . . . . . . . .  49
     13.14. Publish Request Replay . . . . . . . . . . . . . . . . .  50
     13.15. JWE Algorithms and Replay  . . . . . . . . . . . . . . .  50
   14. Privacy Considerations  . . . . . . . . . . . . . . . . . . .  50
   15. Implementation Status . . . . . . . . . . . . . . . . . . . .  50
     15.1.  Mercure.rocks Hub  . . . . . . . . . . . . . . . . . . .  51
     15.2.  Freddie  . . . . . . . . . . . . . . . . . . . . . . . .  52
     15.3.  Ilshidur/node-mercure  . . . . . . . . . . . . . . . . .  53
     15.4.  Symfony  . . . . . . . . . . . . . . . . . . . . . . . .  53
     15.5.  API Platform . . . . . . . . . . . . . . . . . . . . . .  54
     15.6.  Laravel Mercure Broadcaster  . . . . . . . . . . . . . .  55
     15.7.  dart_mercure . . . . . . . . . . . . . . . . . . . . . .  56
     15.8.  pymercure  . . . . . . . . . . . . . . . . . . . . . . .  57
     15.9.  Amphp Mercure Publisher  . . . . . . . . . . . . . . . .  58
     15.10. Java Library for Mercure . . . . . . . . . . . . . . . .  59
     15.11. Yii 2 Mercure behavior . . . . . . . . . . . . . . . . .  59
     15.12. GitHub Action for Mercure  . . . . . . . . . . . . . . .  60
     15.13. Other Implementations  . . . . . . . . . . . . . . . . .  61
   16. Acknowledgements  . . . . . . . . . . . . . . . . . . . . . .  61
   17. References  . . . . . . . . . . . . . . . . . . . . . . . . .  61
     17.1.  Normative References . . . . . . . . . . . . . . . . . .  61
     17.2.  Informative References . . . . . . . . . . . . . . . . .  64
   Appendix A.  Changes from Pre-Standardization Deployments . . . .  67
   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  68

1.  Introduction

   Mercure is a protocol for pushing updates of web resources to clients
   over HTTP.  It builds on Server-Sent Events [HTML] for delivery and
   on JSON Web Signatures [RFC7515] for authorization, so that it can be
   implemented on top of existing HTTP infrastructure and consumed
   natively by web browsers.

Dunglas                 Expires 12 February 2027                [Page 3]
Internet-Draft                   Mercure                     August 2026

   Publishers send updates to a hub.  Subscribers open a long-lived HTTP
   connection to the hub and declare, using topic matchers, which topics
   they want to receive.  The hub checks authorization and dispatches
   matching updates, including updates marked as private that only
   authorized subscribers may receive.

   This document specifies the subscription and publication interfaces,
   the topic matcher types, the OAuth 2.0-based authorization model,
   reconnection and state reconciliation, active-subscription tracking,
   discovery, and update encryption.

   Some normative references of this document are dated snapshots
   (Review Drafts) of WHATWG Living Standards.  Conformance is evaluated
   against the cited snapshots; later changes to those standards do not
   automatically apply to this protocol.

2.  Terminology

   The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD,
   SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this
   document are to be interpreted as described in BCP 14 [RFC2119]
   [RFC8174] when, and only when, they appear in all capitals, as shown
   here.

   *  Topic: The unit to which one can subscribe for changes.  The topic
      is identified by a string that can be an IRI [RFC3987].  Topic
      strings MUST be valid UTF-8 [RFC3629] and MUST NOT contain C0
      (U+0000–U+001F) or C1 (U+0080–U+009F) control characters, U+007F
      (DEL), or Unicode format characters (general category Cf
      [UNICODE], such as the bidirectional and zero-width controls),
      which are invisible and enable identifier spoofing.  Character
      classifications are those of the Unicode version cited in
      [UNICODE]; hubs MAY also reject characters that later Unicode
      versions assign to general category Cf.
   *  Canonical topic: The first topic value of an update; the primary
      identifier of the updated resource.
   *  Alternate topic: Any topic value of an update other than the
      canonical topic.  An update is dispatched to subscribers matching
      either the canonical topic or an alternate topic (see Section 5).
   *  Update: The message containing the updated version of the topic.
      An update can be marked as private; in that case, it MUST be
      dispatched only to subscribers allowed to receive it.
   *  Topic matcher: An expression matched against one or more topics,
      depending on the matcher type.
   *  Topic matcher type: The kind of a matching expression, which
      determines how the expression is interpreted.  This document
      defines two matcher types, exact and urlpattern; others can be
      registered (see Section 4).

Dunglas                 Expires 12 February 2027                [Page 4]
Internet-Draft                   Mercure                     August 2026

   *  Publisher: An owner of a topic.  Notifies the hub when the topic
      feed has been updated.  As in almost all pub-sub systems, the
      publisher is unaware of the subscribers, if any.  Other pub-sub
      systems might call the publisher the "source".  Typically a site
      or a web API, but it can also be a web browser.
   *  Subscriber: A client application that subscribes to real-time
      updates of topics using topic matchers.  Typically a web or a
      mobile application, but it can also be a server.
   *  Subscription: A topic matcher used by a subscriber to receive
      updates.  A single subscriber can have several subscriptions by
      providing several topic matchers.
   *  Hub: A server that handles subscription requests and distributes
      content to subscribers when the corresponding topics have been
      updated.  A hub MAY implement its own policies on who can use it.
      The hub is an OAuth 2.0 protected resource [RFC6749].
   *  Access token: The credential a client presents to the hub to prove
      authorization, carried as a JWT [RFC7519] following the JWT access
      token profile [RFC9068].
   *  Resource identifier: The OAuth 2.0 resource identifier of the hub,
      used as the audience (aud) of access tokens and advertised through
      protected resource metadata [RFC9728].
   *  Authorization server: An OAuth 2.0 authorization server [RFC6749]
      that issues access tokens for the hub.  Its use is OPTIONAL;
      access tokens MAY be self-issued.
   *  Authorization details: The authorization_details claim [RFC9396]
      carried in an access token, expressing which actions a client may
      perform on which topics.

3.  Subscription

   The subscriber subscribes to a URL exposed by a hub to receive
   updates from one or more topics.  To subscribe, the client opens an
   HTTPS connection to the hub's subscription URL (advertised by the
   publisher; see Section 9) following the Server-Sent Events
   specification [HTML].  Hubs MUST support the GET HTTP method, which
   is the method used by EventSource clients [HTML].  Hubs MAY also
   accept any other safe [RFC9110] method whose semantics allow the
   topic matcher parameters to be carried in the request body instead of
   the query component — notably the QUERY method [RFC10008] — so that
   subscribers can send topic matcher lists too large for the URI length
   limits of intermediaries.  When the parameters are carried in the
   request body, they MUST be encoded as application/x-www-form-
   urlencoded [URL], and the reserved-namespace rule and value
   constraints below apply identically to the body-decoded names and
   values.

Dunglas                 Expires 12 February 2027                [Page 5]
Internet-Draft                   Mercure                     August 2026

   A request carrying such a body MAY also carry parameters in the query
   component.  The hub MUST then take the union of the two: every name/
   value pair from the query component and every pair from the body, as
   if they had all appeared in one component.  This applies to the topic
   matcher parameters and to last_event_id (see Section 7) alike, so a
   subscriber whose matcher list only fits in the body can still send
   its cursor there.  A subscription is created for every matcher
   parameter in the union, so the two components need no precedence rule
   between them. last_event_id is single-valued: if it appears in both,
   the hub MUST use the value from the query component, and the Last-
   Event-ID HTTP field still takes precedence over either (see
   Section 7).

   The connection SHOULD use HTTP version 2 or higher to leverage
   multiplexing and other performance-related features.

   The subscriber specifies the topics to receive updates from using
   topic matcher query parameters.  The parameter name encodes the
   matcher type: the bare match parameter selects the default exact
   matcher type, and a match_<matcher-type> parameter selects the named
   matcher type — for example, match_urlpattern selects the urlpattern
   matcher type, and match_exact is the explicit spelling of the
   default.  The <matcher-type> suffix MUST be the matcher type name in
   its canonical form as defined in Section 4.  A request MAY contain
   several such parameters, in any combination.  See Section 4.  These
   parameters select which topics the subscriber receives; they do not
   by themselves grant access to private updates, which is governed by
   the access token (see Section 6).

   This mirrors the topic matcher list of authorization details (see
   Section 6.7), where the match_type member is optional and defaults to
   exact: omitting it there is equivalent to using the bare match
   parameter here.

   The query component of the subscription URL MUST be parsed into name/
   value pairs using the application/x-www-form-urlencoded parsing
   algorithm of [URL] (the algorithm implemented by URLSearchParams and
   used by EventSource).  The reserved-namespace rule and the value
   constraints below apply to the percent-decoded parameter names and
   values.  A parameter name given without a value is equivalent to that
   name with an empty value.  Clients MUST percent-encode any character
   in a matcher name or value that application/x-www-form-urlencoded
   serialization would encode — notably &, =, +, ;, and % — as
   URLSearchParams does; this keeps parsing unambiguous across
   implementations (some form-urlencoded parsers treat a raw ; as a
   delimiter or reject a stray %).

Dunglas                 Expires 12 February 2027                [Page 6]
Internet-Draft                   Mercure                     August 2026

   The names of topic matcher query parameters are case-sensitive.  A
   request using a parameter name in the reserved match namespace (a
   name equal to match, or beginning with match under an ASCII case-
   insensitive comparison) that does not correspond to a matcher type
   supported by the hub (see Section 4) MUST be rejected with a 400 "Bad
   Request" HTTP status code.  This deliberately reserves the whole
   match prefix: unrelated query parameters whose names begin with match
   cannot be used on the subscription URL, and a misspelled matcher type
   — or a registered matcher type the hub does not implement — fails
   loudly instead of being silently ignored.

   The value of each topic matcher query parameter MUST be valid UTF-8
   [RFC3629] and MUST NOT contain C0 (U+0000–U+001F) or C1
   (U+0080–U+009F) control characters, U+007F, or Unicode format
   characters (general category Cf [UNICODE]).  A parameter value that
   is not valid for its matcher type (for example, a match_urlpattern
   value that is not a well-formed URL Pattern) is equally invalid.
   Requests violating any of these constraints MUST be rejected with a
   400 "Bad Request" HTTP status code.

   The subscriber receives updates for all topics matching at least one
   topic matcher according to the matcher type rules.

   To mitigate resource exhaustion, hubs SHOULD apply implementation-
   defined maximums to the number of topic matcher query parameters in a
   single request and to the length of each matcher's pattern.  Requests
   exceeding any such limit MUST be rejected with a 400 "Bad Request"
   HTTP status code.  A subscription is created for every topic matcher
   query parameter present in the request.  Hubs MAY deduplicate
   subscriptions that have identical matcher type and pattern.  See
   Section 8.1.

   Because subscription connections are long-lived, hubs SHOULD also
   apply implementation-defined limits to the number of concurrent
   connections held by a single client (for example, per source address
   or per token subject) and in total, and MAY reject further connection
   attempts with a 429 "Too Many Requests" HTTP status code [RFC6585].

   The EventSource JavaScript interface [HTML] MAY be used to establish
   the connection.  Any other appropriate mechanism, including but not
   limited to readable streams [streams] and XMLHttpRequest [xhr] (used
   by popular polyfills), MAY also be used.

   Web browsers enforce the CORS protocol [FETCH] on cross-origin
   EventSource connections.  Hubs serving browser-based subscribers on
   other origins MUST send the appropriate CORS response headers.  When
   the connection carries credentials (such as the cookie defined in
   Section 6.1.2), the Access-Control-Allow-Origin response header MUST

Dunglas                 Expires 12 February 2027                [Page 7]
Internet-Draft                   Mercure                     August 2026

   NOT be the * wildcard and MUST NOT be reflected from arbitrary
   request origins: it MUST be restricted to an explicit allowlist of
   trusted origins, and the hub MUST also send Access-Control-Allow-
   Credentials: true.  Reflecting arbitrary origins on a credentialed
   endpoint would allow any site visited by the subscriber to read
   updates using the subscriber's cookie.

   The hub sends updates to the subscriber for topics matching the
   provided topic matchers.

   If an update is marked as private, the hub MUST NOT dispatch it to
   subscribers not authorized to receive it.  See Section 6.

   The hub MUST send these updates as text/event-stream-compliant events
   [HTML].

   Event streams are long-lived responses and interact poorly with
   intermediaries that buffer responses or terminate idle connections.
   When no update has been dispatched for an implementation-defined
   period, hubs SHOULD send an SSE comment line (a line starting with :
   [HTML]) as a keep-alive, and deployments SHOULD configure
   intermediaries not to buffer event streams.

   The data property MUST contain the topic's new version.  It MAY be
   the full resource or a partial update in formats such as JSON Patch
   [RFC6902] or JSON Merge Patch [RFC7396].

   All other properties defined in the Server-Sent Events specification
   MAY be used and MUST be supported by hubs.

   The resource MAY be represented in a format with hypermedia
   capabilities such as JSON-LD [W3C.REC-json-ld11-20200716], Atom
   [RFC4287], XML [W3C.REC-xml-20081126] or HTML [HTML].

   Web Linking [RFC8288] MAY be used to indicate the IRI of the resource
   sent in the event.  When using Atom, XML, or HTML as the
   serialization format, the document SHOULD contain a link element with
   a self relation that holds the IRI of the resource.  When using JSON-
   LD, the document SHOULD contain an @id property holding the IRI of
   the resource.

   Example:

Dunglas                 Expires 12 February 2027                [Page 8]
Internet-Draft                   Mercure                     August 2026

   // The subscriber subscribes to updates
   // for the https://example.com/foo topic, the bar topic,
   // and to any topic matching the https://example.com/bar/:id URL Pattern
   const url = new URL('https://example.com/.well-known/mercure');
   url.searchParams.append('match', 'https://example.com/foo');
   url.searchParams.append('match', 'bar');
   url.searchParams.append('match_urlpattern', 'https://example.com/bar/:id');

   const eventSource = new EventSource(url);

   // The callback will be called every time an update is published
   eventSource.onmessage = function ({data}) {
       console.log(data);
   };

   The hub MAY apply extra authorization rules not defined in this
   specification.  See Section 6.

4.  Matcher Types

   A topic matcher is an expression matched against topics; its matcher
   type determines how the expression is interpreted.  This document
   defines two matcher types, exact and urlpattern.  Hubs MUST support
   both.

   Additional matcher types can be defined by other specifications and
   registered in the "Mercure Topic Matcher Types" registry (see
   Section 12).  Hubs MAY support registered additional matcher types
   and SHOULD advertise the complete set of matcher types they support
   with the mercure_matcher_types_supported metadata member (see
   Section 9.2).  Requests and tokens using a matcher type the hub does
   not support are rejected as defined in Section 3 and Section 6.7:
   matcher evaluation determines both routing and authorization, so a
   matcher the hub cannot interpret must fail loudly rather than be
   skipped.

   The matcher value * is reserved as a wildcard that matches every
   topic.  It is recognized before the matcher type is resolved, so it
   has this meaning regardless of matcher type and regardless of whether
   match_type is supplied or defaulted (see Section 6.7).  As a
   consequence, a topic whose value is exactly * is not addressable: no
   matcher can select that single topic without also selecting every
   other.  Publishing a topic whose value is exactly * is therefore
   rejected (see Section 5).  This mirrors the reserved wildcard
   characters of other publish-subscribe systems.

Dunglas                 Expires 12 February 2027                [Page 9]
Internet-Draft                   Mercure                     August 2026

4.1.  Exact Matching

   The hub MUST support exact matching.  With this matcher type, the hub
   MUST perform an exact, case-sensitive, byte-for-byte comparison
   between the topic and the matcher.  The hub MUST NOT resolve relative
   values against the hub's URL or any other base, and MUST NOT perform
   Unicode or IRI normalization.

   Note: Because comparison is performed on raw bytes, publishers and
   subscribers SHOULD normalize topic strings to a canonical form before
   publication or subscription.  Recommended canonicalizations are
   Unicode NFC [UNICODE] and, for IRIs, IDNA-canonical hosts [RFC5891]
   and percent-encoding normalization [RFC3986].  Otherwise, visually
   identical topics will be treated as distinct, and homograph attacks
   (see Section 13) become possible.

   The matcher type name is exact.  It is the default matcher type: the
   corresponding subscribe query parameter is the bare match (or,
   explicitly, match_exact), and it is the default match_type value in
   authorization details (see Section 6.7).

4.2.  URL Pattern

   The hub MUST support using URL patterns [urlpattern] as matchers.

   URL patterns MAY be absolute (e.g., https://example.com/books/:id) or
   relative (e.g., /.well-known/mercure/subscriptions/
   exact/:topic/:subscriber).  When evaluating a relative pattern or a
   relative topic, the hub MUST use the hub's URL as the base URL.  This
   allows subscribers to match relative topics published by the hub
   itself, such as subscription events (see Section 8.1).

   URL patterns are evaluated per the URL Pattern Living Standard
   [urlpattern]; hubs MUST NOT enable the ignoreCase option.  Host
   components remain case-insensitive as defined by URL canonicalization
   [RFC3986]; all other components are case-sensitive.

   A topic that cannot be parsed as a URL reference against the hub's
   URL cannot be matched by a URL pattern: hubs MUST treat its
   evaluation against any URL pattern as not matching.

Dunglas                 Expires 12 February 2027               [Page 10]
Internet-Draft                   Mercure                     August 2026

   The URL Pattern Living Standard compiles patterns to regular
   expressions internally; crafted patterns can therefore trigger
   catastrophic backtracking.  To mitigate denial-of-service attacks by
   clients submitting pathological patterns, hubs MUST either use a
   regular expression engine that guarantees linear-time matching (such
   as RE2 [re2]) or enforce an implementation-defined evaluation cost or
   time limit.  When such a limit is reached, the pattern MUST be
   treated as not matching and the evaluation MUST be aborted.

   URL patterns whose protocol component is a wildcard or capture group
   can match data:, javascript:, file:, and other potentially dangerous
   URI schemes.  Topic strings are opaque identifiers within this
   protocol; subscribers MUST NOT dereference them as URLs without
   validating the scheme against an allowlist appropriate for the
   subscriber's environment.

   The matcher type name is urlpattern.  The corresponding subscribe
   query parameter is match_urlpattern, and the corresponding match_type
   value in authorization details (see Section 6.7) is urlpattern.

4.3.  Summary of Matcher Types

      +==============+==================+============+=============+
      | Matcher Type | Subscribe Query  | match_type | Requirement |
      |              | Parameter        |            |             |
      +==============+==================+============+=============+
      | exact        | match (or        | exact      | MUST        |
      |              | match_exact)     |            |             |
      +--------------+------------------+------------+-------------+
      | urlpattern   | match_urlpattern | urlpattern | MUST        |
      +--------------+------------------+------------+-------------+

                                 Table 1

   This table lists the matcher types defined by this document; the
   "Mercure Topic Matcher Types" registry (see Section 12) records
   additional registered types.

5.  Publication

   The publisher sends updates by issuing POST HTTPS requests to the hub
   URL.  When it receives an update, the hub dispatches it to
   subscribers using the established Server-Sent Events connections.

   The hub MAY also dispatch the update using other protocols such as
   WebSub [W3C.REC-websub-20180123] or ActivityPub
   [W3C.REC-activitypub-20180123].

Dunglas                 Expires 12 February 2027               [Page 11]
Internet-Draft                   Mercure                     August 2026

   An application MAY deliver events directly to subscribers without an
   external hub.  In that case, the publish endpoint described in this
   section is not required.

   The request MUST be encoded using the application/x-www-form-
   urlencoded format [URL].  Field names and values MUST be UTF-8
   [RFC3629].  The request MUST contain at least one topic field; all
   other fields defined below are optional:

   *  topic (required): The identifier of an updated topic.  It is
      RECOMMENDED to use an IRI as identifier.  This field MAY appear
      more than once: the first occurrence is the canonical topic (the
      primary identifier of the updated resource) and any remaining
      occurrences are alternate topics (see Section 2).  The hub
      dispatches the update to subscribers matching any one of these
      topics, and private-read authorization is evaluated against the
      set as a whole (see Section 6.6) — the audience of a private
      update is therefore the union of the audiences of all its topics
      (see Section 13.6).  Each topic value MUST conform to the
      constraints defined in Section 2.  Every topic value (canonical or
      alternate) MUST NOT address the reserved hub namespace.  To test
      this, each topic MUST be resolved against the hub's URL (see
      Section 9) using the URL parser of [URL] — the same algorithm and
      canonicalization used for URL Pattern matching (see Section 4.2).
      A topic addresses the reserved namespace when the resolved path
      component equals the path of the hub's URL or begins with that
      path followed by / — for the default hub URL (see Section 9),
      /.well-known/mercure and /.well-known/mercure/... — regardless of
      scheme or authority.  Before this comparison, the path MUST have
      its dot-segments removed and its percent-encoded octets that
      correspond to unreserved characters decoded [RFC3986]; otherwise,
      variants such as /.well-known/%6Dercure/... or /.well-known/
      mercure/../mercure/... would bypass the check.  A topic that
      cannot be parsed as a URL reference against the hub's URL does not
      address the reserved namespace (it cannot name a hub path) and is
      not rejected by this rule.  This namespace is reserved for
      resources generated by the hub itself, including subscription
      events (see Section 8.1).  Every topic value MUST NOT be exactly
      *, which is reserved for the wildcard matcher (see Section 4) and
      would otherwise not be addressable.  Because both rules reject the
      request regardless of the presented access token, hubs MUST reject
      publish requests violating them, for any topic value, with a 400
      "Bad Request" HTTP status code.  Checking the resolved path
      component (rather than a leading-substring match on the raw value)
      prevents a publisher from forging subscription events with an
      absolute topic such as https://hub.example.com/.well-
      known/mercure/subscriptions/....

Dunglas                 Expires 12 February 2027               [Page 12]
Internet-Draft                   Mercure                     August 2026

   *  data (optional): the content of the new version of this topic.
      The value MUST be valid UTF-8 [RFC3629].  When dispatching the
      update, the hub MUST serialize the value as one SSE data: field
      per line, splitting on CR, LF, or CRLF, per the Server-Sent Events
      serialization rules [HTML].  A receiver reassembles the fields
      joined by LF, so a value containing CR or CRLF is received with
      those sequences normalized to LF; publishers that require byte-
      exact round-tripping (for example, of encrypted payloads) SHOULD
      encode the value (for example, with base64) before publication.
      The value is opaque to the hub: publishers and subscribers MAY
      agree on any format.  Only the hub-generated documents described
      in Section 8.1 have a format defined by this specification.
   *  private (optional): if this field is present, the update MUST NOT
      be dispatched to subscribers not authorized to receive it.  See
      Section 6.  The presence of the field name marks the update as
      private regardless of its value, whether or not a value is
      supplied; hubs MUST NOT interpret the field's value to determine
      privacy.  It is RECOMMENDED to set the value to on for
      interoperability, but it MAY contain any value, including an empty
      string.
   *  id (optional): the topic's revision identifier; used as the SSE id
      property.  The provided ID MUST NOT start with the # character,
      MUST NOT be the reserved value earliest (see Section 7), and MUST
      NOT contain control characters (C0 (U+0000–U+001F), U+007F, or C1
      (U+0080–U+009F)) or Unicode format characters (general category Cf
      [UNICODE]) — the same constraint as topics (see Section 2), since
      the ID also travels in the Last-Event-ID HTTP field.  The provided
      ID MAY be a valid IRI.  If omitted, the hub MUST generate either a
      valid IRI [RFC3987] or a relative reference consisting of a
      fragment (starting with #).  A UUID [RFC9562] or a DID [DID] MAY
      be used as the IRI; a fragment is convenient to return an offset
      or a sequence that is unique for this hub.  A client-supplied ID
      SHOULD be unique within the scope of the hub: the hub treats IDs
      as cursors into a single, hub-wide event sequence rather than one
      scoped per topic (see Section 7), so a reused ID leaves ambiguous
      which update a subsequent Last-Event-ID refers to.  The hub MAY
      ignore the client-supplied ID and generate its own.  The hub MUST
      reject client-supplied IDs violating the character constraints
      above with a 400 HTTP status code.
   *  type (optional): the SSE event property (a specific event type).
      The value MUST NOT contain control characters (C0 (U+0000–U+001F),
      U+007F, or C1 (U+0080–U+009F)) or Unicode format characters
      (general category Cf [UNICODE]); hubs MUST reject violating values
      with a 400 HTTP status code.  The value mercure is reserved for
      updates the hub generates itself (see Section 8.1); hubs MUST
      reject a publish request whose type is mercure with a 400 HTTP
      status code, so that a publisher cannot forge such events.

Dunglas                 Expires 12 February 2027               [Page 13]
Internet-Draft                   Mercure                     August 2026

   *  retry (optional): the SSE retry property (the reconnection time).
      The value MUST consist solely of ASCII digits (U+0030–U+0039);
      hubs MUST reject violating values with a 400 HTTP status code.

   To allow future extensions, hubs MUST ignore fields they do not
   recognize.

   On success, the hub MUST return a 200 (OK) HTTP status code, and the
   response body MUST be the id generated by the hub for the update,
   served with the text/plain media type and the UTF-8 charset (Content-
   Type: text/plain; charset=utf-8).  Other 2xx status codes are not
   appropriate: some cannot carry content at all (204, 205) [RFC9110],
   and 201 (Created) would misrepresent an ephemeral message as a
   resource retrievable at a dereferenceable URL — the id is an event
   cursor (see Section 7), not a Location.  The publisher MUST be
   authorized to publish updates; see Section 6.

   Hubs SHOULD apply implementation-defined maximums to the size of the
   request body, to the length of individual fields, and to the number
   of topic fields per request.  Requests exceeding any such limit MUST
   be rejected with a 413 "Content Too Large" HTTP status code.

   Example:

   POST /.well-known/mercure HTTP/1.1
   Host: example.com
   Content-Type: application/x-www-form-urlencoded
   Authorization: Bearer [snip]

   topic=https://example.com/foo&data=the%20content

   HTTP/1.1 200 OK
   Content-Type: text/plain; charset=utf-8

   urn:uuid:e1ee88e2-532a-4d6f-ba70-f0f8bd584022

6.  Authorization

   The hub is an OAuth 2.0 protected resource [RFC6749].  To prove that
   they are authorized, publishers MUST present an access token to the
   hub (except in the closed-network deployments described in the note
   below), and subscribers MUST present an access token to receive
   updates marked as private.  Hubs MAY accept unauthenticated
   subscribers; such subscribers receive only updates that are not
   marked as private.  Hubs MAY instead require all subscribers to
   present an access token, according to their own policy.  The access
   token MUST be a JWT [RFC7519] structured as a JWT access token
   [RFC9068] — in particular using the at+jwt media type — carried as a

Dunglas                 Expires 12 February 2027               [Page 14]
Internet-Draft                   Mercure                     August 2026

   JWS [RFC7515] in compact serialization, and MUST be validated as
   described in Section 6.3.  Every token, including a self-issued one,
   MUST carry the claims [RFC9068] requires issuers to populate (iss,
   exp, aud, sub, client_id, iat, and jti), so that any [RFC9068]
   validator can process it without Mercure-specific exceptions.  The
   token SHOULD be short-lived, especially when the subscriber is a web
   browser.

   Access tokens MAY be issued by an OAuth 2.0 authorization server or
   self-issued by the publisher (for example, signed with a key shared
   out of band with the hub).  The hub need not operate or trust an
   external authorization server.  A self-issuing publisher acts as a
   local token issuer: it MUST use a stable issuer identifier (for
   example, its own URL) as the iss value, bound out of band to its
   signing key(s) in the hub's configuration (see Section 6.3).  When an
   authorization server is used, the hub MAY advertise it through
   protected resource metadata (see Section 9).  Different keys SHOULD
   be used to sign subscribers' and publishers' tokens so that
   compromise of one role does not entail compromise of the other.

   For example, a self-issued subscriber token is a JWS with the
   protected header {"alg": "ES256", "typ": "at+jwt"} and the following
   claims (see Section 6.3 for how a hub validates them):

   {
     "iss": "https://example.com",
     "aud": "https://hub.example.com/.well-known/mercure",
     "sub": "urn:uuid:bb3de268-05b0-4c65-b44e-8f9acefc29d6",
     "client_id": "https://example.com",
     "iat": 1767225300,
     "exp": 1767225600,
     "jti": "urn:uuid:e70ff8d7-59d5-4a92-b0b7-505e8f6d09fa",
     "authorization_details": [
       {
         "type": "https://mercure.rocks/authorization-detail",
         "actions": ["subscribe"],
         "topics": [{"match": "https://example.com/books/:id", "match_type": "urlpattern"}]
       }
     ]
   }

   Authorization is expressed with the authorization_details claim
   [RFC9396]; see Section 6.4.  Routing (which topics a subscriber
   listens to, via the query parameters of Section 3) is independent of
   authorization (what a token permits): the query parameters never
   grant access to private updates.

Dunglas                 Expires 12 February 2027               [Page 15]
Internet-Draft                   Mercure                     August 2026

   Note: Hubs MAY also be deployed without requiring authorization for
   publication (for example, on a trusted private network).  Because any
   client able to reach such a hub can publish at will, these
   deployments MUST NOT be reachable from networks containing untrusted
   clients.  The remainder of this section assumes token-based
   authorization is in use for publication.

6.1.  Presenting the Access Token

   Two mechanisms are defined to present the access token to the hub,
   following the OAuth 2.0 Bearer Token Usage specification [RFC6750]
   where applicable:

   *  an Authorization HTTP header with the Bearer scheme [RFC6750],
   *  a cookie (a Mercure extension for web browsers, see below).

   The access_token URI query parameter of [RFC6750] section 2.3 is not
   part of this specification: the OAuth 2.0 Security Best Current
   Practice [RFC9700] forbids clients from passing access tokens in the
   URI query string, and hubs implementing this specification do not
   accept it.

   When either mechanism is used, the connection MUST use an encryption
   layer such as HTTPS.

   Per [RFC6750], clients MUST NOT use more than one mechanism to
   transmit the token in a single request.  [RFC6750] treats a request
   that nevertheless carries more than one mechanism as an
   invalid_request; this specification deviates from that handling in
   one narrow case: when an Authorization HTTP header is present, its
   token MUST be used and any cookie MUST be ignored rather than the
   request rejected.  Cookies are ambient credentials — the user agent
   attaches them regardless of the client's choice of mechanism — so
   their presence alongside an explicit Authorization header is not
   treated as a client protocol violation.  When no Authorization header
   is present, the cookie, if any, MUST be used.

6.1.1.  Authorization HTTP Header

   Clients SHOULD use an Authorization HTTP header.  This header MUST
   contain the string Bearer followed by a space character and by the
   access token, as defined in [RFC6750].  As with every HTTP
   authentication scheme, the scheme name is matched case-insensitively
   [RFC9110].

   In a web browser, the fetch() API [FETCH] can set this header and
   consume the Server-Sent Events stream through the response body's
   readable stream [streams]; this is the RECOMMENDED mechanism when the

Dunglas                 Expires 12 February 2027               [Page 16]
Internet-Draft                   Mercure                     August 2026

   token must be scoped to a single connection or browsing context (for
   example, several tabs each holding a different token), or when the
   hub and the web application do not share the same registrable domain
   — situations a cookie cannot accommodate.

6.1.2.  Cookie

   Per the EventSource specification [HTML], web browsers cannot set
   custom HTTP headers on such connections, and the connections can only
   be established using the GET HTTP method.  However, cookies are
   supported and can be included even in cross-origin requests when the
   CORS credentials mode is enabled through the withCredentials
   attribute of EventSource [HTML].  This cookie mechanism is a Mercure-
   specific extension to [RFC6750]; hubs that support it SHOULD
   advertise it in their protected resource metadata (see Section 9).

   If the publisher or the subscriber is a web browser, it SHOULD,
   whenever possible, send a cookie containing the access token when
   connecting to the hub.  It is RECOMMENDED to name the cookie
   __Secure-mercure_access_token: the __Secure- name prefix
   [I-D.ietf-httpbis-rfc6265bis] makes user agents refuse the cookie
   over insecure transport while — unlike the __Host- prefix — remaining
   compatible with the Domain attribute used below.  A different name
   MAY be used to prevent conflicts when several hubs share the same
   domain.

   The cookie SHOULD be set during discovery (see Section 9) to improve
   overall security.  Consequently, if the cookie is set during
   discovery, the publisher and the hub MUST share the same registrable
   domain (eTLD+1).  The Domain attribute MAY be used to allow the
   publisher and the hub to use different subdomains of that registrable
   domain.  See Section 9.

   The cookie MUST have the Secure and HttpOnly attributes set
   [RFC6265].  The cookie SHOULD also have SameSite=Strict
   [I-D.ietf-httpbis-rfc6265bis]: because the publisher and the hub
   share the same registrable domain (see above), requests between them
   are same-site and SameSite=Strict does not impede them.  SameSite=Lax
   MAY be used when the deployment requires the cookie on qualifying
   top-level navigations arriving from other sites; Lax does not release
   the cookie on cross-site subresource requests such as EventSource
   connections [I-D.ietf-httpbis-rfc6265bis], so it does not enable
   cross-site hub access — a genuinely cross-site deployment would
   require SameSite=None; Secure and remains subject to user-agent
   third-party-cookie policies, and SHOULD use the Authorization header
   via fetch() instead (see Section 6.1.1).  The cookie's Path attribute
   SHOULD be set to the path of the hub's subscription URL.  See
   Section 13.

Dunglas                 Expires 12 February 2027               [Page 17]
Internet-Draft                   Mercure                     August 2026

6.2.  Error Responses

   The hub reports authorization failures using the error responses
   defined in [RFC6750] section 3.  For the token-related failures below
   (missing, invalid, or insufficiently-scoped token), the error code is
   carried as the error attribute (auth-param) of a WWW-Authenticate:
   Bearer challenge, not as a response body field; every such 401 or 403
   carries that header [RFC9110], and the challenge SHOULD include the
   resource_metadata parameter (see Section 9) per [RFC9728], not only
   when no token is presented.  A 400 for a request that is malformed
   independently of the access token (for example, a missing topic field
   or an unknown matcher query parameter) need not carry a Bearer
   challenge:

   *  If no access token is presented and the requested operation
      requires one (see above), the hub MUST return a 401 "Unauthorized"
      status code with a WWW-Authenticate: Bearer challenge and MUST NOT
      include an error code.  The challenge SHOULD include a
      resource_metadata parameter pointing to the hub's protected
      resource metadata (see Section 9) per [RFC9728].
   *  If a token is presented but fails validation as defined in
      Section 6.3, or carries Mercure authorization details that fail to
      parse or validate or that exceed implementation-defined limits
      (see Section 6.4), the hub MUST return a 401 status code with
      error="invalid_token".
   *  If a valid token does not authorize the requested operation, the
      hub MUST return a 403 "Forbidden" status code with
      error="insufficient_scope".
   *  If the request is malformed, the hub MUST return a 400 "Bad
      Request" status code.  When the malformed element is the access
      token or its presentation, the response MUST carry a WWW-
      Authenticate: Bearer challenge with error="invalid_request"
      [RFC6750].  A 400 for a request malformed independently of the
      access token conveys no [RFC6750] error code unless the hub
      chooses to include such a challenge, since these codes are defined
      only as challenge parameters.

   Returning invalid_token for every presented-token failure (rather
   than distinguishing a bad signature from an expired or not-yet-valid
   token) avoids disclosing why validation failed.

   For error responses that are not conveyed through the WWW-
   Authenticate: Bearer challenge (for example, a 400 for a request
   malformed independently of the access token, a 429, or the 400 for a
   publish targeting the reserved namespace), the hub MAY return a
   problem details document [RFC9457] as the response body.  The OAuth
   error codes above remain carried in the challenge and are not
   duplicated in such a body.

Dunglas                 Expires 12 February 2027               [Page 18]
Internet-Draft                   Mercure                     August 2026

6.3.  Token Validation

   Hubs MUST validate access tokens as JWT access tokens [RFC9068] and
   in accordance with the JSON Web Token Best Current Practices
   [RFC8725].  The requirements below profile those documents for hubs
   rather than replace them.  In particular:

   *  Hubs MUST verify that the token header typ is at+jwt, or the
      equivalent application/at+jwt, compared with the application/
      prefix omitted and case-insensitively per [RFC7515] section 4.1.9
      [RFC9068], so that tokens issued for other purposes (for example,
      OpenID Connect ID Tokens) are not accepted.
   *  Hubs MUST be configured with an explicit allowlist of accepted
      signature algorithms and MUST reject any token whose alg is not on
      that allowlist.  Hubs MUST NOT accept alg=none, MUST NOT derive
      the set of acceptable algorithms from the token, and MUST verify
      that alg is compatible with the key used for verification
      (preventing algorithm-confusion attacks).  The allowlist SHOULD
      include at minimum EdDSA, ES256, and RS256, and SHOULD NOT include
      algorithms known to be cryptographically weak at the time of
      deployment.
   *  Hubs MUST select the verification key from a preconfigured or pre-
      trusted set and MUST NOT use key material supplied by the token
      itself (such as the jwk, jku, or x5u header parameters).  When
      more than one trusted key is in use — for example, separate
      publisher and subscriber keys or rotated keys — the kid header
      parameter MAY be used as a hint to choose among the trusted keys,
      as MAY the role of the endpoint; the token can thus influence
      which trusted key is tried, but never introduce a new one.
      Trusted keys are obtained from static configuration or from the
      authorization server's JWK Set [RFC7517] discovered through its
      metadata [RFC8414].  This specification defines no hub-specific
      key distribution endpoint.  Each trusted key MUST be associated in
      the hub's configuration with the issuer identifier it belongs to —
      the authorization server's issuer identifier [RFC8414], or the
      stable identifier chosen by a self-issuing publisher.  When the
      hub trusts key material from more than one issuer, it MUST verify
      the signature using only the key(s) associated with the token's
      iss, never a pooled set spanning issuers, so that a token signed
      by one trusted issuer cannot be accepted under another issuer's
      identity.
   *  Hubs MUST enforce the exp claim [RFC7519], including on the first
      request received bearing a token, and MUST reject a token that has
      no exp claim ([RFC9068] requires it).  Hubs MUST enforce the nbf
      claim if present.
   *  Hubs MUST be configured with their resource identifier and MUST
      verify that it appears in the token aud claim [RFC9068].  It is
      RECOMMENDED that the resource identifier be the canonical URL of

Dunglas                 Expires 12 February 2027               [Page 19]
Internet-Draft                   Mercure                     August 2026

      the hub (for example, https://hub.example.com/.well-known/
      mercure), which gives deployments an obvious default.  Per
      [RFC7519], aud MAY be a single string or an array of strings; the
      resource identifier matches when it equals that string or is a
      member of that array.  This bounds a token to its intended hub and
      mitigates replay across hubs that share signing keys (see
      Section 13.11).  Hubs MUST verify that the iss claim is present
      and exactly matches the issuer identifier of one of the hub's
      trusted issuers [RFC9068] — an authorization server (whether or
      not it is advertised in the hub's protected resource metadata, see
      Section 9) or a self-issuing publisher configured out of band.

   [RFC9068] requires issuers to populate the iss, exp, aud, sub,
   client_id, iat, and jti claims; this requirement applies to self-
   issuing publishers as it does to authorization servers (see
   Section 6), so that every token is a conforming [RFC9068] JWT access
   token.  Hubs MUST enforce iss, exp, and aud as described above, per
   [RFC9068] section 4; the remaining claims are issuer obligations that
   hubs use when they rely on them.

   The sub claim identifies the subscriber and is used to derive
   subscription event identifiers (see Section 8.1).

   Failure of any of these checks MUST be reported as defined in
   Section 6.2.

6.4.  Authorization Details

   Authorization is expressed with the authorization_details claim
   [RFC9396], a JSON array of authorization detail objects.  This
   specification defines the authorization detail type identified by the
   URI https://mercure.rocks/authorization-detail, referred to in this
   document as a Mercure authorization detail.  [RFC9396] does not
   establish a registry of authorization details type identifiers (it
   leaves their registration with authorization servers out of scope);
   as [RFC9396] section 2.1 recommends for an API deployed across
   independently operated servers, the type identifier is a collision-
   resistant URI under the control of this specification.  Authorization
   servers supporting it advertise this URI in their
   authorization_details_types_supported metadata [RFC9396].

   A Mercure authorization detail object:

   *  MUST have a type property whose value is the string
      https://mercure.rocks/authorization-detail, compared byte by byte.
   *  MUST have an actions property: a non-empty JSON array of strings
      (the actions field of [RFC9396]).  This document defines the
      actions publish and subscribe; additional actions can be

Dunglas                 Expires 12 February 2027               [Page 20]
Internet-Draft                   Mercure                     August 2026

      registered in the "Mercure Actions" registry (see Section 12).
      Hubs MUST ignore action values they do not recognize: an
      unrecognized action grants nothing, and its presence does not
      invalidate the token.  This lets issuers include actions defined
      by future specifications without breaking deployed hubs; a detail
      whose actions contains no action recognized by the hub simply
      grants nothing.  A non-string entry in actions remains a
      validation failure.
   *  MUST have a topics property: a non-empty JSON array of topic
      matcher objects (see Section 6.7) identifying the topics the
      actions apply to.
   *  MAY have a payload property (a JSON object), meaningful only for
      the subscribe action (see Section 6.8).

   A token grants an action on a topic when it carries a Mercure
   authorization detail whose actions includes that action and one of
   whose topics matches the topic.  Tokens with no Mercure authorization
   detail grant no publish or subscribe rights.

   Hubs SHOULD apply implementation-defined maximums to the number of
   Mercure authorization details, to the number of entries in each
   topics array, and to the length of individual patterns.  Tokens
   exceeding any such limit MUST be rejected as invalid tokens, with a
   401 status code and error="invalid_token" (see Section 6.2).  If any
   Mercure authorization detail fails to parse or validate (including
   failures specific to a matcher's match_type), the hub MUST reject the
   token the same way and *MUST NOT* act on the basis of the remaining
   entries; partial acceptance is forbidden because it would silently
   alter the effective authorization of the token.  These are defects of
   the presented token, not of the request, so they are reported as
   invalid_token rather than invalid_request [RFC6750].

   Hubs MAY also limit the number of concurrent subscriptions
   established under a single token and MAY reject further subscription
   attempts with a 429 "Too Many Requests" status code once the limit is
   reached.

6.5.  Publishers

   A publisher MUST present a token that grants the publish action on
   every topic of the update, canonical and alternate alike: for each
   topic, the token MUST carry a Mercure authorization detail whose
   actions includes publish and one of whose topics matches that topic.
   If the token is not authorized for every topic of the update, the hub
   MUST NOT dispatch the update — even if some of its topics are allowed
   — and MUST return a 403 status code with error="insufficient_scope"
   (see Section 6.2).

Dunglas                 Expires 12 February 2027               [Page 21]
Internet-Draft                   Mercure                     August 2026

6.6.  Subscribers

   To receive updates marked as private, a subscriber MUST present a
   token that grants the subscribe action on at least one topic of the
   update (canonical or alternate): the token MUST carry a Mercure
   authorization detail whose actions includes subscribe and one of
   whose topics matches at least one of the update's topics.  If the
   token does not grant subscribe on any topic of the update, the hub
   MUST NOT deliver the update to the subscriber.

   Authorization is therefore evaluated against the update's topics as a
   whole, not against any one of them in isolation: the audience of a
   private update is the union of the audiences of each of its topics
   (see Section 13.6).  The subscriber's routing matchers (query
   parameters) only select which topics it listens to; they never widen
   what it may read — the subscriber still needs a subscribe grant
   matching one of the update's topics to receive it.

   When the subscriber presented an access token, the hub MUST close the
   connection no later than the token's exp time, since exp is required
   (see Section 6.3).  Since exp alone cannot revoke an already-
   established long-lived connection, hubs SHOULD also impose a maximum
   connection lifetime independent of exp and close connections that
   exceed it, requiring the subscriber to reconnect and re-authenticate.

   For example, a subscriber may listen to all books via the routing
   matcher match_urlpattern=https://example.com/books/:id while its
   token authorizes reading only specific books:

   {
     "authorization_details": [
       {
         "type": "https://mercure.rocks/authorization-detail",
         "actions": ["subscribe"],
         "topics": [
           {"match": "https://example.com/books/1", "match_type": "exact"},
           {"match": "https://example.com/books/7", "match_type": "exact"}
         ]
       }
     ]
   }

   A private update for https://example.com/books/1 is delivered; a
   private update for https://example.com/books/2 is not, even though
   the routing matcher would select it.

Dunglas                 Expires 12 February 2027               [Page 22]
Internet-Draft                   Mercure                     August 2026

   Alternate topics let a publisher grant this same kind of narrow, per-
   subscriber access without issuing a token scoped to every individual
   resource.  Consider a subscriber authorized only for its own
   namespace, https://example.com/users/42/*, that listens to all books
   via match_urlpattern=https://example.com/books/:id:

   {
     "authorization_details": [
       {
         "type": "https://mercure.rocks/authorization-detail",
         "actions": ["subscribe"],
         "topics": [
           {"match": "https://example.com/users/42/*", "match_type": "urlpattern"}
         ]
       }
     ]
   }

   The publisher publishes a private update about https://example.com/
   books/1, attaching https://example.com/users/42/books/1 as an
   alternate topic:

   POST /.well-known/mercure HTTP/1.1
   Host: example.com
   Content-Type: application/x-www-form-urlencoded
   Authorization: Bearer [snip]

   topic=https://example.com/books/1&topic=https://example.com/users/42/books/1&private=on

   The subscriber's routing matcher selects the update by its canonical
   topic, books/1, which its token does not grant subscribe on.  The
   token does grant subscribe on the alternate topic, so the hub
   delivers the update.  A publisher MUST NOT attach to a private update
   an alternate topic matchable by an audience broader than the audience
   intended to read the update's content; doing so discloses the full
   content of the update to that broader audience (see Section 13.6).

6.7.  Topic Matcher List

   A topic matcher object appears in the topics array of a Mercure
   authorization detail (see Section 6.4).  It MUST be a JSON object
   with a match property containing the topic matcher itself, and MAY
   have an OPTIONAL match_type property containing the matcher type.
   The value of match_type is case-sensitive and MUST be the name of a
   matcher type supported by the hub (see Section 4); this document
   defines exact and urlpattern.  Unlike unrecognized actions (see
   Section 6.4), a match_type the hub does not support MUST cause the
   token to be rejected: the grant cannot be evaluated, and skipping it

Dunglas                 Expires 12 February 2027               [Page 23]
Internet-Draft                   Mercure                     August 2026

   would silently alter the effective authorization of the token.  If no
   match_type key is present, the hub MUST assume the exact matcher
   type.  A match value of * is the reserved wildcard and matches every
   topic regardless of match_type, including when match_type is absent
   (see Section 4).

   Any entry that is not a JSON object, or that fails to parse or
   validate as a topic matcher, MUST cause the token to be rejected with
   a 401 status code and error="invalid_token" as defined in
   Section 6.4.

6.8.  Payloads

   User-defined data can be attached to a subscription and made
   available through the subscription API and in subscription events.
   See Section 8.1.

   A Mercure authorization detail with the subscribe action MAY carry a
   payload JSON object.  The payload of the first subscribe
   authorization detail whose topics matches the subscription's own
   matcher (the match or match_<matcher-type> query parameter value)
   MUST be included under the payload key of the JSON object describing
   the subscription, both in the subscription API and in subscription
   events.  A subscribe detail whose topics contains the * wildcard
   matches every subscription and can serve as a default.

   Matching here treats the subscription's own matcher string as if it
   were a topic: for example, the urlpattern matcher
   https://example.com/bar/:id in a subscribe detail matches the
   subscription created by match_urlpattern=https://example.com/bar/:id,
   and an exact matcher matches a subscription whose matcher string is
   byte-for-byte identical to it.

   Note: Payload selection is order-dependent; the first matching
   authorization detail wins.  Issuers placing broad matchers before
   more specific entries will mask the payloads of the specific entries.
   Specific matchers SHOULD appear before broader ones.

   Privacy: Payloads are forwarded to other authorized subscribers via
   subscription events (see Section 8.1).  Issuers MUST NOT place data
   in payloads that should not be visible to other subscribers
   authorized for the corresponding subscription events.  In particular,
   storing data identifying the subscriber (such as a user identifier or
   IP address) effectively broadcasts that data to all other subscribers
   within the same subscription-events scope.

   Example access token claims carrying payloads:

Dunglas                 Expires 12 February 2027               [Page 24]
Internet-Draft                   Mercure                     August 2026

   {
       "authorization_details": [
           {
               "type": "https://mercure.rocks/authorization-detail",
               "actions": ["subscribe"],
               "topics": [{"match": "https://example.com/foo"}],
               "payload": {"custom1": "data only available for this topic"}
           },
           {
               "type": "https://mercure.rocks/authorization-detail",
               "actions": ["subscribe"],
               "topics": [{"match": "https://example.com/bar/:id", "match_type": "urlpattern"}],
               "payload": {"custom2": "data available for matching subscriptions"}
           },
           {
               "type": "https://mercure.rocks/authorization-detail",
               "actions": ["subscribe"],
               "topics": [{"match": "*"}],
               "payload": {"custom3": "default data for all other subscriptions"}
           }
       ]
   }

   For instance, a payload can carry coarse-grained metadata such as a
   tenant identifier or a display label for the subscription.  Issuers
   MUST consider the privacy note above before including any identifier
   of the subscriber, since payloads are visible to other authorized
   subscribers via subscription events.

7.  Reconnection, State Reconciliation, and Event Sourcing

   The protocol allows reconciliation of state after a reconnection.  It
   can also be used to implement an event store [EventSourcing].

   To allow re-establishment in case of connection loss, events
   dispatched by the hub MUST include an id property.  The value of this
   property SHOULD be an IRI [RFC3987].  A UUID [RFC9562] or a DID [DID]
   MAY be used.

   Per the Server-Sent Events specification, the subscriber tries to
   reconnect automatically in case of connection loss.  During
   reconnection, the subscriber MUST send the last received event ID in
   a Last-Event-ID HTTP request header [HTML].

Dunglas                 Expires 12 February 2027               [Page 25]
Internet-Draft                   Mercure                     August 2026

   To fetch any update dispatched between the initial resource
   generation by the publisher and the connection to the hub, the
   subscriber MUST send the event ID provided during discovery either as
   a Last-Event-ID header or as a last_event_id query parameter.  See
   Section 9.

   EventSource implementations may not allow setting HTTP headers on the
   first connection (before a reconnection), and web browser
   implementations do not allow it.

   To work around this, the hub MUST also accept the last event ID in a
   query parameter named last_event_id.

   If both the Last-Event-ID HTTP header and the last_event_id query
   parameter are present, the HTTP header MUST take precedence.

   If the Last-Event-ID HTTP header or the last_event_id query parameter
   is present, the hub SHOULD send all events published after the one
   bearing this identifier to the subscriber, subject to authorization.

   The authorization rules defined in Section 6.6 apply to replayed
   events identically to live events: the hub MUST re-evaluate each
   candidate replayed event against the current access token before
   dispatching it.  Events whose private flag is set and that the token
   does not authorize the subscriber to read (see Section 6.4) MUST NOT
   be dispatched, regardless of any authorization that may have applied
   at publication time.

   The reserved value earliest requests that the hub send all updates it
   has for the subscribed topics.  The hub MAY ignore this request
   according to its own policy.  Hub-generated identifiers are IRIs or
   fragments (see above) and publishers are forbidden from supplying
   earliest as an update ID (see Section 5), so earliest cannot collide
   with an event identifier.

   If more than one update in the hub's history shares the same ID, in
   violation of the recommendation in Section 5, a Last-Event-ID bearing
   that value MUST resolve to the earliest such update in the hub's
   history.  The hub MUST then send all updates published after that
   one, including any later update carrying the same, reused ID.

   The hub MAY discard some events for operational reasons.  When the
   request contains a Last-Event-ID HTTP header or a last_event_id query
   parameter, the hub MUST set a Mercure-Last-Event-ID field on the HTTP
   response.  This document defines the Mercure-Last-Event-ID response
   field and registers it in Section 12 rather than reusing Last-Event-
   ID, whose registration [HTML] defines request semantics only.

Dunglas                 Expires 12 February 2027               [Page 26]
Internet-Draft                   Mercure                     August 2026

   The value of this response field MUST be the identifier of the event
   preceding the first event sent to the subscriber, or the reserved
   value earliest if there is no preceding event (for example, when the
   hub history is empty, when the subscriber requests the earliest
   event, or when the requested event does not exist or has been
   discarded).

   Subscribers using the hub as an event store can use the returned
   identifier as a recovery anchor; subscribers that only need to detect
   data loss can compare it against the requested value (a different
   value indicates that loss may have occurred).

   Note: Event identifiers are cursors, and the hub exposes them to
   subscribers through the last-event-id attribute of the rel="mercure"
   Link header provided during discovery (see Section 9) and by the
   subscription API (see Section 8.2), and through the Mercure-Last-
   Event-ID response field.  The field value can be the identifier of an
   event immediately preceding one the subscriber is not authorized to
   receive.  A subscriber can therefore infer the existence of such an
   event and, when the hub uses time-ordered identifiers (e.g., UUIDv7
   [RFC9562]), its approximate timing and ordering.  Operators handling
   sensitive private updates SHOULD generate opaque, random event
   identifiers (e.g., UUIDv4 [RFC9562]), or expose an encrypted form of
   an internal ordered identifier, so that the cursor discloses nothing
   beyond what the subscriber already knows.

   The subscriber SHOULD NOT assume that no events will be lost (events
   may be lost, for instance, if the hub stores only a limited number of
   events in its history).  In some cases (for example, when sending
   partial updates in the JSON Patch [RFC6902] format, or when using the
   hub as an event store), lost updates can cause data loss.

   To detect data loss, the subscriber MAY compare the value of the
   Mercure-Last-Event-ID response field with the last event ID it
   requested.  In case of data loss, the subscriber SHOULD re-fetch the
   original topic.

   Note: Native EventSource implementations do not expose HTTP response
   headers.  However, polyfills and Server-Sent Events clients in most
   programming languages do.

   The hub MAY also specify the reconnection time using the retry key,
   as defined by the Server-Sent Events format.

Dunglas                 Expires 12 February 2027               [Page 27]
Internet-Draft                   Mercure                     August 2026

8.  Active Subscriptions

   Mercure provides a mechanism to track active subscriptions.  If the
   hub supports this optional set of features, updates will be published
   when a subscription is created, or terminated, and a web API exposes
   the list of active subscriptions.  Hubs supporting this feature
   SHOULD advertise it with the mercure_subscriptions metadata member
   (see Section 9.2).

   Variables are templated and expanded following [RFC6570].

8.1.  Subscription Events

   If the hub supports the active subscriptions feature, it MUST publish
   an update every time a subscription is created or terminated.

   The topic of these updates MUST be the path of the hub's URL followed
   by an expansion of /subscriptions/{match_type}/{match}/{subscriber} —
   for the default hub URL, /.well-known/mercure/
   subscriptions/{match_type}/{match}/{subscriber} — with the following
   variables:

   *  {match_type}: the topic matcher type used for this subscription.
      The value MUST be the matcher type name in its canonical case as
      defined in Section 4 (e.g., urlpattern, exact).  URL path
      components are case-sensitive.
   *  {match}: the topic matcher used for this subscription
   *  {subscriber}: a unique identifier for the subscriber.  Subscribers
      and publishers MUST NOT forge, supply, or override this value
      through query parameters, headers, request bodies, or any other
      client-controlled channel.  It is RECOMMENDED that the hub
      generate a random identifier (for example, a UUID [RFC9562]) for
      each connection.  The hub MAY instead derive the identifier from
      information it has cryptographically validated, but the derivation
      MUST yield an identifier unique per connection: the pair formed by
      the token's issuer and its sub claim (after validation per
      Section 6.3) is not sufficient on its own, because one access
      token can be used on several concurrent connections (for example,
      two browser tabs) whose subscription event topics would then
      collide (see Section 13.5).  A derivation MUST also incorporate
      the issuer: sub alone is unique only within one issuer, and two
      trusted issuers can assign the same value to different
      subscribers.  Hub-generated random identifiers have a privacy
      advantage: they do not disclose the sub value to the other
      subscribers authorized for subscription events.  Distinct
      subscribers MUST receive distinct identifiers.  A single
      subscriber holding several active subscriptions through one
      connection reuses the same identifier across them.

Dunglas                 Expires 12 February 2027               [Page 28]
Internet-Draft                   Mercure                     August 2026

   Note: Because strings containing reserved characters (e.g., URIs, URL
   Patterns, and URI Templates) can be used for the {match} and
   {subscriber} variables, per [RFC6570] the value of each variable MUST
   be percent-encoded exactly once during expansion, encoding the raw
   matcher or subscriber string as a whole (any % characters it already
   contains are themselves encoded).  Hubs MUST NOT double-encode an
   already-encoded value.

   {subscriber} SHOULD be an IRI [RFC3987].  A UUID [RFC9562] or a DID
   [DID] MAY also be used.

   The content of the update MUST be a JSON [RFC8259] document
   containing at least the following properties:

   *  id: the identifier of this update; MUST be the same value as the
      subscription update's topic.
   *  type: the fixed value subscription.
   *  match_type: the topic matcher type used for this subscription.
      The value is case-sensitive and MUST be the matcher type name in
      its canonical case as defined in Section 4.
   *  match: the topic matcher used for this subscription.
   *  subscriber: the identifier of the subscriber.  It SHOULD be an
      IRI.
   *  active: true when the subscription is active, false when it is
      terminated.
   *  payload (optional): the content of the payload field associated
      with this subscription in the subscriber's access token (see
      Section 6.8).

   The document MAY contain other properties.

   To restrict subscription events to authorized subscribers, the
   subscription update MUST be marked as private.

   The hub MUST dispatch subscription events with the update type field
   — the SSE event property (see Section 5) — set to the reserved value
   mercure.  A subscriber receiving several topics through a single
   connection can then distinguish subscription events from application
   updates without inspecting the payload (for example, with
   addEventListener("mercure", ...)).  Publishers cannot produce updates
   carrying this event type (see Section 5), so a subscriber can trust
   that an event of this type was generated by the hub.  This SSE event
   value is distinct from the JSON type property above, which identifies
   the kind of document (subscription here, subscriptions for a
   collection, see Section 8.2).

   Example:

Dunglas                 Expires 12 February 2027               [Page 29]
Internet-Draft                   Mercure                     August 2026

   {
      "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector/urn%3Auuid%3Abb3de268-05b0-4c65-b44e-8f9acefc29d6",
      "type": "subscription",
      "match_type": "urlpattern",
      "match": "https://example.com/:selector",
      "subscriber": "urn:uuid:bb3de268-05b0-4c65-b44e-8f9acefc29d6",
      "active": true,
      "payload": {"foo": "bar"}
   }

8.2.  Subscription API

   If the hub supports subscription events (see Section 8.1), it SHOULD
   also expose active subscriptions through a web API.

   For instance, subscribers interested in maintaining a list of active
   subscriptions can call the web API to retrieve them, then use
   subscription events (see Section 8.1) to keep the list up to date.

   The web API MUST expose endpoints whose paths are the path of the
   hub's URL followed by the patterns below (shown expanded for the
   default hub URL):

   *  /.well-known/mercure/subscriptions: the collection of
      subscriptions.
   *  /.well-known/mercure/subscriptions/{match_type}/{match}: the
      collection of subscriptions for the given topic matcher.
   *  /.well-known/mercure/
      subscriptions/{match_type}/{match}/{subscriber}: a specific
      subscription.

   To access these URLs, clients MUST be authorized according to the
   rules defined in Section 6.  The topic to authorize is the requested
   URL in relative form: its absolute path (for example, /.well-
   known/mercure/subscriptions/{match_type}/{match}), which is the same
   form used for subscription event topics (see Section 8.1); any query
   component of the request URL is not part of the topic.  The hub MUST
   verify that a Mercure authorization detail in the access token grants
   the subscribe action on this relative topic, evaluated with the
   matcher rules of Section 4: exact matchers are compared byte-for-byte
   against the relative form, while URL patterns — absolute, or relative
   and then resolved against the hub's URL — are evaluated against it
   per Section 4.2.  Because subscription event topics and subscription
   API URLs share this canonical relative form, a single matcher covers
   both the events and the API resources describing the same
   subscriptions.  The same matching applies to every endpoint shape
   above: the collection URL /.well-known/mercure/subscriptions, the
   per-matcher collection URL, and the single-subscription URL are each

Dunglas                 Expires 12 February 2027               [Page 30]
Internet-Draft                   Mercure                     August 2026

   matched as a topic, so a token whose subscribe matcher selects a
   broader set (for example a urlpattern covering the subscriptions
   namespace, or the reserved *) grants access to the corresponding
   endpoints.  If no detail grants subscribe on the requested URL, the
   hub MUST answer 403 as defined in Section 6.

   The web API MUST set the Content-Type HTTP header to application/
   json.  When the hub serves cross-origin subscribers, it MUST expose
   the Link response header to them (for example, through the Access-
   Control-Expose-Headers header [FETCH]) so they can read the last-
   event-id attribute defined below; see Section 3.

   URLs returning a single subscription (following the pattern /.well-
   known/mercure/subscriptions/{match_type}/{match}/{subscriber}) MUST
   expose the same document as described in Section 8.1.  If the
   requested subscription does not exist, the hub MUST return a 404 HTTP
   status code.

   If the requested subscription is no longer active, the hub MAY either
   return the document with the active property set to false or return a
   404 status code.  Likewise, collection endpoints MAY include
   terminated subscriptions with active set to false or omit them.

   Collection endpoints MUST return JSON documents containing at least
   the following properties:

   *  id: the URL used to retrieve the document.
   *  type: the fixed value subscriptions.
   *  subscriptions: an array of subscription documents as described in
      Section 8.1.

   In addition, every endpoint MUST carry the reconciliation cursor as a
   last-event-id target attribute on the rel="mercure" Link header
   [RFC8288], following the same mechanism as discovery (see Section 9).
   The value is the identifier of the last event dispatched by the hub
   at the time of this request (see Section 7), or earliest if no events
   have been dispatched yet.  It SHOULD be passed back to the hub as the
   last_event_id query parameter (see Section 7) when subscribing to
   subscription events, to prevent data loss.  Because the cursor is
   carried on the Link header, a single-subscription response body is
   the subscription event document of Section 8.1 without modification.

   Subscription events are a homogeneous stream: they are always
   delivered under the reserved mercure event type with a JSON body (see
   Section 8.1).  For consistency with discovery (see Section 9), the
   hub SHOULD also set the type and content-type attributes on the same
   rel="mercure" Link header, with the values mercure and application/
   json respectively.

Dunglas                 Expires 12 February 2027               [Page 31]
Internet-Draft                   Mercure                     August 2026

   Active subscription collections can be large.  Hubs MAY truncate or
   paginate collection responses according to an implementation-defined
   policy; each returned document MUST remain valid as described above.
   Pagination mechanisms are out of scope for this specification.

   Because data returned by this web API is volatile, clients SHOULD
   validate that a cached response is still fresh before using it.

   Examples:

Dunglas                 Expires 12 February 2027               [Page 32]
Internet-Draft                   Mercure                     August 2026

   GET /.well-known/mercure/subscriptions HTTP/1.1
   Host: example.com

   HTTP/1.1 200 OK
   Content-Type: application/json
   Link: <https://example.com/.well-known/mercure>; rel="mercure"; last-event-id="urn:uuid:5e94c686-2c0b-4f9b-958c-92ccc3bbb4eb"; type="mercure"; content-type="application/json"
   ETag: "urn:uuid:5e94c686-2c0b-4f9b-958c-92ccc3bbb4eb"
   Cache-Control: must-revalidate

   {
      "id": "/.well-known/mercure/subscriptions",
      "type": "subscriptions",
      "subscriptions": [
         {
            "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector/urn%3Auuid%3Abb3de268-05b0-4c65-b44e-8f9acefc29d6",
            "type": "subscription",
            "match_type": "urlpattern",
            "match": "https://example.com/:selector",
            "subscriber": "urn:uuid:bb3de268-05b0-4c65-b44e-8f9acefc29d6",
            "active": true,
            "payload": {"foo": "bar"}
         },
         {
            "id": "/.well-known/mercure/subscriptions/exact/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2Fa-topic/urn%3Auuid%3A1e0cba4c-4bcd-44f0-ae8a-7b76f7ef1280",
            "type": "subscription",
            "match": "https://example.com/a-topic",
            "match_type": "exact",
            "subscriber": "urn:uuid:1e0cba4c-4bcd-44f0-ae8a-7b76f7ef1280",
            "active": true,
            "payload": {"baz": "bat"}
         },
         {
            "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector/urn%3Auuid%3Aa6c49794-5f74-4723-999c-3a7e33e51d49",
            "type": "subscription",
            "match_type": "urlpattern",
            "match": "https://example.com/:selector",
            "subscriber": "urn:uuid:a6c49794-5f74-4723-999c-3a7e33e51d49",
            "active": true,
            "payload": {"foo": "bap"}
         }
      ]
   }

Dunglas                 Expires 12 February 2027               [Page 33]
Internet-Draft                   Mercure                     August 2026

   GET /.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector HTTP/1.1
   Host: example.com

   HTTP/1.1 200 OK
   Content-Type: application/json
   Link: <https://example.com/.well-known/mercure>; rel="mercure"; last-event-id="urn:uuid:5e94c686-2c0b-4f9b-958c-92ccc3bbb4eb"; type="mercure"; content-type="application/json"
   ETag: "urn:uuid:5e94c686-2c0b-4f9b-958c-92ccc3bbb4eb"
   Cache-Control: must-revalidate

   {
      "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector",
      "type": "subscriptions",
      "subscriptions": [
         {
            "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector/urn%3Auuid%3Abb3de268-05b0-4c65-b44e-8f9acefc29d6",
            "type": "subscription",
            "match": "https://example.com/:selector",
            "match_type": "urlpattern",
            "subscriber": "urn:uuid:bb3de268-05b0-4c65-b44e-8f9acefc29d6",
            "active": true,
            "payload": {"foo": "bar"}
         },
         {
            "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector/urn%3Auuid%3Aa6c49794-5f74-4723-999c-3a7e33e51d49",
            "type": "subscription",
            "match": "https://example.com/:selector",
            "match_type": "urlpattern",
            "subscriber": "urn:uuid:a6c49794-5f74-4723-999c-3a7e33e51d49",
            "active": true,
            "payload": {"foo": "bap"}
         }
      ]
   }

Dunglas                 Expires 12 February 2027               [Page 34]
Internet-Draft                   Mercure                     August 2026

   GET /.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector/urn%3Auuid%3Abb3de268-05b0-4c65-b44e-8f9acefc29d6 HTTP/1.1
   Host: example.com

   HTTP/1.1 200 OK
   Content-Type: application/json
   Link: <https://example.com/.well-known/mercure>; rel="mercure"; last-event-id="urn:uuid:5e94c686-2c0b-4f9b-958c-92ccc3bbb4eb"; type="mercure"; content-type="application/json"
   ETag: "urn:uuid:5e94c686-2c0b-4f9b-958c-92ccc3bbb4eb"
   Cache-Control: must-revalidate

   {
      "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%2Fblue-sea-697d.quartiers047.workers.dev%3A443%2Fhttps%2Fexample.com%2F%3Aselector/urn%3Auuid%3Abb3de268-05b0-4c65-b44e-8f9acefc29d6",
      "type": "subscription",
      "match": "https://example.com/:selector",
      "match_type": "urlpattern",
      "subscriber": "urn:uuid:bb3de268-05b0-4c65-b44e-8f9acefc29d6",
      "active": true,
      "payload": {"foo": "bar"}
   }

9.  Discovery

9.1.  Hub Discovery

   The discovery mechanism aims at identifying the URL of one or more
   hubs designated by the publisher.

   The URL of the hub SHOULD be the "well-known" [RFC8615] fixed path
   /.well-known/mercure, which gives publishers and subscribers a
   default requiring no configuration.  The hub URL advertised through
   discovery is authoritative: it MAY be any HTTPS URL, enabling
   deployments behind path prefixes and several hubs sharing an origin.
   Protected resource metadata (see Section 9.2) is derived from the hub
   URL per [RFC9728] whatever its path, and the reserved namespace and
   subscription-event topics follow the path of the hub URL (see
   Section 5 and Section 8.1).  Examples throughout this document assume
   the default hub URL.

   If the publisher is a server, it SHOULD advertise the URL of one or
   more hubs to the subscriber so that the subscriber can receive live
   updates.  If more than one hub URL is specified, the publisher MUST
   notify each hub, and the subscriber MAY subscribe to one or more of
   them.

   Note: Publishers may wish to advertise and publish to more than one
   hub for fault tolerance and redundancy.  If one hub fails to
   propagate an update, the others increase the likelihood of delivery
   to subscribers.

Dunglas                 Expires 12 February 2027               [Page 35]
Internet-Draft                   Mercure                     August 2026

   The publisher SHOULD include at least one Link Header [RFC8288] with
   rel=mercure (a hub link header).  The target URL of such links MUST
   be a hub implementing the Mercure protocol.

   Note: A compromised publisher can advertise a malicious hub URL and
   capture the access tokens of subscribers that connect to it.
   Subscribers SHOULD restrict accepted hub URLs to origins they have a
   basis to trust (for example, hubs sharing the publisher's registered
   domain) and MAY verify the hub's identity through out-of-band means
   before transmitting credentials.

   The publisher MAY provide the following target attributes in the Link
   Headers:

   *  last-event-id: the identifier of the last event dispatched by the
      publisher at the time the resource was generated.  If provided, it
      MUST be passed to the hub through a query parameter named
      last_event_id; this ensures that updates dispatched between the
      resource generation and the connection to the hub are not lost.
      See Section 7.
   *  content-type: the content type of the updates that will be pushed
      by the hub.  If omitted, the subscriber MUST assume that the
      content type matches that of the original resource.  The content-
      type attribute is especially useful to indicate that partial
      updates will be pushed, in formats such as JSON Patch [RFC6902] or
      JSON Merge Patch [RFC7396].  Because the data field of a Server-
      Sent Event carries no content type of its own, this attribute lets
      a subscriber process the payload without content sniffing.
   *  type: the Server-Sent Events event field value (see Section 3)
      that the updates pushed for this resource will carry.  A
      subscriber using the EventSource interface [HTML] can register a
      listener for this type; if omitted, the subscriber MUST assume the
      default event type (the events delivered to the EventSource
      message handler).  It is subject to the same character constraints
      as the publication type field (see Section 5).  A publisher
      advertising its own resource MUST NOT use the reserved mercure
      type, which the hub generates for subscription events; the hub
      advertises mercure on the subscription API, where the stream
      consists of those events (see Section 8.2 and Section 8.1).

   The content-type and type attributes each describe a single,
   homogeneous update stream: a publisher whose updates for a resource
   use more than one content type, or more than one event type, MUST
   omit the corresponding attribute, and the subscriber then determines
   that value from each individual update.  These attributes are hints;
   they do not affect routing, which is governed solely by topic
   matchers (see Section 3).

Dunglas                 Expires 12 February 2027               [Page 36]
Internet-Draft                   Mercure                     August 2026

   All these attributes are optional.

   Minimal example:

   GET /books/foo HTTP/1.1
   Host: example.com

   HTTP/1.1 200 OK
   Content-Type: application/ld+json
   Link: <https://example.com/.well-known/mercure>; rel="mercure"

   {"@id": "/books/foo", "foo": "bar"}

9.2.  Protected Resource Metadata

   As an OAuth 2.0 protected resource, the hub SHOULD publish OAuth 2.0
   Protected Resource Metadata [RFC9728] at the path derived from its
   URL as defined in that specification (for the hub URL /.well-known/
   mercure, the metadata is served at /.well-known/oauth-protected-
   resource/.well-known/mercure).  Publishing the document is a SHOULD;
   a document that is published MUST include the resource member
   (required by [RFC9728]) and MAY include the others:

   *  resource (required): the hub's resource identifier, which is the
      value clients MUST place in the access token aud claim (see
      Section 6.3).  Clients MUST verify that this value equals the
      resource identifier they used to derive the well-known URL
      [RFC9728].
   *  authorization_servers (optional): issuer identifiers of the
      authorization servers that can issue tokens for the hub [RFC8414].
      Omitted when tokens are self-issued.
   *  authorization_details_types_supported: the authorization detail
      types the hub understands.  The metadata member is defined by
      [RFC9728]; its values are authorization_details type identifiers
      per [RFC9396].  For hubs implementing this specification, the
      array contains https://mercure.rocks/authorization-detail (see
      Section 6.4).
   *  bearer_methods_supported: the [RFC6750] token presentation methods
      the hub actually accepts (see Section 6.1): header [RFC9728].  The
      cookie mechanism is not a [RFC6750] bearer method, so it is not
      listed here; it is advertised by the separate mercure_cookie
      member below.
   *  mercure_cookie (optional): a string, the name of the cookie in
      which the hub also accepts the access token (a Mercure extension
      to [RFC6750]; see Section 6.1.2).  A client that cannot set an
      Authorization header (for example, a web browser using
      EventSource) presents the token by setting a cookie of this name.
      The cookie mechanism is advertised as a dedicated metadata member

Dunglas                 Expires 12 February 2027               [Page 37]
Internet-Draft                   Mercure                     August 2026

      rather than a value of bearer_methods_supported, whose values are
      constrained to the [RFC6750] methods.  This member is omitted when
      the hub does not offer cookie authorization.
   *  mercure_matcher_types_supported (optional): a JSON array of
      strings listing the names of the topic matcher types the hub
      supports (see Section 4).  When omitted, the supported set is
      exactly the two types defined by this document, exact and
      urlpattern; the member is only needed when the hub supports
      registered additional types.
   *  mercure_subscriptions (optional): a boolean.  When true, the hub
      implements the active subscriptions feature (see Section 8).  This
      member is omitted when the hub does not implement it.

   This protocol carries no version identifier: a future incompatible
   revision is expected to be published as a new specification defining
   its own metadata members or well-known location.  Hubs implementing
   pre-standardization revisions of this protocol (which used a topic
   subscribe query parameter and a bespoke token claim) do not publish
   protected resource metadata; clients needing to coexist with them MAY
   treat the absence of this metadata as a hint that the hub implements
   such a revision.

   When a request carries no access token, the hub's WWW-Authenticate:
   Bearer challenge SHOULD include a resource_metadata parameter
   pointing to this document (see Section 6.2), so that clients can
   discover the resource identifier and authorization server without
   prior configuration.

9.3.  Topic Discovery

   The discovery mechanism MAY also be used to identify the canonical
   URL for the topic that subscribers are expected to use for
   subscriptions.

   The publisher MAY include one Link Header [RFC8288] with rel=self
   (the self link header).  It SHOULD contain the canonical URL for the
   topic.  If the link with rel=self is omitted, the current URL of the
   resource MAY be used as a fallback.

   Links embedded in HTML or XML documents as defined in the WebSub
   recommendation [W3C.REC-websub-20180123] MAY also be supported by
   subscribers.  If both a header and an embedded link are provided, the
   header MUST be preferred.

Dunglas                 Expires 12 February 2027               [Page 38]
Internet-Draft                   Mercure                     August 2026

9.3.1.  Content Negotiation

   For practical purposes, the rel=self URL SHOULD offer a single
   representation.  The hub has no way to know which Media Type
   ([RFC6838]) or language was requested by the subscriber upon
   discovery, and therefore cannot select a representation on its
   behalf.

   Content negotiation can, however, be performed by returning a
   different rel=self URL based on the HTTP headers of the discovery
   request.  For example, a request to /books/foo with an Accept header
   containing application/ld+json could return a rel=self value of
   /books/foo.jsonld.

   The example below illustrates how a topic URL can return different
   Link headers depending on the Accept header.

   GET /books/foo HTTP/1.1
   Host: example.com
   Accept: application/ld+json

   HTTP/1.1 200 OK
   Content-Type: application/ld+json
   Link: </books/foo.jsonld>; rel="self"
   Link: <https://example.com/.well-known/mercure>; rel="mercure"

   {"@id": "/books/foo", "foo": "bar"}

   GET /books/foo HTTP/1.1
   Host: example.com
   Accept: text/html

   HTTP/1.1 200 OK
   Content-Type: text/html
   Link: </books/foo.html>; rel="self"
   Link: <https://example.com/.well-known/mercure>; rel="mercure"

   <!doctype html>
   <link rel="self" href="/books/foo.html">
   <link rel="mercure" href="https://example.com/.well-known/mercure">
   <title>foo: bar</title>

   The same technique can be used to return a different rel=self URL
   depending on the language requested by the Accept-Language header.

Dunglas                 Expires 12 February 2027               [Page 39]
Internet-Draft                   Mercure                     August 2026

   GET /books/foo HTTP/1.1
   Host: example.com
   Accept: application/ld+json
   Accept-Language: fr-FR

   HTTP/1.1 200 OK
   Content-Type: application/ld+json
   Content-Language: fr-FR
   Link: </books/foo-fr-FR.jsonld>; rel="self"
   Link: <https://example.com/.well-known/mercure>; rel="mercure"

   {"@id": "/books/foo", "foo": "bar", "@context": {"@language": "fr-FR"}}

10.  Encryption

   Using HTTPS does not prevent the hub from accessing the content of an
   update.  Depending on the intended privacy of the information
   contained in the update, it MAY be necessary to prevent eavesdropping
   by the hub.

   To prevent the hub from reading the message content, the publisher
   MAY encrypt the message before sending it.  The publisher SHOULD use
   JSON Web Encryption [RFC7516] to encrypt the content of the update.
   The encryption keys are shared between the publisher and the
   subscriber through any out-of-band mechanism, for example a JSON Web
   Key Set [RFC7517]; the hub is not involved in this exchange.

   Update encryption is considered a best practice to prevent mass
   surveillance, especially when the hub is managed by an external
   provider.

   Implementations SHOULD restrict JWE algorithms to those whose
   security properties remain acceptable at the time of deployment; in
   particular, RSA1_5 MUST NOT be used, due to padding oracle
   vulnerabilities [RFC8017].  At the time of writing, key management
   algorithms ECDH-ES+A256KW and RSA-OAEP-256, and content encryption
   algorithm A256GCM, are RECOMMENDED.

   JWE provides integrity per message but does not provide replay
   protection: a hub or an on-path attacker that captures a ciphertext
   can later replay it without modifying it.  Publishers concerned with
   replay SHOULD include a freshness indicator (such as a timestamp or
   nonce) inside the encrypted payload and require subscribers to
   validate it.

Dunglas                 Expires 12 February 2027               [Page 40]
Internet-Draft                   Mercure                     August 2026

   Long-lived JWE keys do not provide forward secrecy: compromise of
   such keys decrypts all past traffic encrypted under them.  Publishers
   handling sensitive data SHOULD rotate JWE keys periodically and MAY
   use ephemeral key agreement (e.g., ECDH-ES) to bound the impact of a
   future key compromise.

11.  Using HTTP

   This protocol follows the guidance of [RFC9205].  It uses standard
   HTTP methods, standard status codes, and registered media types, and
   does not overload their semantics.  Clients discover a hub from
   rel=mercure Web Linking [RFC8288] relations (see Section 9) rather
   than a mandated well-known location: the fixed path /.well-known/
   mercure is only a SHOULD default, and the authoritative hub URL
   advertised through discovery MAY be any HTTPS URL.  The hub's own
   resources (protected resource metadata and the subscription API) hang
   off that authoritative URL, so the protocol adds no application
   semantics to a site-wide well-known space it does not own.

12.  IANA Considerations

12.1.  Well-Known URIs Registry

   The "mercure" well-known URI described in Section 9 is already
   registered in the "Well-Known URIs" registry.  IANA is requested to
   update the reference of the existing registration to this document:

   *  URI Suffix: mercure
   *  Change Controller: IETF
   *  Specification document(s): This specification, Section 9
   *  Status: permanent
   *  Related information: N/A

12.2.  Link Relation Types Registry

   The "mercure" link relation type described in Section 9 is already
   registered in the "Link Relation Types" registry.  IANA is requested
   to update the reference of the existing registration to this
   document:

   *  Relation Name: mercure
   *  Description: The Mercure Hub to use to subscribe to updates of
      this resource.
   *  Reference: This specification, Section 9

Dunglas                 Expires 12 February 2027               [Page 41]
Internet-Draft                   Mercure                     August 2026

12.3.  OAuth Protected Resource Metadata Registry

   The following values are to be registered in the "OAuth Protected
   Resource Metadata" registry established by [RFC9728]:

   *  Metadata Name: mercure_cookie

   *  Metadata Description: String naming the cookie in which the
      Mercure hub also accepts the access token

   *  Change Controller: IETF

   *  Specification Document(s): This specification, Section 9.2

   *  Metadata Name: mercure_matcher_types_supported

   *  Metadata Description: JSON array listing the names of the Mercure
      topic matcher types supported by the hub

   *  Change Controller: IETF

   *  Specification Document(s): This specification, Section 9.2

   *  Metadata Name: mercure_subscriptions

   *  Metadata Description: Boolean indicating that the Mercure hub
      implements the active subscriptions feature

   *  Change Controller: IETF

   *  Specification Document(s): This specification, Section 9.2

12.4.  HTTP Field Name Registry

   IANA is requested to register the following entry in the "Hypertext
   Transfer Protocol (HTTP) Field Name Registry" defined by [RFC9110]:

   *  Field Name: Mercure-Last-Event-ID
   *  Status: permanent
   *  Structured Type: N/A
   *  Reference: This specification, Section 7
   *  Comments: Response field carrying the identifier of the event
      preceding the first event sent, or the reserved value earliest

Dunglas                 Expires 12 February 2027               [Page 42]
Internet-Draft                   Mercure                     August 2026

12.5.  Mercure Topic Matcher Types Registry

   IANA is requested to establish a "Mercure Topic Matcher Types"
   registry.  New registrations follow the Specification Required policy
   [RFC8126].

   A matcher type name MUST consist of lowercase ASCII letters and
   digits and MUST begin with a lowercase letter.  The name is case-
   sensitive and is used verbatim as the suffix of the match_<matcher-
   type> subscribe query parameter (see Section 3) and as the match_type
   value in authorization details (see Section 6.7).  The designated
   experts verify that the defining specification states the matching
   semantics precisely, bounds the evaluation cost of a crafted matcher
   (see Section 13.7 for the kind of exposure to consider), and does not
   conflict with the reserved wildcard * (see Section 4).

   A registration provides:

   *  Matcher Type Name: the name, subject to the syntax above.
   *  Description: a brief description of the matching semantics.
   *  Change Controller: "IETF" for registrations in the IETF stream,
      otherwise the party responsible for the registration.
   *  Reference: the specification defining the matcher type.

   Note that the name syntax above admits no name equal to the reserved
   wildcard * (see Section 4), so no registration can shadow it.

   Initial registrations:

     +============+===================+============+================+
     | Matcher    | Description       | Change     | Reference      |
     | Type Name  |                   | Controller |                |
     +============+===================+============+================+
     | exact      | Byte-for-byte     | IETF       | This           |
     |            | comparison of     |            | specification, |
     |            | topic and matcher |            | Section 4.1    |
     +------------+-------------------+------------+----------------+
     | urlpattern | Matching per the  | IETF       | This           |
     |            | URL Pattern       |            | specification, |
     |            | Living Standard   |            | Section 4.2    |
     +------------+-------------------+------------+----------------+

                                 Table 2

Dunglas                 Expires 12 February 2027               [Page 43]
Internet-Draft                   Mercure                     August 2026

12.6.  Mercure Actions Registry

   IANA is requested to establish a "Mercure Actions" registry for the
   values of the actions property of Mercure authorization details (see
   Section 6.4).  New registrations follow the Specification Required
   policy [RFC8126].

   An action name MUST consist of lowercase ASCII letters and digits.
   Hubs ignore actions they do not recognize (see Section 6.4), so
   registering a new action does not require deployed hubs to change.

   The designated experts verify that the defining specification names
   an operation the hub performs on a topic, states how a token carrying
   the action is evaluated against the topics of its authorization
   detail, and does not duplicate an already registered action.

   A registration provides:

   *  Action Name: the name, subject to the syntax above.
   *  Description: a brief description of the operation the action
      authorizes.
   *  Change Controller: "IETF" for registrations in the IETF stream,
      otherwise the party responsible for the registration.
   *  Reference: the specification defining the action.

   Initial registrations:

     +===========+=====================+============+================+
     | Action    | Description         | Change     | Reference      |
     | Name      |                     | Controller |                |
     +===========+=====================+============+================+
     | publish   | Send updates for    | IETF       | This           |
     |           | the matching topics |            | specification, |
     |           |                     |            | Section 6.5    |
     +-----------+---------------------+------------+----------------+
     | subscribe | Receive private     | IETF       | This           |
     |           | updates for the     |            | specification, |
     |           | matching topics     |            | Section 6.6    |
     +-----------+---------------------+------------+----------------+

                                  Table 3

13.  Security Considerations

   The recommendations of the OAuth 2.0 Security Best Current Practice
   [RFC9700] apply to deployments of this protocol; this section
   highlights the considerations specific to Mercure.

Dunglas                 Expires 12 February 2027               [Page 44]
Internet-Draft                   Mercure                     August 2026

   The confidentiality of the secret key(s) used to sign access tokens
   is a primary concern.  Such keys MUST be stored securely and MUST be
   revoked immediately in the event of a breach.

   A valid access token allows any client that holds it to subscribe to
   or publish on the hub.  Its confidentiality MUST therefore be
   ensured: access tokens MUST only be transmitted over secure
   connections.

   When the client is a web browser, the access token SHOULD NOT be
   exposed to JavaScript, to provide resilience against cross-site
   scripting (XSS) attacks [OWASP-XSS].  For this reason, HttpOnly
   cookies SHOULD be preferred as the authorization mechanism in that
   case.

   In the event of a breach, revoking access tokens before their
   expiration is often difficult.  Short-lived tokens are therefore
   strongly RECOMMENDED.

   The hub's publishing endpoint can be targeted by cross-site request
   forgery (CSRF) attacks [OWASP-CSRF] when the cookie-based
   authorization mechanism is used.  Implementations supporting that
   mechanism MUST mitigate such attacks: the SameSite cookie attribute
   recommended in Section 6.1.2 is the first line of defense, and
   because some deployed user agents do not enforce it, hubs SHOULD also
   verify that the source origin conveyed by the Origin or Referer HTTP
   header matches the target origin, rejecting the request when neither
   header is available.  CSRF prevention techniques are described in
   depth in [OWASP-CSRF-Prevention].

   Access tokens MUST NOT be passed in URLs (for example, via the
   access_token query parameter, which this specification does not
   accept; see Section 6.1).  Browsers, web servers, and other software
   may not adequately secure URLs stored in browser history, server
   logs, and other data structures, and an attacker able to read those
   locations could steal the token [RFC9700].

13.1.  Token Validation

   Access tokens are validated as JWT access tokens before authorization
   details are evaluated (see Section 6.3).  Failure to verify the typ
   is at+jwt, to reject alg: none, to bind alg to the key type, to
   enforce exp/nbf, or to verify aud enables token forgery, token type
   confusion (for example, accepting an ID Token), replay across
   contexts, and algorithm-confusion attacks.

Dunglas                 Expires 12 February 2027               [Page 45]
Internet-Draft                   Mercure                     August 2026

13.2.  Server-Sent Events Field Injection

   Topic strings and the id, type, and retry publish fields end up on
   the wire as part of the Server-Sent Events framing.  Values
   containing CR (U+000D), LF (U+000A), or NUL (U+0000) could inject
   arbitrary SSE fields into the stream as seen by subscribers,
   including forged event identifiers and event types.  The character
   constraints in Section 2 and Section 5 prevent this injection.  The
   data field may contain line breaks legitimately; it is not
   constrained the same way, so the hub MUST serialize it as one data:
   field per line (see Section 5) rather than emitting the raw value.  A
   hub that writes the value without this line-splitting would let a
   data value containing \nevent: or \nid: inject a forged field, so
   this serialization is a security requirement, not only a formatting
   one.

13.3.  Reserved Hub Namespace

   The path subtree of the hub URL (/.well-known/mercure/ for the
   default hub URL) is reserved for resources generated by the hub
   itself (see Section 5).  A publisher with broad scope publishing
   under this prefix could forge subscription events (see Section 8.1)
   and mislead other subscribers tracking subscription lifecycle.  The
   reserved-namespace test is applied to the topic's path component
   after resolution against the hub's URL and after percent-encoding
   normalization, not as a leading-substring match on the raw value; a
   substring test would let an absolute topic addressing the hub's own
   host (for example, https://hub.example.com/.well-known/mercure/
   subscriptions/...) bypass it, and skipping normalization would let
   percent-encoded variants (for example, /.well-known/%6Dercure/...) do
   the same.

   The mercure SSE event type is reserved the same way.  The hub sets it
   on the events it generates (see Section 8.1), and a subscriber may
   route on it (for example, with addEventListener("mercure", ...)).
   Because the SSE event field carries the publisher-supplied type
   field, a publisher could otherwise emit an update with type set to
   mercure on any topic and have it delivered to that listener as a
   forged hub event.  Hubs MUST therefore reject a publish request whose
   type is mercure (see Section 5).  Reserved-namespace enforcement
   bounds forgery by topic; this rule bounds it by event type, for
   subscribers that key on the event type rather than the topic.

Dunglas                 Expires 12 February 2027               [Page 46]
Internet-Draft                   Mercure                     August 2026

13.4.  Authorization on Event Replay

   When a subscriber reconnects with a Last-Event-ID header or
   last_event_id query parameter, the same authorization rules apply to
   replayed events as to live events (see Section 7 and Section 6.6).  A
   subscriber whose authorized scope has shrunk between publication and
   reconnection does not receive private events outside its scope at
   reconnection time.  The Mercure-Last-Event-ID response field is a
   cursor, however, and MAY contain the identifier of an event the
   subscriber is not authorized to receive.  Operators handling
   sensitive private updates SHOULD use opaque, random event identifiers
   so that this identifier discloses nothing beyond the event's
   existence.

13.5.  Subscriber Identifier Assignment

   The {subscriber} identifier in subscription event topic URLs is
   preferably a random per-connection identifier generated by the hub,
   and otherwise derived from information the hub has cryptographically
   validated (see Section 8.1).  Allowing clients to supply, suggest, or
   override this value through any unauthenticated channel would enable
   spoofing of subscription events and hijacking of subscription state
   belonging to other subscribers.  A derivation must be unique per
   connection: the issuer and sub pair alone collides when one token is
   used on several concurrent connections holding the same matcher — the
   connections then share a subscription event topic, so terminating one
   emits an active: false event that misrepresents the state of the
   others, and the corresponding subscription API resource becomes
   ambiguous.  Deriving the identifier from sub alone would additionally
   let subscribers of distinct issuers collide with — and thereby
   impersonate — one another, which is why any derivation incorporates
   the issuer.  Identifiers derived from sub also disclose that claim's
   value to every subscriber authorized for the corresponding
   subscription events; hub-generated random identifiers avoid that
   disclosure.

13.6.  Private Update Audience

   A private update is delivered to a subscriber only when the
   subscriber's access token grants the subscribe action on at least one
   of its topics (see Section 6.6 and Section 6.4).  Authorization is
   therefore a hub-enforced check tied to the token issued for the
   subscriber, not a property of the subscriber's routing matchers:
   those only select which topics a subscriber listens to and never
   widen what it may read.  Unauthenticated subscribers, when the hub
   accepts them (see Section 6), present no token and therefore never
   receive private updates.

Dunglas                 Expires 12 February 2027               [Page 47]
Internet-Draft                   Mercure                     August 2026

   Because authorization is evaluated against the update's topics as a
   whole, the audience of a private update is the union of the audiences
   of each of its topics: attaching an alternate topic matchable by a
   broad audience discloses the update's full content to that audience,
   regardless of how narrowly the canonical topic is scoped.  Publishers
   MUST NOT attach to a private update an alternate topic matchable by
   an audience broader than the audience intended to read the update's
   content (see Section 6.6 for a worked example).  This places the per-
   resource confidentiality boundary in the publisher's choice of
   topics, in addition to the hub-enforced check against the
   subscriber's token: the hub verifies that the token matches some
   topic of the update, but does not know which topics a publisher
   intended as narrowing versus broadening.  Issuers and publishers are
   jointly responsible for keeping topics and authorization details
   aligned with the intended access-control policy; deployments with
   strict per-resource confidentiality requirements should account for
   this when designing their topic conventions.

13.7.  URL-Pattern Denial of Service

   URL Pattern compiles internally to a regular expression.  Naive
   implementations on engines such as PCRE are vulnerable to
   catastrophic backtracking.  The mitigations required in Section 4.2 —
   a linear-time engine (such as RE2 [re2]) or a per-evaluation cost or
   time limit — bound this exposure.

13.8.  Payload Privacy

   Payloads carried in Mercure authorization details are included in
   subscription events and forwarded to other authorized subscribers
   (see Section 6.8).  Within the set of subscribers authorized for the
   corresponding subscription events, a payload is effectively
   broadcast; it cannot carry private metadata about an individual
   subscriber.

13.9.  Topic Normalization

   Topic strings are compared as byte sequences.  Without Unicode
   normalization (NFC) and IDNA host canonicalization, visually
   identical topics may be treated as distinct, leading to undelivered
   updates or to spoofable topic names through homograph attacks (e.g.,
   example.com versus a host containing Cyrillic look-alike characters).
   The normalization guidance in Section 4.1 addresses this.

Dunglas                 Expires 12 February 2027               [Page 48]
Internet-Draft                   Mercure                     August 2026

13.10.  Resource Limits

   Absent limits on request and token size, malicious clients can
   exhaust hub resources.  The implementation-defined limits described
   elsewhere in this document — on publish request body size, individual
   field length, the number of topic fields per publish request, the
   number of topic matcher query parameters per request, the number of
   Mercure authorization details and of entries in their topics arrays,
   individual pattern length, concurrent subscriptions per token, and
   concurrent connections per client and in total (see Section 3) —
   bound this exposure.

13.11.  Hub Trust

   Subscribers obtain hub URLs from publishers via the discovery
   mechanism (see Section 9) and transmit credentials to the hub.  A
   compromised publisher can therefore redirect subscribers to a hub of
   its choosing and capture those credentials.  As described in
   Section 9, subscribers constrain the set of hub origins they connect
   to and can verify hub identity out of band.  Scoping each token to
   its intended hub with the aud claim (see Section 6.3) limits the
   value of a captured token: a token bound to one hub's identifier
   cannot be replayed against another hub, even when the two share
   signing keys.

13.12.  Protected Resource Metadata and Authorization Server Selection

   When the hub advertises an authorization server through protected
   resource metadata [RFC9728] (see Section 9), a client that is misled
   into using an inappropriate authorization server may expose itself to
   an adversary-in-the-middle.  Clients SHOULD validate protected
   resource metadata as described in [RFC9728] and obtain it only from
   the deterministically derived, TLS-protected well-known location.
   Hubs and clients fetching metadata or key sets by URL SHOULD take
   precautions against server-side request forgery, such as refusing
   requests to internal address ranges.

13.13.  Bearer Tokens and Sender Constraint

   The access token is a bearer credential: any party that obtains it
   can act within its scope until it expires.  Short-lived tokens (see
   above) limit the exposure window but do not prevent use of a token
   during its lifetime.  Deployments protecting high-value operations
   MAY additionally sender-constrain tokens, for example with DPoP
   [RFC9449] or mutual-TLS-bound tokens, so that a captured token is
   unusable without the corresponding proof-of-possession key.

Dunglas                 Expires 12 February 2027               [Page 49]
Internet-Draft                   Mercure                     August 2026

13.14.  Publish Request Replay

   A captured publish request carrying a bearer access token can be
   replayed by an on-path attacker, causing the same update to be
   dispatched again.  For most deployments re-dispatching an identical
   update is harmless.  Deployments for which it is not SHOULD include a
   freshness indicator in the update (for example, checking the unique
   id recommended in Section 5 against previously seen values, or a
   timestamp) and reject duplicates.

13.15.  JWE Algorithms and Replay

   JWE-protected updates are subject to algorithm-selection pitfalls and
   to replay.  The algorithm restrictions and freshness guidance in
   Section 10 address these.

14.  Privacy Considerations

   The general privacy guidance of [RFC6973] applies.  The following
   considerations are specific to this protocol:

   *  Subscription events and the subscription API expose presence
      information: which subscriber identifiers hold which
      subscriptions, and when those subscriptions are created and
      terminated.  Hubs restrict this information to authorized
      subscribers (see Section 8.1); token issuers SHOULD NOT grant
      subscribe on the subscriptions namespace more broadly than the
      tracking use case requires.
   *  Payloads embedded in access tokens are broadcast to every
      subscriber authorized for the corresponding subscription events
      (see Section 6.8 and Section 13.8).
   *  Topic identifiers appear in URLs (subscription query parameters
      and subscription event topics) and are therefore visible to the
      hub operator and to any infrastructure that logs URLs.  Sensitive
      information SHOULD NOT be encoded in topic strings.
   *  The hub operator observes connection metadata — client addresses,
      connection times, topic matchers, and update traffic patterns —
      even when update contents are encrypted.  Update encryption (see
      Section 10) bounds what a curious or compromised hub learns to
      this metadata.
   *  Event identifiers act as cursors that can reveal the existence and
      approximate timing of updates a subscriber is not authorized to
      read; see Section 13.4.

15.  Implementation Status

   [RFC Editor Note: Please remove this entire section prior to
   publication as an RFC.]

Dunglas                 Expires 12 February 2027               [Page 50]
Internet-Draft                   Mercure                     August 2026

   This section records the status of known implementations of the
   protocol defined by this specification at the time of posting of this
   Internet-Draft, and is based on a proposal described in [RFC7942].
   The description of implementations in this section is intended to
   assist the IETF in its decision processes in progressing drafts to
   RFCs.  Please note that the listing of any individual implementation
   here does not imply endorsement by the IETF.  Furthermore, no effort
   has been spent to verify the information presented here that was
   supplied by IETF contributors.  This is not intended as, and must not
   be construed to be, a catalog of available implementations or their
   features.  Readers are advised to note that other implementations may
   exist.  According to RFC 7942, "this will allow reviewers and working
   groups to assign due consideration to documents that have the benefit
   of running code, which may serve as evidence of valuable
   experimentation and feedback that have made the implemented protocols
   more mature.  It is up to the individual working groups to use this
   information as they see fit."

   Note: entries reporting compatibility with revision 5 of this draft
   predate the OAuth 2.0 authorization model and the topic matcher types
   defined by this revision; they interoperate only with hubs
   implementing those earlier revisions.

15.1.  Mercure.rocks Hub

   Organization responsible for the implementation:

   Dunglas Services SAS

   Implementation Name and Details:

   Mercure.rocks, available at https://mercure.rocks
   (https://mercure.rocks)

   Brief Description:

   This is the reference implementation of the Mercure hub.  It is
   written in Go and is optimized for performance.

   Level of Maturity:

   Widely used.

   Coverage:

   All the features of the protocol.

   Version compatibility:

Dunglas                 Expires 12 February 2027               [Page 51]
Internet-Draft                   Mercure                     August 2026

   The implementation follows the latest draft.

   Licensing:

   All code is covered under the GNU Affero Public License version 3 or
   later.

   Implementation Experience:

   Used in production.

   Contact Information:

   Kévin Dunglas, contact@mercure.rocks (mailto:contact@mercure.rocks)
   https://mercure.rocks (https://mercure.rocks)

   Interoperability:

   Reported compatible with all major browsers and server-side tools.

15.2.  Freddie

   Implementation Name and Details:

   Freddie, https://github.com/bpolaszek/freddie
   (https://github.com/bpolaszek/freddie)

   Brief Description:

   Freddie is a PHP implementation of the Mercure Hub Specification.

   Level of Maturity:

   Stable.

   Coverage:

   All the features of the protocol except the subscription events.

   Version compatibility:

   The implementation follows the latest draft.

   Licensing:

   All code is covered under the GNU General Public License v3.0.

   Contact Information:

Dunglas                 Expires 12 February 2027               [Page 52]
Internet-Draft                   Mercure                     August 2026

   https://github.com/bpolaszek/freddie (https://github.com/bpolaszek/
   freddie)

   Interoperability:

   Reported compatible with all major browsers and server-side tools.

15.3.  Ilshidur/node-mercure

   Implementation Name and Details:

   Ilshidur/node-mercure, https://github.com/Ilshidur/node-mercure
   (https://github.com/Ilshidur/node-mercure)

   Brief Description:

   Hub and Publisher implemented in Node.

   Level of Maturity:

   Beta, not suitable for production.

   Coverage:

   All the features of the protocol except the subscription events.

   Version compatibility:

   The implementation currently follows the revision 5 of the draft.

   Licensing:

   All code is covered under the GNU Public License version 3 or later.

   Contact Information:

   https://github.com/Ilshidur/node-mercure
   (https://github.com/Ilshidur/node-mercure)

   Interoperability:

   Reported compatible with all major browsers and server-side tools.

15.4.  Symfony

   Implementation Name and Details:

Dunglas                 Expires 12 February 2027               [Page 53]
Internet-Draft                   Mercure                     August 2026

   Symfony Mercure Component, available at
   https://symfony.com/doc/current/components/mercure.html
   (https://symfony.com/doc/current/components/mercure.html)

   Brief Description:

   This is a publisher library written in PHP.  It also provides support
   for Mercure in the Symfony web framework.

   Level of Maturity:

   Widely used.

   Coverage:

   All the publisher features of the protocol.

   Version compatibility:

   The implementation follows the latest draft.

   Licensing:

   All code is covered under the MIT license.

   Implementation Experience:

   Used in production.

   Contact Information:

   https://symfony.com (https://symfony.com)

   Interoperability:

   Reported compatible with the Mercure.rocks Hub.

15.5.  API Platform

   Implementation Name and Details:

   API Platform, available at https://api-platform.com/docs/core/
   mercure/ (https://api-platform.com/docs/core/mercure/)

   Brief Description:

Dunglas                 Expires 12 February 2027               [Page 54]
Internet-Draft                   Mercure                     August 2026

   The API Platform framework lets developers create async APIs
   implementing the Mercure protocol and generate clients for these
   APIs.

   Level of Maturity:

   Widely used.

   Coverage:

   All the publisher and consumer features of the protocol.

   Version compatibility:

   The implementation follows the latest draft.

   Licensing:

   All code is covered under the MIT license.

   Implementation Experience:

   Used in production.

   Contact Information:

   https://api-platform.com (https://api-platform.com)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

15.6.  Laravel Mercure Broadcaster

   Implementation Name and Details:

   Laravel Mercure Broadcaster, available at
   https://github.com/mvanduijker/laravel-mercure-broadcaster
   (https://github.com/mvanduijker/laravel-mercure-broadcaster)

   Brief Description:

   Laravel broadcaster for Mercure.  Use the Mercure protocol as
   transport for Laravel Broadcast.

   Level of Maturity:

Dunglas                 Expires 12 February 2027               [Page 55]
Internet-Draft                   Mercure                     August 2026

   Production

   Coverage:

   All the publisher features of the protocol.

   Version compatibility:

   The implementation currently follows the revision 5 of the draft.  An
   open Pull Request adds support for the latest version of the draft.

   Licensing:

   All code is covered under the MIT license.

   Implementation Experience:

   Used in production.

   Contact Information:

   https://github.com/mvanduijker/laravel-mercure-broadcaster
   (https://github.com/mvanduijker/laravel-mercure-broadcaster)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

15.7.  dart_mercure

   Implementation Name and Details:

   dart_mercure, available at https://github.com/wallforfry/dart_mercure
   (https://github.com/wallforfry/dart_mercure)

   Brief Description:

   Publisher and Subscriber library for Dart / Flutter.

   Level of Maturity:

   Stable

   Coverage:

   All the publisher and subscriber features of the protocol.

Dunglas                 Expires 12 February 2027               [Page 56]
Internet-Draft                   Mercure                     August 2026

   Version compatibility:

   The implementation follows the latest draft.

   Licensing:

   All code is covered under the BSD 2-Clause "Simplified" License.

   Contact Information:

   https://github.com/wallforfry/dart_mercure
   (https://github.com/wallforfry/dart_mercure)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

15.8.  pymercure

   Implementation Name and Details:

   pymercure, available at https://github.com/vitorluis/python-mercure
   (https://github.com/vitorluis/python-mercure)

   Brief Description:

   Publisher and Subscriber library for Python.

   Level of Maturity:

   Alpha

   Coverage:

   All the publisher and subscriber features of the protocol.

   Version compatibility:

   The implementation currently follows the revision 5 of the draft.  An
   open Pull Request adds support for the latest version of the draft.

   Licensing:

   All code is covered under the BSD 2-Clause "Simplified" License.

   Contact Information:

Dunglas                 Expires 12 February 2027               [Page 57]
Internet-Draft                   Mercure                     August 2026

   https://github.com/vitorluis/python-mercure
   (https://github.com/vitorluis/python-mercure)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

15.9.  Amphp Mercure Publisher

   Implementation Name and Details:

   Amphp Mercure Publisher, available at https://github.com/eislambey/
   amp-mercure-publisher (https://github.com/eislambey/amp-mercure-
   publisher)

   Brief Description:

   Async Mercure publisher based on Amphp.

   Level of Maturity:

   Stable

   Coverage:

   All the publisher features of the protocol.

   Version compatibility:

   The implementation currently follows the revision 5 of the draft.  An
   open Pull Request adds support for the latest version of the draft.

   Licensing:

   All code is covered under the MIT license.

   Contact Information:

   https://github.com/eislambey/amp-mercure-publisher
   (https://github.com/eislambey/amp-mercure-publisher)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

Dunglas                 Expires 12 February 2027               [Page 58]
Internet-Draft                   Mercure                     August 2026

15.10.  Java Library for Mercure

   Implementation Name and Details:

   Java Library for Mercure, available at https://github.com/vitorluis/
   java-mercure (https://github.com/vitorluis/java-mercure)

   Brief Description:

   Java library to publish messages to a Mercure Hub!

   Level of Maturity:

   Alpha

   Coverage:

   All the publisher features of the protocol.

   Version compatibility:

   The implementation currently follows the revision 5 of the draft.  An
   open Pull Request adds support for the latest version of the draft.

   Licensing:

   All code is covered under the MIT license.

   Contact Information:

   https://github.com/vitorluis/java-mercure
   (https://github.com/vitorluis/java-mercure)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

15.11.  Yii 2 Mercure behavior

   Implementation Name and Details:

   Yii 2 Mercure behavior, available at https://github.com/bizley/
   mercure-behavior (https://github.com/bizley/mercure-behavior)

   Brief Description:

   Yii 2 behavior to automatically publish updates to a Mercure hub.

Dunglas                 Expires 12 February 2027               [Page 59]
Internet-Draft                   Mercure                     August 2026

   Level of Maturity:

   Stable

   Coverage:

   All the publisher features of the protocol.

   Version compatibility:

   The implementation currently follows the revision 5 of the draft.

   Licensing:

   All code is covered under the Apache License 2.0.

   Contact Information:

   https://github.com/bizley/mercure-behavior
   (https://github.com/bizley/mercure-behavior)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

15.12.  GitHub Action for Mercure

   Implementation Name and Details:

   GitHub Action for Mercure, available at
   https://github.com/marketplace/actions/github-action-for-mercure
   (https://github.com/marketplace/actions/github-action-for-mercure)

   Brief Description:

   Send a Mercure update when a GitHub event occurs.

   Level of Maturity:

   Stable

   Coverage:

   All the publisher features of the protocol.

   Version compatibility:

Dunglas                 Expires 12 February 2027               [Page 60]
Internet-Draft                   Mercure                     August 2026

   The implementation currently follows the latest version of the draft.

   Licensing:

   All code is covered under the GNU Public License version 3 or later.

   Contact Information:

   https://github.com/Ilshidur/action-mercure
   (https://github.com/Ilshidur/action-mercure)

   Interoperability:

   Reported compatible with the reference implementation of the Mercure
   Hub.

15.13.  Other Implementations

   Other implementations can be found on GitHub:
   https://github.com/topics/mercure (https://github.com/topics/mercure)

16.  Acknowledgements

   Parts of this specification, especially Section 9 have been adapted
   from the WebSub recommendation [W3C.REC-websub-20180123].  The editor
   wishes to thank all the authors of this specification.

17.  References

17.1.  Normative References

   [FETCH]    The Web Hypertext Application Technology Working Group
              (WHATWG), "Fetch Living Standard (Review Draft, June
              2026)", June 2026,
              <https://fetch.spec.whatwg.org/review-drafts/2026-06/>.

   [HTML]     The Web Hypertext Application Technology Working Group
              (WHATWG), "HTML Living Standard (Review Draft, January
              2026)", January 2026,
              <https://html.spec.whatwg.org/review-drafts/2026-01/>.

   [I-D.ietf-httpbis-rfc6265bis]
              Bingler, S., West, M., and J. Wilander, "Cookies: HTTP
              State Management Mechanism", Work in Progress, Internet-
              Draft, draft-ietf-httpbis-rfc6265bis-22, 1 December 2025,
              <https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-
              rfc6265bis-22>.

Dunglas                 Expires 12 February 2027               [Page 61]
Internet-Draft                   Mercure                     August 2026

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/info/rfc2119>.

   [RFC3629]  Yergeau, F., "UTF-8, a transformation format of ISO
              10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November
              2003, <https://www.rfc-editor.org/info/rfc3629>.

   [RFC3986]  Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
              Resource Identifier (URI): Generic Syntax", STD 66,
              RFC 3986, DOI 10.17487/RFC3986, January 2005,
              <https://www.rfc-editor.org/info/rfc3986>.

   [RFC3987]  Duerst, M. and M. Suignard, "Internationalized Resource
              Identifiers (IRIs)", RFC 3987, DOI 10.17487/RFC3987,
              January 2005, <https://www.rfc-editor.org/info/rfc3987>.

   [RFC5891]  Klensin, J., "Internationalized Domain Names in
              Applications (IDNA): Protocol", RFC 5891,
              DOI 10.17487/RFC5891, August 2010,
              <https://www.rfc-editor.org/info/rfc5891>.

   [RFC6265]  Barth, A., "HTTP State Management Mechanism", RFC 6265,
              DOI 10.17487/RFC6265, April 2011,
              <https://www.rfc-editor.org/info/rfc6265>.

   [RFC6570]  Gregorio, J., Fielding, R., Hadley, M., Nottingham, M.,
              and D. Orchard, "URI Template", RFC 6570,
              DOI 10.17487/RFC6570, March 2012,
              <https://www.rfc-editor.org/info/rfc6570>.

   [RFC6585]  Nottingham, M. and R. Fielding, "Additional HTTP Status
              Codes", RFC 6585, DOI 10.17487/RFC6585, April 2012,
              <https://www.rfc-editor.org/info/rfc6585>.

   [RFC6749]  Hardt, D., Ed., "The OAuth 2.0 Authorization Framework",
              RFC 6749, DOI 10.17487/RFC6749, October 2012,
              <https://www.rfc-editor.org/info/rfc6749>.

   [RFC6750]  Jones, M. and D. Hardt, "The OAuth 2.0 Authorization
              Framework: Bearer Token Usage", RFC 6750,
              DOI 10.17487/RFC6750, October 2012,
              <https://www.rfc-editor.org/info/rfc6750>.

Dunglas                 Expires 12 February 2027               [Page 62]
Internet-Draft                   Mercure                     August 2026

   [RFC6838]  Freed, N., Klensin, J., and T. Hansen, "Media Type
              Specifications and Registration Procedures", BCP 13,
              RFC 6838, DOI 10.17487/RFC6838, January 2013,
              <https://www.rfc-editor.org/info/rfc6838>.

   [RFC7515]  Jones, M., Bradley, J., and N. Sakimura, "JSON Web
              Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May
              2015, <https://www.rfc-editor.org/info/rfc7515>.

   [RFC7516]  Jones, M. and J. Hildebrand, "JSON Web Encryption (JWE)",
              RFC 7516, DOI 10.17487/RFC7516, May 2015,
              <https://www.rfc-editor.org/info/rfc7516>.

   [RFC7517]  Jones, M., "JSON Web Key (JWK)", RFC 7517,
              DOI 10.17487/RFC7517, May 2015,
              <https://www.rfc-editor.org/info/rfc7517>.

   [RFC7519]  Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token
              (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015,
              <https://www.rfc-editor.org/info/rfc7519>.

   [RFC8126]  Cotton, M., Leiba, B., and T. Narten, "Guidelines for
              Writing an IANA Considerations Section in RFCs", BCP 26,
              RFC 8126, DOI 10.17487/RFC8126, June 2017,
              <https://www.rfc-editor.org/info/rfc8126>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/info/rfc8174>.

   [RFC8259]  Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
              Interchange Format", STD 90, RFC 8259,
              DOI 10.17487/RFC8259, December 2017,
              <https://www.rfc-editor.org/info/rfc8259>.

   [RFC8288]  Nottingham, M., "Web Linking", RFC 8288,
              DOI 10.17487/RFC8288, October 2017,
              <https://www.rfc-editor.org/info/rfc8288>.

   [RFC8414]  Jones, M., Sakimura, N., and J. Bradley, "OAuth 2.0
              Authorization Server Metadata", RFC 8414,
              DOI 10.17487/RFC8414, June 2018,
              <https://www.rfc-editor.org/info/rfc8414>.

   [RFC8615]  Nottingham, M., "Well-Known Uniform Resource Identifiers
              (URIs)", RFC 8615, DOI 10.17487/RFC8615, May 2019,
              <https://www.rfc-editor.org/info/rfc8615>.

Dunglas                 Expires 12 February 2027               [Page 63]
Internet-Draft                   Mercure                     August 2026

   [RFC8725]  Sheffer, Y., Hardt, D., and M. Jones, "JSON Web Token Best
              Current Practices", BCP 225, RFC 8725,
              DOI 10.17487/RFC8725, February 2020,
              <https://www.rfc-editor.org/info/rfc8725>.

   [RFC9068]  Bertocci, V., "JSON Web Token (JWT) Profile for OAuth 2.0
              Access Tokens", RFC 9068, DOI 10.17487/RFC9068, October
              2021, <https://www.rfc-editor.org/info/rfc9068>.

   [RFC9110]  Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke,
              Ed., "HTTP Semantics", STD 97, RFC 9110,
              DOI 10.17487/RFC9110, June 2022,
              <https://www.rfc-editor.org/info/rfc9110>.

   [RFC9396]  Lodderstedt, T., Richer, J., and B. Campbell, "OAuth 2.0
              Rich Authorization Requests", RFC 9396,
              DOI 10.17487/RFC9396, May 2023,
              <https://www.rfc-editor.org/info/rfc9396>.

   [RFC9700]  Lodderstedt, T., Bradley, J., Labunets, A., and D. Fett,
              "Best Current Practice for OAuth 2.0 Security", BCP 240,
              RFC 9700, DOI 10.17487/RFC9700, January 2025,
              <https://www.rfc-editor.org/info/rfc9700>.

   [RFC9728]  Jones, M.B., Hunt, P., and A. Parecki, "OAuth 2.0
              Protected Resource Metadata", RFC 9728,
              DOI 10.17487/RFC9728, April 2025,
              <https://www.rfc-editor.org/info/rfc9728>.

   [UNICODE]  The Unicode Consortium, "The Unicode Standard, Version
              17.0.0", September 2025,
              <https://www.unicode.org/versions/Unicode17.0.0/>.

   [URL]      The Web Hypertext Application Technology Working Group
              (WHATWG), "URL Living Standard (Review Draft, February
              2026)", February 2026,
              <https://url.spec.whatwg.org/review-drafts/2026-02/>.

   [urlpattern]
              The Web Hypertext Application Technology Working Group
              (WHATWG), "URL Pattern Living Standard (Review Draft,
              September 2025)", September 2025,
              <https://urlpattern.spec.whatwg.org/review-
              drafts/2025-09/>.

17.2.  Informative References

Dunglas                 Expires 12 February 2027               [Page 64]
Internet-Draft                   Mercure                     August 2026

   [DID]      World Wide Web Consortium (W3C), "Decentralized
              Identifiers (DIDs) v1.0", 2022,
              <https://www.w3.org/TR/did-core/>.

   [EventSourcing]
              Fowler, M., "Event Sourcing", December 2005,
              <https://martinfowler.com/eaaDev/EventSourcing.html>.

   [OWASP-CSRF]
              OWASP Foundation, "Cross Site Request Forgery (CSRF)",
              2024, <https://owasp.org/www-community/attacks/csrf>.

   [OWASP-CSRF-Prevention]
              OWASP Foundation, "Cross-Site Request Forgery Prevention
              Cheat Sheet", 2024,
              <https://cheatsheetseries.owasp.org/cheatsheets/Cross-
              Site_Request_Forgery_Prevention_Cheat_Sheet.html>.

   [OWASP-XSS]
              OWASP Foundation, "Cross Site Scripting (XSS)", 2024,
              <https://owasp.org/www-community/attacks/xss/>.

   [RFC10008] Reschke, J., Snell, J.M., and M. Bishop, "The HTTP QUERY
              Method", RFC 10008, DOI 10.17487/RFC10008, June 2026,
              <https://www.rfc-editor.org/info/rfc10008>.

   [RFC4287]  Nottingham, M., Ed. and R. Sayre, Ed., "The Atom
              Syndication Format", RFC 4287, DOI 10.17487/RFC4287,
              December 2005, <https://www.rfc-editor.org/info/rfc4287>.

   [RFC6902]  Bryan, P., Ed. and M. Nottingham, Ed., "JavaScript Object
              Notation (JSON) Patch", RFC 6902, DOI 10.17487/RFC6902,
              April 2013, <https://www.rfc-editor.org/info/rfc6902>.

   [RFC6973]  Cooper, A., Tschofenig, H., Aboba, B., Peterson, J.,
              Morris, J., Hansen, M., and R. Smith, "Privacy
              Considerations for Internet Protocols", RFC 6973,
              DOI 10.17487/RFC6973, July 2013,
              <https://www.rfc-editor.org/info/rfc6973>.

   [RFC7396]  Hoffman, P. and J. Snell, "JSON Merge Patch", RFC 7396,
              DOI 10.17487/RFC7396, October 2014,
              <https://www.rfc-editor.org/info/rfc7396>.

   [RFC7942]  Sheffer, Y. and A. Farrel, "Improving Awareness of Running
              Code: The Implementation Status Section", BCP 205,
              RFC 7942, DOI 10.17487/RFC7942, July 2016,
              <https://www.rfc-editor.org/info/rfc7942>.

Dunglas                 Expires 12 February 2027               [Page 65]
Internet-Draft                   Mercure                     August 2026

   [RFC8017]  Moriarty, K., Ed., Kaliski, B., Jonsson, J., and A. Rusch,
              "PKCS #1: RSA Cryptography Specifications Version 2.2",
              RFC 8017, DOI 10.17487/RFC8017, November 2016,
              <https://www.rfc-editor.org/info/rfc8017>.

   [RFC9205]  Nottingham, M., "Building Protocols with HTTP", BCP 56,
              RFC 9205, DOI 10.17487/RFC9205, June 2022,
              <https://www.rfc-editor.org/info/rfc9205>.

   [RFC9449]  Fett, D., Campbell, B., Bradley, J., Lodderstedt, T.,
              Jones, M., and D. Waite, "OAuth 2.0 Demonstrating Proof of
              Possession (DPoP)", RFC 9449, DOI 10.17487/RFC9449,
              September 2023, <https://www.rfc-editor.org/info/rfc9449>.

   [RFC9457]  Nottingham, M., Wilde, E., and S. Dalal, "Problem Details
              for HTTP APIs", RFC 9457, DOI 10.17487/RFC9457, July 2023,
              <https://www.rfc-editor.org/info/rfc9457>.

   [RFC9562]  Davis, K., Peabody, B., and P. Leach, "Universally Unique
              IDentifiers (UUIDs)", RFC 9562, DOI 10.17487/RFC9562, May
              2024, <https://www.rfc-editor.org/info/rfc9562>.

   [W3C.REC-activitypub-20180123]
              Lemmer-Webber, C., Ed. and J. Tallon, Ed., "ActivityPub",
              W3C REC REC-activitypub-20180123, W3C REC-activitypub-
              20180123, 23 January 2018,
              <https://www.w3.org/TR/2018/REC-activitypub-20180123/>.

   [W3C.REC-json-ld11-20200716]
              Longley, D., Ed., Kellogg, G., Ed., and P. Champin, Ed.,
              "JSON-LD 1.1", W3C REC REC-json-ld11-20200716, W3C REC-
              json-ld11-20200716, 16 July 2020,
              <https://www.w3.org/TR/2020/REC-json-ld11-20200716/>.

   [W3C.REC-websub-20180123]
              Parecki, A., Ed. and J. Genestoux, Ed., "WebSub", W3C REC 
              REC-websub-20180123, W3C REC-websub-20180123, 23 January
              2018, <https://www.w3.org/TR/2018/REC-websub-20180123/>.

   [W3C.REC-xml-20081126]
              Maler, E., Ed., Yergeau, F., Ed., Paoli, J., Ed.,
              Sperberg-McQueen, M., Ed., and T. Bray, Ed., "Extensible
              Markup Language (XML) 1.0 (Fifth Edition)", W3C REC REC-
              xml-20081126, W3C REC-xml-20081126, 26 November 2008,
              <https://www.w3.org/TR/2008/REC-xml-20081126/>.

Dunglas                 Expires 12 February 2027               [Page 66]
Internet-Draft                   Mercure                     August 2026

   [re2]      Google, "RE2: a principled approach to regular expression
              matching", 2024,
              <https://github.com/google/re2/wiki/Syntax>.

   [streams]  The Web Hypertext Application Technology Working Group
              (WHATWG), "Streams Living Standard", 2026,
              <https://streams.spec.whatwg.org/>.

   [xhr]      The Web Hypertext Application Technology Working Group
              (WHATWG), "XMLHttpRequest Living Standard (Review Draft,
              August 2025)", August 2025,
              <https://xhr.spec.whatwg.org/review-drafts/2025-08/>.

Appendix A.  Changes from Pre-Standardization Deployments

   This appendix is non-normative.  It summarizes the wire-level
   differences between this specification and the pre-standardization
   revisions of Mercure still deployed in the wild, to help implementers
   migrate.  It complements the version-detection note in Section 9: a
   hub that publishes no protected resource metadata (see Section 9.2)
   likely implements one of these earlier revisions.

Dunglas                 Expires 12 February 2027               [Page 67]
Internet-Draft                   Mercure                     August 2026

   +=============+====================+================================+
   |Aspect       |Pre-standardization | This specification             |
   +=============+====================+================================+
   |Authorization|mercure JWT claim   | RFC 9396                       |
   |grant        |with publish and    | authorization_details entry    |
   |             |subscribe arrays of | with type                      |
   |             |topic selectors     | https://mercure.rocks/         |
   |             |                    | authorization-detail, an       |
   |             |                    | actions array (publish,        |
   |             |                    | subscribe), and a topics       |
   |             |                    | array (see Section 6.4)        |
   +-------------+--------------------+--------------------------------+
   |Token type   |any signed JWT      | JWT access token with typ      |
   |             |                    | at+jwt (see Section 6.3)       |
   +-------------+--------------------+--------------------------------+
   |Topic        |raw topic selectors | exact and urlpattern matcher   |
   |matching     |passed as topic     | types selected with the        |
   |             |query parameters    | match/match_<matcher-type>     |
   |             |                    | query parameters or the        |
   |             |                    | match_type member (see         |
   |             |                    | Section 4)                     |
   +-------------+--------------------+--------------------------------+
   |Reconnection |lastEventID         | last_event_id (see Section 7)  |
   |query        |                    |                                |
   |parameter    |                    |                                |
   +-------------+--------------------+--------------------------------+
   |Reconnection |Last-Event-ID       | Mercure-Last-Event-ID (see     |
   |response     |                    | Section 7)                     |
   |field        |                    |                                |
   +-------------+--------------------+--------------------------------+
   |Authorization|mercureAuthorization| __Secure-mercure_access_token  |
   |cookie       |                    | (see Section 6.1.2)            |
   +-------------+--------------------+--------------------------------+
   |Token in the |authorization query | not accepted; Authorization    |
   |URL          |parameter           | header or cookie (see          |
   |             |                    | Section 6.1)                   |
   +-------------+--------------------+--------------------------------+

                                  Table 4

Author's Address

   Kévin Dunglas (editor)
   Les-Tilleuls.coop
   82 rue Winston Churchill
   59160 Lille
   France
   Email: kevin@les-tilleuls.coop

Dunglas                 Expires 12 February 2027               [Page 68]