<?xml version="1.0" encoding="utf-8"?>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc version="3" ipr="trust200902" docName="draft-dunglas-mercure-08" submissionType="IETF" category="std" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true">

<front>
<title abbrev="Mercure">The Mercure Protocol</title><seriesInfo value="draft-dunglas-mercure-08" stream="IETF" status="standard" name="Internet-Draft"></seriesInfo>
<author role="editor" initials="K." surname="Dunglas" fullname="Kévin Dunglas"><organization abbrev="Les-Tilleuls.coop">Les-Tilleuls.coop</organization><address><postal><street>82 rue Winston Churchill</street>
<city>Lille</city>
<code>59160</code>
<country>France</country>
</postal><email>kevin@les-tilleuls.coop</email>
</address></author><date/>
<area>Web and Internet Transport</area>
<workgroup></workgroup>

<abstract>
<t>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.</t>
<t>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.</t>
</abstract>

</front>

<middle>

<section anchor="introduction"><name>Introduction</name>
<t>Mercure is a protocol for pushing updates of web resources to clients over HTTP. It builds on
Server-Sent Events <xref target="HTML"></xref> for delivery and on JSON Web Signatures
<xref target="RFC7515"></xref> for authorization, so that it can be implemented on top of existing HTTP
infrastructure and consumed natively by web browsers.</t>
<t>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.</t>
<t>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.</t>
<t>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.</t>
</section>

<section anchor="terminology"><name>Terminology</name>
<t>The key words <bcp14>MUST</bcp14>, <bcp14>MUST NOT</bcp14>, <bcp14>REQUIRED</bcp14>, <bcp14>SHALL</bcp14>, <bcp14>SHALL NOT</bcp14>, <bcp14>SHOULD</bcp14>,
<bcp14>SHOULD NOT</bcp14>, <bcp14>RECOMMENDED</bcp14>, <bcp14>NOT RECOMMENDED</bcp14>, <bcp14>MAY</bcp14>, and <bcp14>OPTIONAL</bcp14> in this document
are to be interpreted as described in BCP 14 <xref target="RFC2119"></xref> <xref target="RFC8174"></xref> when, and only when, they
appear in all capitals, as shown here.</t>

<ul spacing="compact">
<li>Topic: The unit to which one can subscribe for changes. The topic is identified by a string
that can be an IRI <xref target="RFC3987"></xref>. Topic strings <bcp14>MUST</bcp14> be valid UTF-8 <xref target="RFC3629"></xref> and
<bcp14>MUST NOT</bcp14> contain C0 (U+0000–U+001F) or C1 (U+0080–U+009F) control characters, U+007F
(DEL), or Unicode format characters (general category <tt>Cf</tt> <xref target="UNICODE"></xref>, 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 <xref target="UNICODE"></xref>; hubs
<bcp14>MAY</bcp14> also reject characters that later Unicode versions assign to general category <tt>Cf</tt>.</li>
<li>Canonical topic: The first <tt>topic</tt> value of an update; the primary identifier of the updated
resource.</li>
<li>Alternate topic: Any <tt>topic</tt> 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
<xref target="publication"></xref>).</li>
<li>Update: The message containing the updated version of the topic. An update can be marked as
private; in that case, it <bcp14>MUST</bcp14> be dispatched only to subscribers allowed to receive it.</li>
<li>Topic matcher: An expression matched against one or more topics,
depending on the matcher type.</li>
<li>Topic matcher type: The kind of a matching expression, which determines how the expression is
interpreted. This document defines two matcher types, <tt>exact</tt> and <tt>urlpattern</tt>; others can be
registered (see <xref target="matcher-types"></xref>).</li>
<li>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 &quot;source&quot;. Typically a site or a web API, but it can also
be a web browser.</li>
<li>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.</li>
<li>Subscription: A topic matcher used by a subscriber to receive updates. A single subscriber can
have several subscriptions by providing several topic matchers.</li>
<li>Hub: A server that handles subscription requests and distributes content to subscribers when
the corresponding topics have been updated. A hub <bcp14>MAY</bcp14> implement its own policies on who
can use it. The hub is an OAuth 2.0 protected resource <xref target="RFC6749"></xref>.</li>
<li>Access token: The credential a client presents to the hub to prove authorization, carried as
a JWT <xref target="RFC7519"></xref> following the JWT access token profile <xref target="RFC9068"></xref>.</li>
<li>Resource identifier: The OAuth 2.0 resource identifier of the hub, used as the audience
(<tt>aud</tt>) of access tokens and advertised through protected resource metadata <xref target="RFC9728"></xref>.</li>
<li>Authorization server: An OAuth 2.0 authorization server <xref target="RFC6749"></xref> that issues access tokens
for the hub. Its use is <bcp14>OPTIONAL</bcp14>; access tokens <bcp14>MAY</bcp14> be self-issued.</li>
<li>Authorization details: The <tt>authorization_details</tt> claim <xref target="RFC9396"></xref> carried in an access
token, expressing which actions a client may perform on which topics.</li>
</ul>
</section>

<section anchor="subscription"><name>Subscription</name>
<t>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 <xref target="discovery"></xref>) following the Server-Sent Events specification
<xref target="HTML"></xref>. Hubs <bcp14>MUST</bcp14> support the <tt>GET</tt> HTTP method, which is the method used by
<tt>EventSource</tt> clients <xref target="HTML"></xref>. Hubs <bcp14>MAY</bcp14> also accept any other safe <xref target="RFC9110"></xref>
method whose semantics allow the topic matcher parameters to be carried in the request
body instead of the query component — notably the <tt>QUERY</tt> method <xref target="RFC10008"></xref> — 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 <bcp14>MUST</bcp14> be
encoded as <tt>application/x-www-form-urlencoded</tt> <xref target="URL"></xref>, and the reserved-namespace rule
and value constraints below apply identically to the body-decoded names and values.</t>
<t>A request carrying such a body <bcp14>MAY</bcp14> also carry parameters in the query component. The hub
<bcp14>MUST</bcp14> 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 <tt>last_event_id</tt> (see <xref target="reconciliation"></xref>) 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. <tt>last_event_id</tt> is single-valued: if it appears in both, the hub <bcp14>MUST</bcp14> use the
value from the query component, and the <tt>Last-Event-ID</tt> HTTP field still takes precedence over
either (see <xref target="reconciliation"></xref>).</t>
<t>The connection <bcp14>SHOULD</bcp14> use HTTP version 2 or higher to leverage multiplexing and other
performance-related features.</t>
<t>The subscriber specifies the topics to receive updates from using topic matcher query
parameters. The parameter name encodes the matcher type: the bare <tt>match</tt> parameter selects the
default <tt>exact</tt> matcher type, and a <tt>match_&lt;matcher-type&gt;</tt> parameter selects the named matcher
type — for example, <tt>match_urlpattern</tt> selects the <tt>urlpattern</tt> matcher type, and <tt>match_exact</tt> is
the explicit spelling of the default. The <tt>&lt;matcher-type&gt;</tt> suffix <bcp14>MUST</bcp14> be the matcher type
name in its canonical form as defined in <xref target="matcher-types"></xref>. A request <bcp14>MAY</bcp14> contain several such
parameters, in any combination. See <xref target="matcher-types"></xref>. 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 <xref target="authorization"></xref>).</t>
<t>This mirrors the topic matcher list of authorization details (see <xref target="topic-matcher-list"></xref>), where
the <tt>match_type</tt> member is optional and defaults to <tt>exact</tt>: omitting it there is equivalent to
using the bare <tt>match</tt> parameter here.</t>
<t>The query component of the subscription URL <bcp14>MUST</bcp14> be parsed into name/value pairs using the
<tt>application/x-www-form-urlencoded</tt> parsing algorithm of <xref target="URL"></xref> (the algorithm implemented by
<tt>URLSearchParams</tt> and used by <tt>EventSource</tt>). 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 <bcp14>MUST</bcp14>
percent-encode any character in a matcher name or value that <tt>application/x-www-form-urlencoded</tt>
serialization would encode — notably <tt>&amp;</tt>, <tt>=</tt>, <tt>+</tt>, <tt>;</tt>, and <tt>%</tt> — as <tt>URLSearchParams</tt>
does; this keeps parsing unambiguous across implementations (some form-urlencoded parsers treat
a raw <tt>;</tt> as a delimiter or reject a stray <tt>%</tt>).</t>
<t>The names of topic matcher query parameters are case-sensitive. A request using a parameter name
in the reserved <tt>match</tt> namespace (a name equal to <tt>match</tt>, or beginning with <tt>match</tt> under an
ASCII case-insensitive comparison) that does not correspond to a matcher type supported by the
hub (see <xref target="matcher-types"></xref>) <bcp14>MUST</bcp14> be rejected with a 400 &quot;Bad Request&quot; HTTP status code. This
deliberately reserves the whole <tt>match</tt> prefix: unrelated query parameters whose names begin
with <tt>match</tt> 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.</t>
<t>The value of each topic matcher query parameter <bcp14>MUST</bcp14> be valid UTF-8 <xref target="RFC3629"></xref> and
<bcp14>MUST NOT</bcp14> contain C0 (U+0000–U+001F) or C1 (U+0080–U+009F) control characters, U+007F, or
Unicode format characters (general category <tt>Cf</tt> <xref target="UNICODE"></xref>).
A parameter value that is not valid for its matcher type (for example, a <tt>match_urlpattern</tt>
value that is not a well-formed URL Pattern) is equally invalid. Requests violating any of these
constraints <bcp14>MUST</bcp14> be rejected with a 400 &quot;Bad Request&quot; HTTP status code.</t>
<t>The subscriber receives updates for all topics matching at least one topic matcher according to
the matcher type rules.</t>
<t>To mitigate resource exhaustion, hubs <bcp14>SHOULD</bcp14> 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 <bcp14>MUST</bcp14> be rejected with a 400 &quot;Bad
Request&quot; HTTP status code. A subscription is created for every topic matcher query parameter
present in the request. Hubs <bcp14>MAY</bcp14> deduplicate subscriptions that have identical matcher type
and pattern. See <xref target="subscription-events"></xref>.</t>
<t>Because subscription connections are long-lived, hubs <bcp14>SHOULD</bcp14> 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 <bcp14>MAY</bcp14> reject further
connection attempts with a 429 &quot;Too Many Requests&quot; HTTP status code <xref target="RFC6585"></xref>.</t>
<t>The <tt>EventSource</tt> JavaScript interface <xref target="HTML"></xref> <bcp14>MAY</bcp14> be used to establish
the connection. Any other appropriate mechanism, including but not limited to readable streams
<xref target="streams"></xref> and XMLHttpRequest <xref target="xhr"></xref> (used by popular polyfills),
<bcp14>MAY</bcp14> also be used.</t>
<t>Web browsers enforce the CORS protocol <xref target="FETCH"></xref> on cross-origin <tt>EventSource</tt> connections.
Hubs serving browser-based subscribers on other origins <bcp14>MUST</bcp14> send the appropriate CORS
response headers. When the connection carries credentials (such as the cookie defined in
<xref target="cookie"></xref>), the <tt>Access-Control-Allow-Origin</tt> response header <bcp14>MUST NOT</bcp14> be the <tt>*</tt> wildcard
and <bcp14>MUST NOT</bcp14> be reflected from arbitrary request origins: it <bcp14>MUST</bcp14> be restricted to an
explicit allowlist of trusted origins, and the hub <bcp14>MUST</bcp14> also send
<tt>Access-Control-Allow-Credentials: true</tt>. Reflecting arbitrary origins on a credentialed
endpoint would allow any site visited by the subscriber to read updates using the
subscriber's cookie.</t>
<t>The hub sends updates to the subscriber for topics matching the provided topic matchers.</t>
<t>If an update is marked as <tt>private</tt>, the hub <bcp14>MUST NOT</bcp14> dispatch it to subscribers not authorized
to receive it. See <xref target="authorization"></xref>.</t>
<t>The hub <bcp14>MUST</bcp14> send these updates as <tt>text/event-stream</tt>-compliant events
<xref target="HTML"></xref>.</t>
<t>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 <bcp14>SHOULD</bcp14> send an SSE comment line (a line starting with
<tt>:</tt> <xref target="HTML"></xref>) as a keep-alive, and deployments <bcp14>SHOULD</bcp14> configure intermediaries not to
buffer event streams.</t>
<t>The <tt>data</tt> property <bcp14>MUST</bcp14> contain the topic's new version. It <bcp14>MAY</bcp14> be the full resource or
a partial update in formats such as JSON Patch <xref target="RFC6902"></xref> or JSON Merge Patch <xref target="RFC7396"></xref>.</t>
<t>All other properties defined in the Server-Sent Events specification <bcp14>MAY</bcp14> be used and <bcp14>MUST</bcp14>
be supported by hubs.</t>
<t>The resource <bcp14>MAY</bcp14> be represented in a format with hypermedia capabilities such as
JSON-LD <xref target="W3C.REC-json-ld11-20200716"></xref>, Atom <xref target="RFC4287"></xref>, XML <xref target="W3C.REC-xml-20081126"></xref> or HTML
<xref target="HTML"></xref>.</t>
<t>Web Linking <xref target="RFC8288"></xref> <bcp14>MAY</bcp14> 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 <bcp14>SHOULD</bcp14> contain a
<tt>link</tt> element with a <tt>self</tt> relation that holds the IRI of the resource. When using JSON-LD,
the document <bcp14>SHOULD</bcp14> contain an <tt>@id</tt> property holding the IRI of the resource.</t>
<t>Example:</t>

<sourcecode type="javascript"><![CDATA[// 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);
};
]]></sourcecode>
<t>The hub <bcp14>MAY</bcp14> apply extra authorization rules not defined in this specification. See
<xref target="authorization"></xref>.</t>
</section>

<section anchor="matcher-types"><name>Matcher Types</name>
<t>A topic matcher is an expression matched against topics; its matcher type determines how the
expression is interpreted. This document defines two matcher types, <tt>exact</tt> and <tt>urlpattern</tt>.
Hubs <bcp14>MUST</bcp14> support both.</t>
<t>Additional matcher types can be defined by other specifications and registered in the &quot;Mercure
Topic Matcher Types&quot; registry (see <xref target="iana-considerations"></xref>). Hubs <bcp14>MAY</bcp14> support registered
additional matcher types and <bcp14>SHOULD</bcp14> advertise the complete set of matcher types they
support with the <tt>mercure_matcher_types_supported</tt> metadata member (see
<xref target="protected-resource-metadata"></xref>). Requests and tokens using a matcher type the hub does not
support are rejected as defined in <xref target="subscription"></xref> and <xref target="topic-matcher-list"></xref>: matcher
evaluation determines both routing and authorization, so a matcher the hub cannot interpret
must fail loudly rather than be skipped.</t>
<t>The matcher value <tt>*</tt> 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 <tt>match_type</tt> is supplied or defaulted (see <xref target="topic-matcher-list"></xref>). As a consequence, a topic
whose value is exactly <tt>*</tt> is not addressable: no matcher can select that single topic without also
selecting every other. Publishing a topic whose value is exactly <tt>*</tt> is therefore rejected (see
<xref target="publication"></xref>). This mirrors the reserved wildcard characters of other publish-subscribe
systems.</t>

<section anchor="exact-matching"><name>Exact Matching</name>
<t>The hub <bcp14>MUST</bcp14> support exact matching. With this matcher type, the hub <bcp14>MUST</bcp14> perform an
exact, case-sensitive, byte-for-byte comparison between the topic and the matcher. The hub
<bcp14>MUST NOT</bcp14> resolve relative values against the hub's URL or any other base, and <bcp14>MUST NOT</bcp14>
perform Unicode or IRI normalization.</t>
<t>Note: Because comparison is performed on raw bytes, publishers and subscribers <bcp14>SHOULD</bcp14>
normalize topic strings to a canonical form before publication or subscription. Recommended
canonicalizations are Unicode NFC <xref target="UNICODE"></xref> and, for IRIs, IDNA-canonical hosts <xref target="RFC5891"></xref>
and percent-encoding normalization <xref target="RFC3986"></xref>. Otherwise, visually identical topics will be
treated as distinct, and homograph attacks (see <xref target="security-considerations"></xref>) become possible.</t>
<t>The matcher type name is <tt>exact</tt>. It is the default matcher type: the corresponding subscribe
query parameter is the bare <tt>match</tt> (or, explicitly, <tt>match_exact</tt>), and it is the default
<tt>match_type</tt> value in authorization details (see <xref target="topic-matcher-list"></xref>).</t>
</section>

<section anchor="url-pattern"><name>URL Pattern</name>
<t>The hub <bcp14>MUST</bcp14> support using URL patterns <xref target="urlpattern"></xref> as matchers.</t>
<t>URL patterns <bcp14>MAY</bcp14> be absolute (e.g., <tt>https://example.com/books/:id</tt>) or relative
(e.g., <tt>/.well-known/mercure/subscriptions/exact/:topic/:subscriber</tt>). When evaluating
a relative pattern or a relative topic, the hub <bcp14>MUST</bcp14> 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 <xref target="subscription-events"></xref>).</t>
<t>URL patterns are evaluated per the URL Pattern Living Standard <xref target="urlpattern"></xref>; hubs <bcp14>MUST NOT</bcp14>
enable the <tt>ignoreCase</tt> option. Host components remain case-insensitive as defined by URL
canonicalization <xref target="RFC3986"></xref>; all other components are case-sensitive.</t>
<t>A topic that cannot be parsed as a URL reference against the hub's URL cannot be matched by a
URL pattern: hubs <bcp14>MUST</bcp14> treat its evaluation against any URL pattern as not matching.</t>
<t>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 <bcp14>MUST</bcp14> either use a regular expression
engine that guarantees linear-time matching (such as RE2 <xref target="re2"></xref>) or enforce an
implementation-defined evaluation cost or time limit. When such a limit is reached, the pattern
<bcp14>MUST</bcp14> be treated as not matching and the evaluation <bcp14>MUST</bcp14> be aborted.</t>
<t>URL patterns whose <tt>protocol</tt> component is a wildcard or capture group can match <tt>data:</tt>,
<tt>javascript:</tt>, <tt>file:</tt>, and other potentially dangerous URI schemes. Topic strings are opaque
identifiers within this protocol; subscribers <bcp14>MUST NOT</bcp14> dereference them as URLs without
validating the scheme against an allowlist appropriate for the subscriber's environment.</t>
<t>The matcher type name is <tt>urlpattern</tt>. The corresponding subscribe query parameter is
<tt>match_urlpattern</tt>, and the corresponding <tt>match_type</tt> value in authorization details (see
<xref target="topic-matcher-list"></xref>) is <tt>urlpattern</tt>.</t>
</section>

<section anchor="summary-of-matcher-types"><name>Summary of Matcher Types</name>
<table>
<thead>
<tr>
<th>Matcher Type</th>
<th>Subscribe Query Parameter</th>
<th><tt>match_type</tt></th>
<th>Requirement</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>exact</tt></td>
<td><tt>match</tt> (or <tt>match_exact</tt>)</td>
<td><tt>exact</tt></td>
<td><bcp14>MUST</bcp14></td>
</tr>

<tr>
<td><tt>urlpattern</tt></td>
<td><tt>match_urlpattern</tt></td>
<td><tt>urlpattern</tt></td>
<td><bcp14>MUST</bcp14></td>
</tr>
</tbody>
</table><t>This table lists the matcher types defined by this document; the &quot;Mercure Topic Matcher Types&quot;
registry (see <xref target="iana-considerations"></xref>) records additional registered types.</t>
</section>
</section>

<section anchor="publication"><name>Publication</name>
<t>The publisher sends updates by issuing <tt>POST</tt> HTTPS requests to the hub URL. When it receives an
update, the hub dispatches it to subscribers using the established Server-Sent Events connections.</t>
<t>The hub <bcp14>MAY</bcp14> also dispatch the update using other protocols such as WebSub
<xref target="W3C.REC-websub-20180123"></xref> or ActivityPub <xref target="W3C.REC-activitypub-20180123"></xref>.</t>
<t>An application <bcp14>MAY</bcp14> deliver events directly to subscribers without an external hub. In that
case, the publish endpoint described in this section is not required.</t>
<t>The request <bcp14>MUST</bcp14> be encoded using the <tt>application/x-www-form-urlencoded</tt> format
<xref target="URL"></xref>. Field names and values <bcp14>MUST</bcp14> be UTF-8 <xref target="RFC3629"></xref>. The request
<bcp14>MUST</bcp14> contain at least one <tt>topic</tt> field; all other fields defined below are optional:</t>

<ul spacing="compact">
<li><tt>topic</tt> (required): The identifier of an updated topic. It is <bcp14>RECOMMENDED</bcp14> to use an IRI
as identifier. This field <bcp14>MAY</bcp14> 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 <xref target="terminology"></xref>). 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 <xref target="subscribers"></xref>) — the audience of a private update is
therefore the union of the audiences of all its topics (see
<xref target="private-update-audience"></xref>). Each topic value <bcp14>MUST</bcp14> conform to the constraints defined in
<xref target="terminology"></xref>. Every topic value (canonical or alternate) <bcp14>MUST NOT</bcp14> address the reserved
hub namespace. To test this, each topic <bcp14>MUST</bcp14> be resolved against the hub's URL (see
<xref target="discovery"></xref>) using the URL parser of <xref target="URL"></xref> — the same algorithm and canonicalization used
for URL Pattern matching (see <xref target="url-pattern"></xref>). 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 <tt>/</tt> — for the default hub URL (see <xref target="discovery"></xref>), <tt>/.well-known/mercure</tt> and
<tt>/.well-known/mercure/...</tt> — regardless of scheme or authority. Before this comparison, the path
<bcp14>MUST</bcp14> have its dot-segments removed and its percent-encoded octets that correspond to
unreserved characters decoded <xref target="RFC3986"></xref>; otherwise, variants such as
<tt>/.well-known/%6Dercure/...</tt> or <tt>/.well-known/mercure/../mercure/...</tt> 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
<xref target="subscription-events"></xref>). Every topic value <bcp14>MUST NOT</bcp14> be exactly <tt>*</tt>, which is reserved for the
wildcard matcher (see <xref target="matcher-types"></xref>) and would otherwise not be addressable. Because both rules
reject the request regardless of the presented access token, hubs <bcp14>MUST</bcp14> reject publish requests
violating them, for any topic value, with a 400 &quot;Bad Request&quot; 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
<tt>https://hub.example.com/.well-known/mercure/subscriptions/...</tt>.</li>
<li><tt>data</tt> (optional): the content of the new version of this topic. The value <bcp14>MUST</bcp14> be
valid UTF-8 <xref target="RFC3629"></xref>. When dispatching the update, the hub <bcp14>MUST</bcp14> serialize the value as
one SSE <tt>data:</tt> field per line, splitting on CR, LF, or CRLF, per the Server-Sent Events
serialization rules <xref target="HTML"></xref>. 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) <bcp14>SHOULD</bcp14> encode the
value (for example, with base64) before publication. The value is opaque to the hub: publishers
and subscribers <bcp14>MAY</bcp14> agree on any format. Only the hub-generated documents described in
<xref target="subscription-events"></xref> have a format defined by this specification.</li>
<li><tt>private</tt> (optional): if this field is present, the update <bcp14>MUST NOT</bcp14> be dispatched to
subscribers not authorized to receive it. See <xref target="authorization"></xref>. The presence of the field
name marks the update as private regardless of its value, whether or not a value is
supplied; hubs <bcp14>MUST NOT</bcp14> interpret the field's value to determine privacy. It is
<bcp14>RECOMMENDED</bcp14> to set the value to <tt>on</tt> for interoperability, but it <bcp14>MAY</bcp14> contain any
value, including an empty string.</li>
<li><tt>id</tt> (optional): the topic's revision identifier; used as the SSE <tt>id</tt> property.
The provided ID <bcp14>MUST NOT</bcp14> start with the <tt>#</tt> character, <bcp14>MUST NOT</bcp14> be the reserved
value <tt>earliest</tt> (see <xref target="reconciliation"></xref>), and <bcp14>MUST NOT</bcp14> contain control characters
(C0 (U+0000–U+001F), U+007F, or C1 (U+0080–U+009F)) or Unicode format characters (general
category <tt>Cf</tt> <xref target="UNICODE"></xref>) — the same constraint as topics (see <xref target="terminology"></xref>), since the
ID also travels in the <tt>Last-Event-ID</tt> HTTP field.
The provided ID <bcp14>MAY</bcp14> be a valid IRI. If omitted, the
hub <bcp14>MUST</bcp14> generate either a valid IRI <xref target="RFC3987"></xref> or a relative reference consisting of a
fragment (starting with <tt>#</tt>). A UUID <xref target="RFC9562"></xref> or a DID <xref target="DID"></xref> <bcp14>MAY</bcp14> 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 <bcp14>SHOULD</bcp14> 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
<xref target="reconciliation"></xref>), so a reused ID leaves ambiguous which update a subsequent
<tt>Last-Event-ID</tt> refers to. The hub <bcp14>MAY</bcp14> ignore the client-supplied ID and generate its
own. The hub <bcp14>MUST</bcp14> reject client-supplied IDs violating the character constraints above
with a 400 HTTP status code.</li>
<li><tt>type</tt> (optional): the SSE <tt>event</tt> property (a specific event type). The value <bcp14>MUST NOT</bcp14>
contain control characters (C0 (U+0000–U+001F), U+007F, or C1 (U+0080–U+009F)) or Unicode
format characters (general category <tt>Cf</tt> <xref target="UNICODE"></xref>); hubs <bcp14>MUST</bcp14> reject violating values
with a 400 HTTP status code. The value <tt>mercure</tt> is reserved for updates the hub generates
itself (see <xref target="subscription-events"></xref>); hubs <bcp14>MUST</bcp14> reject a publish request whose <tt>type</tt> is
<tt>mercure</tt> with a 400 HTTP status code, so that a publisher cannot forge such events.</li>
<li><tt>retry</tt> (optional): the SSE <tt>retry</tt> property (the reconnection time). The value <bcp14>MUST</bcp14>
consist solely of ASCII digits (U+0030–U+0039); hubs <bcp14>MUST</bcp14> reject violating values with
a 400 HTTP status code.</li>
</ul>
<t>To allow future extensions, hubs <bcp14>MUST</bcp14> ignore fields they do not recognize.</t>
<t>On success, the hub <bcp14>MUST</bcp14> return a 200 (OK) HTTP status code, and the response body <bcp14>MUST</bcp14>
be the <tt>id</tt> generated by the hub for the update, served with the <tt>text/plain</tt> media type and the
UTF-8 charset (<tt>Content-Type: text/plain; charset=utf-8</tt>). Other 2xx status codes are not
appropriate: some cannot carry content at all (204, 205) <xref target="RFC9110"></xref>, and 201
(Created) would misrepresent an ephemeral message as a resource
retrievable at a dereferenceable URL — the <tt>id</tt> is an event cursor (see <xref target="reconciliation"></xref>),
not a <tt>Location</tt>. The publisher <bcp14>MUST</bcp14> be authorized to publish updates; see <xref target="authorization"></xref>.</t>
<t>Hubs <bcp14>SHOULD</bcp14> apply implementation-defined maximums to the size of the request body, to
the length of individual fields, and to the number of <tt>topic</tt> fields per request. Requests
exceeding any such limit <bcp14>MUST</bcp14> be rejected with a 413 &quot;Content Too Large&quot; HTTP status code.</t>
<t>Example:</t>

<sourcecode type="http"><![CDATA[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
]]></sourcecode>
</section>

<section anchor="authorization"><name>Authorization</name>
<t>The hub is an OAuth 2.0 protected resource <xref target="RFC6749"></xref>. To prove that they are authorized,
publishers <bcp14>MUST</bcp14> present an access token to the hub (except in the closed-network
deployments described in the note below), and subscribers <bcp14>MUST</bcp14> present
an access token to receive updates marked as private. Hubs <bcp14>MAY</bcp14> accept unauthenticated
subscribers; such subscribers receive only updates that are not marked as private. Hubs
<bcp14>MAY</bcp14> instead require all subscribers to present an access token, according to their own
policy. The access token
<bcp14>MUST</bcp14> be a JWT <xref target="RFC7519"></xref> structured as a JWT access token <xref target="RFC9068"></xref> — in particular
using the <tt>at+jwt</tt> media type — carried as a JWS <xref target="RFC7515"></xref> in compact serialization, and
<bcp14>MUST</bcp14> be validated as described in <xref target="token-validation"></xref>. Every token, including a
self-issued one, <bcp14>MUST</bcp14> carry the claims <xref target="RFC9068"></xref> requires issuers to populate (<tt>iss</tt>,
<tt>exp</tt>, <tt>aud</tt>, <tt>sub</tt>, <tt>client_id</tt>, <tt>iat</tt>, and <tt>jti</tt>), so that any <xref target="RFC9068"></xref> validator can
process it without Mercure-specific exceptions. The token
<bcp14>SHOULD</bcp14> be short-lived, especially when the subscriber is a web browser.</t>
<t>Access tokens <bcp14>MAY</bcp14> 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 <bcp14>MUST</bcp14> use a stable issuer identifier (for example, its own URL) as the
<tt>iss</tt> value, bound out of band to its signing key(s) in the hub's configuration (see
<xref target="token-validation"></xref>). When an authorization server is used, the hub
<bcp14>MAY</bcp14> advertise it through protected resource metadata (see <xref target="discovery"></xref>). Different keys
<bcp14>SHOULD</bcp14> be used to sign subscribers' and publishers' tokens so that compromise of one role
does not entail compromise of the other.</t>
<t>For example, a self-issued subscriber token is a JWS with the protected header
<tt>{&quot;alg&quot;: &quot;ES256&quot;, &quot;typ&quot;: &quot;at+jwt&quot;}</tt> and the following claims (see <xref target="token-validation"></xref> for
how a hub validates them):</t>

<sourcecode type="json"><![CDATA[{
  "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"}]
    }
  ]
}
]]></sourcecode>
<t>Authorization is expressed with the <tt>authorization_details</tt> claim <xref target="RFC9396"></xref>; see
<xref target="authorization-details"></xref>. Routing (which topics a subscriber listens to, via the query
parameters of <xref target="subscription"></xref>) is independent of authorization (what a token permits): the query
parameters never grant access to private updates.</t>
<t>Note: Hubs <bcp14>MAY</bcp14> 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 <bcp14>MUST NOT</bcp14> be reachable from networks containing
untrusted clients. The remainder of this section assumes token-based authorization is in use
for publication.</t>

<section anchor="presenting-the-access-token"><name>Presenting the Access Token</name>
<t>Two mechanisms are defined to present the access token to the hub, following the OAuth 2.0
Bearer Token Usage specification <xref target="RFC6750"></xref> where applicable:</t>

<ul spacing="compact">
<li>an <tt>Authorization</tt> HTTP header with the <tt>Bearer</tt> scheme <xref target="RFC6750"></xref>,</li>
<li>a cookie (a Mercure extension for web browsers, see below).</li>
</ul>
<t>The <tt>access_token</tt> URI query parameter of <xref target="RFC6750"></xref> section 2.3 is not part of this
specification: the OAuth 2.0 Security Best Current Practice <xref target="RFC9700"></xref> forbids clients from
passing access tokens in the URI query string, and hubs implementing this specification do not
accept it.</t>
<t>When either mechanism is used, the connection <bcp14>MUST</bcp14> use an encryption layer such as
HTTPS.</t>
<t>Per <xref target="RFC6750"></xref>, clients <bcp14>MUST NOT</bcp14> use more than one mechanism to transmit the token in a
single request. <xref target="RFC6750"></xref> treats a request that nevertheless carries more than one mechanism as
an <tt>invalid_request</tt>; this specification deviates from that handling in one narrow case: when
an <tt>Authorization</tt> HTTP header is present, its token <bcp14>MUST</bcp14> be used and any cookie <bcp14>MUST</bcp14>
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 <tt>Authorization</tt> header is not treated as a client protocol violation. When no
<tt>Authorization</tt> header is present, the cookie, if any, <bcp14>MUST</bcp14> be used.</t>

<section anchor="authorization-http-header"><name>Authorization HTTP Header</name>
<t>Clients <bcp14>SHOULD</bcp14> use an <tt>Authorization</tt>
HTTP header. This header <bcp14>MUST</bcp14> contain the string <tt>Bearer</tt> followed by a space character and
by the access token, as defined in <xref target="RFC6750"></xref>. As with every HTTP authentication scheme, the
scheme name is matched case-insensitively <xref target="RFC9110"></xref>.</t>
<t>In a web browser, the <tt>fetch()</tt> API <xref target="FETCH"></xref> can set this header and consume the
Server-Sent Events stream through the response body's readable stream <xref target="streams"></xref>; this is the
<bcp14>RECOMMENDED</bcp14> mechanism when the 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.</t>
</section>

<section anchor="cookie"><name>Cookie</name>
<t>Per the <tt>EventSource</tt> specification <xref target="HTML"></xref>, web browsers cannot set
custom HTTP headers on such connections, and the connections can only be established using the
<tt>GET</tt> HTTP method. However, cookies are supported and can be included even in cross-origin
requests when the CORS credentials mode is enabled through the <tt>withCredentials</tt> attribute of
<tt>EventSource</tt> <xref target="HTML"></xref>.
This cookie mechanism is a Mercure-specific extension to <xref target="RFC6750"></xref>; hubs that support it
<bcp14>SHOULD</bcp14> advertise it in their protected resource metadata (see <xref target="discovery"></xref>).</t>
<t>If the publisher or the subscriber is a web browser, it <bcp14>SHOULD</bcp14>, whenever possible, send a
cookie containing the access token when connecting to the hub. It is <bcp14>RECOMMENDED</bcp14> to name the
cookie <tt>__Secure-mercure_access_token</tt>: the <tt>__Secure-</tt> name prefix
<xref target="I-D.ietf-httpbis-rfc6265bis"></xref> makes user agents refuse the cookie over insecure transport
while — unlike the <tt>__Host-</tt> prefix — remaining compatible with the <tt>Domain</tt> attribute used
below. A different name <bcp14>MAY</bcp14> be used to prevent conflicts when several hubs share the same
domain.</t>
<t>The cookie <bcp14>SHOULD</bcp14> be set during discovery (see <xref target="discovery"></xref>) to improve overall security.
Consequently, if the cookie is set during discovery, the publisher and the hub <bcp14>MUST</bcp14> share
the same registrable domain (eTLD+1). The <tt>Domain</tt> attribute <bcp14>MAY</bcp14> be used to allow the
publisher and the hub to use different subdomains of that registrable domain. See <xref target="discovery"></xref>.</t>
<t>The cookie <bcp14>MUST</bcp14> have the <tt>Secure</tt> and <tt>HttpOnly</tt> attributes set <xref target="RFC6265"></xref>. The cookie
<bcp14>SHOULD</bcp14> also have <tt>SameSite=Strict</tt> <xref target="I-D.ietf-httpbis-rfc6265bis"></xref>: because the publisher
and the hub share the same registrable domain (see above), requests between them are same-site
and <tt>SameSite=Strict</tt> does not impede them. <tt>SameSite=Lax</tt> <bcp14>MAY</bcp14> be used when the deployment
requires the cookie on qualifying top-level navigations arriving from other sites; <tt>Lax</tt> does
not release the cookie on cross-site subresource requests such as <tt>EventSource</tt> connections
<xref target="I-D.ietf-httpbis-rfc6265bis"></xref>, so it does not enable cross-site hub access — a genuinely
cross-site deployment would require <tt>SameSite=None; Secure</tt> and remains subject to user-agent
third-party-cookie policies, and <bcp14>SHOULD</bcp14> use the <tt>Authorization</tt> header via <tt>fetch()</tt>
instead (see <xref target="authorization-http-header"></xref>).
The cookie's <tt>Path</tt> attribute <bcp14>SHOULD</bcp14> be set to the path of the hub's subscription URL. See
<xref target="security-considerations"></xref>.</t>
</section>
</section>

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

<ul spacing="compact">
<li>If no access token is presented and the requested operation requires one (see above), the
hub <bcp14>MUST</bcp14> return a 401 &quot;Unauthorized&quot; status code
with a <tt>WWW-Authenticate: Bearer</tt> challenge and <bcp14>MUST NOT</bcp14> include an error code. The
challenge <bcp14>SHOULD</bcp14> include a <tt>resource_metadata</tt> parameter pointing to the hub's protected
resource metadata (see <xref target="discovery"></xref>) per <xref target="RFC9728"></xref>.</li>
<li>If a token is presented but fails validation as defined in <xref target="token-validation"></xref>, or carries
Mercure authorization details that fail to parse or validate or that exceed
implementation-defined limits (see <xref target="authorization-details"></xref>), the hub
<bcp14>MUST</bcp14> return a 401 status code with <tt>error=&quot;invalid_token&quot;</tt>.</li>
<li>If a valid token does not authorize the requested operation, the hub <bcp14>MUST</bcp14> return a 403
&quot;Forbidden&quot; status code with <tt>error=&quot;insufficient_scope&quot;</tt>.</li>
<li>If the request is malformed, the hub <bcp14>MUST</bcp14> return a 400 &quot;Bad Request&quot; status code. When
the malformed element is the access token or its presentation, the response <bcp14>MUST</bcp14> carry
a <tt>WWW-Authenticate: Bearer</tt> challenge with <tt>error=&quot;invalid_request&quot;</tt> <xref target="RFC6750"></xref>. A 400
for a request malformed independently of the access token conveys no <xref target="RFC6750"></xref> error code
unless the hub chooses to include such a challenge, since these codes are defined only as
challenge parameters.</li>
</ul>
<t>Returning <tt>invalid_token</tt> for every presented-token failure (rather than distinguishing a bad
signature from an expired or not-yet-valid token) avoids disclosing why validation failed.</t>
<t>For error responses that are not conveyed through the <tt>WWW-Authenticate: Bearer</tt> 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 <bcp14>MAY</bcp14> return a problem details document
<xref target="RFC9457"></xref> as the response body. The OAuth error codes above remain carried in the challenge and
are not duplicated in such a body.</t>
</section>

<section anchor="token-validation"><name>Token Validation</name>
<t>Hubs <bcp14>MUST</bcp14> validate access tokens as JWT access tokens <xref target="RFC9068"></xref> and in accordance with the
JSON Web Token Best Current Practices <xref target="RFC8725"></xref>. The requirements below profile those
documents for hubs rather than replace them. In particular:</t>

<ul spacing="compact">
<li>Hubs <bcp14>MUST</bcp14> verify that the token header <tt>typ</tt> is <tt>at+jwt</tt>, or the equivalent
<tt>application/at+jwt</tt>, compared with the <tt>application/</tt> prefix omitted and case-insensitively
per <xref target="RFC7515"></xref> section 4.1.9 <xref target="RFC9068"></xref>, so that tokens issued for other purposes (for
example, OpenID Connect ID Tokens) are not accepted.</li>
<li>Hubs <bcp14>MUST</bcp14> be configured with an explicit allowlist of accepted signature algorithms and
<bcp14>MUST</bcp14> reject any token whose <tt>alg</tt> is not on that allowlist. Hubs <bcp14>MUST NOT</bcp14> accept
<tt>alg=none</tt>, <bcp14>MUST NOT</bcp14> derive the set of acceptable algorithms from the token, and <bcp14>MUST</bcp14>
verify that <tt>alg</tt> is compatible with the key used for verification (preventing
algorithm-confusion attacks). The allowlist <bcp14>SHOULD</bcp14> include at minimum <tt>EdDSA</tt>, <tt>ES256</tt>,
and <tt>RS256</tt>, and <bcp14>SHOULD NOT</bcp14> include algorithms known to be cryptographically weak at
the time of deployment.</li>
<li>Hubs <bcp14>MUST</bcp14> select the verification key from a preconfigured or pre-trusted set and
<bcp14>MUST NOT</bcp14> use key material supplied by the token itself (such as the <tt>jwk</tt>, <tt>jku</tt>, or
<tt>x5u</tt> header parameters). When more than one trusted key is in use — for example, separate
publisher and subscriber keys or rotated keys — the <tt>kid</tt> header parameter <bcp14>MAY</bcp14> be used
as a hint to choose among the trusted keys, as <bcp14>MAY</bcp14> 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 <xref target="RFC7517"></xref> discovered through its metadata <xref target="RFC8414"></xref>. This specification defines no
hub-specific key distribution endpoint. Each trusted key <bcp14>MUST</bcp14> be associated in the
hub's configuration with the issuer identifier it belongs to — the authorization server's
issuer identifier <xref target="RFC8414"></xref>, or the stable identifier chosen by a self-issuing publisher.
When the hub trusts key material from more than one issuer, it
<bcp14>MUST</bcp14> verify the signature using only the key(s) associated with the token's <tt>iss</tt>,
never a pooled
set spanning issuers, so that a token signed by one trusted issuer cannot be accepted under
another issuer's identity.</li>
<li>Hubs <bcp14>MUST</bcp14> enforce the <tt>exp</tt> claim <xref target="RFC7519"></xref>, including on the first request received
bearing a token, and <bcp14>MUST</bcp14> reject a token that has no <tt>exp</tt> claim (<xref target="RFC9068"></xref> requires
it). Hubs <bcp14>MUST</bcp14> enforce the <tt>nbf</tt> claim if present.</li>
<li>Hubs <bcp14>MUST</bcp14> be configured with their resource identifier and <bcp14>MUST</bcp14> verify that it
appears in the token <tt>aud</tt> claim <xref target="RFC9068"></xref>. It is <bcp14>RECOMMENDED</bcp14> that the resource
identifier be the canonical URL of the hub (for example,
<tt>https://hub.example.com/.well-known/mercure</tt>), which gives deployments an obvious default. Per <xref target="RFC7519"></xref>, <tt>aud</tt> <bcp14>MAY</bcp14> 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 <xref target="hub-trust"></xref>). Hubs <bcp14>MUST</bcp14> verify that the <tt>iss</tt> claim
is present and exactly matches the issuer identifier of one of the hub's trusted issuers
<xref target="RFC9068"></xref> — an authorization server (whether or not it is advertised in the hub's
protected resource metadata, see <xref target="discovery"></xref>) or a self-issuing publisher configured out
of band.</li>
</ul>
<t><xref target="RFC9068"></xref> requires issuers to populate the <tt>iss</tt>, <tt>exp</tt>, <tt>aud</tt>, <tt>sub</tt>, <tt>client_id</tt>, <tt>iat</tt>,
and <tt>jti</tt> claims; this requirement applies to self-issuing publishers as it does to
authorization servers (see <xref target="authorization"></xref>), so that every token is a conforming <xref target="RFC9068"></xref>
JWT access token. Hubs <bcp14>MUST</bcp14> enforce <tt>iss</tt>, <tt>exp</tt>, and <tt>aud</tt> as described above, per
<xref target="RFC9068"></xref> section 4; the remaining claims are issuer obligations that hubs use when they
rely on them.</t>
<t>The <tt>sub</tt> claim identifies the subscriber and is used to derive subscription
event identifiers (see <xref target="subscription-events"></xref>).</t>
<t>Failure of any of these checks <bcp14>MUST</bcp14> be reported as defined in <xref target="error-responses"></xref>.</t>
</section>

<section anchor="authorization-details"><name>Authorization Details</name>
<t>Authorization is expressed with the <tt>authorization_details</tt> claim <xref target="RFC9396"></xref>, a JSON array of
authorization detail objects. This specification defines the authorization detail type
identified by the URI <tt>https://mercure.rocks/authorization-detail</tt>, referred to in this
document as a Mercure authorization detail.
<xref target="RFC9396"></xref> does not establish a registry of authorization details type identifiers (it leaves
their registration with authorization servers out of scope); as <xref target="RFC9396"></xref> 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
<tt>authorization_details_types_supported</tt> metadata <xref target="RFC9396"></xref>.</t>
<t>A Mercure authorization detail object:</t>

<ul spacing="compact">
<li><bcp14>MUST</bcp14> have a <tt>type</tt> property whose value is the string
<tt>https://mercure.rocks/authorization-detail</tt>, compared byte by byte.</li>
<li><bcp14>MUST</bcp14> have an <tt>actions</tt> property: a non-empty JSON array of strings (the <tt>actions</tt>
field of <xref target="RFC9396"></xref>). This document defines the actions <tt>publish</tt> and <tt>subscribe</tt>; additional actions can
be registered in the &quot;Mercure Actions&quot; registry (see <xref target="iana-considerations"></xref>). Hubs <bcp14>MUST</bcp14>
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 <tt>actions</tt> contains no action
recognized by the hub simply grants nothing. A non-string entry in <tt>actions</tt> remains a
validation failure.</li>
<li><bcp14>MUST</bcp14> have a <tt>topics</tt> property: a non-empty JSON array of topic matcher objects (see
<xref target="topic-matcher-list"></xref>) identifying the topics the actions apply to.</li>
<li><bcp14>MAY</bcp14> have a <tt>payload</tt> property (a JSON object), meaningful only for the <tt>subscribe</tt> action
(see <xref target="payloads"></xref>).</li>
</ul>
<t>A token grants an action on a topic when it carries a Mercure authorization detail whose
<tt>actions</tt> includes that action and one of whose <tt>topics</tt> matches the topic. Tokens with no
Mercure authorization detail grant no publish or subscribe rights.</t>
<t>Hubs <bcp14>SHOULD</bcp14> apply implementation-defined maximums to the number of Mercure authorization
details, to the number of entries in each <tt>topics</tt> array, and to the length of individual
patterns. Tokens exceeding any such limit <bcp14>MUST</bcp14> be rejected as invalid tokens, with a 401
status code and <tt>error=&quot;invalid_token&quot;</tt> (see <xref target="error-responses"></xref>). If any
Mercure authorization detail fails to parse or validate (including failures specific to a
matcher's <tt>match_type</tt>), the hub <bcp14>MUST</bcp14> reject the token the same way and <strong>MUST
NOT</strong> 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 <tt>invalid_token</tt> rather than
<tt>invalid_request</tt> <xref target="RFC6750"></xref>.</t>
<t>Hubs <bcp14>MAY</bcp14> also limit the number of concurrent subscriptions established under a single token
and <bcp14>MAY</bcp14> reject further subscription attempts with a 429 &quot;Too Many Requests&quot; status code once
the limit is reached.</t>
</section>

<section anchor="publishers"><name>Publishers</name>
<t>A publisher <bcp14>MUST</bcp14> present a token that grants the <tt>publish</tt> action on every topic of the
update, canonical and alternate alike: for each topic, the token <bcp14>MUST</bcp14> carry a Mercure
authorization detail whose <tt>actions</tt> includes <tt>publish</tt> and one of whose <tt>topics</tt> matches that
topic. If the token is not authorized for every topic of the update, the hub <bcp14>MUST NOT</bcp14>
dispatch the update — even if some of its topics are allowed — and <bcp14>MUST</bcp14> return a 403 status
code with <tt>error=&quot;insufficient_scope&quot;</tt> (see <xref target="error-responses"></xref>).</t>
</section>

<section anchor="subscribers"><name>Subscribers</name>
<t>To receive updates marked as <tt>private</tt>, a subscriber <bcp14>MUST</bcp14> present a token that grants the
<tt>subscribe</tt> action on at least one topic of the update (canonical or alternate): the token
<bcp14>MUST</bcp14> carry a Mercure authorization detail whose <tt>actions</tt> includes <tt>subscribe</tt> and one of
whose <tt>topics</tt> matches at least one of the update's topics. If the token does not grant
<tt>subscribe</tt> on any topic of the update, the hub <bcp14>MUST NOT</bcp14> deliver the update to the
subscriber.</t>
<t>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 <xref target="private-update-audience"></xref>). 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 <tt>subscribe</tt> grant matching one of the update's topics to receive it.</t>
<t>When the subscriber presented an access token, the hub <bcp14>MUST</bcp14> close the connection no
later than the token's <tt>exp</tt> time, since <tt>exp</tt> is required (see <xref target="token-validation"></xref>).
Since <tt>exp</tt> alone cannot revoke an already-established
long-lived connection, hubs <bcp14>SHOULD</bcp14> also impose a maximum connection lifetime independent of
<tt>exp</tt> and close connections that exceed it, requiring the subscriber to reconnect and
re-authenticate.</t>
<t>For example, a subscriber may listen to all books via the routing matcher
<tt>match_urlpattern=https://example.com/books/:id</tt> while its token authorizes reading only specific
books:</t>

<sourcecode type="json"><![CDATA[{
  "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"}
      ]
    }
  ]
}
]]></sourcecode>
<t>A private update for <tt>https://example.com/books/1</tt> is delivered; a private update for
<tt>https://example.com/books/2</tt> is not, even though the routing matcher would select it.</t>
<t>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, <tt>https://example.com/users/42/*</tt>, that listens to all books via
<tt>match_urlpattern=https://example.com/books/:id</tt>:</t>

<sourcecode type="json"><![CDATA[{
  "authorization_details": [
    {
      "type": "https://mercure.rocks/authorization-detail",
      "actions": ["subscribe"],
      "topics": [
        {"match": "https://example.com/users/42/*", "match_type": "urlpattern"}
      ]
    }
  ]
}
]]></sourcecode>
<t>The publisher publishes a private update about <tt>https://example.com/books/1</tt>, attaching
<tt>https://example.com/users/42/books/1</tt> as an alternate topic:</t>

<sourcecode type="http"><![CDATA[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
]]></sourcecode>
<t>The subscriber's routing matcher selects the update by its canonical topic, <tt>books/1</tt>, which its
token does not grant <tt>subscribe</tt> on. The token does grant <tt>subscribe</tt> on the alternate topic, so
the hub delivers the update. A publisher <bcp14>MUST NOT</bcp14> 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
<xref target="private-update-audience"></xref>).</t>
</section>

<section anchor="topic-matcher-list"><name>Topic Matcher List</name>
<t>A topic matcher object appears in the <tt>topics</tt> array of a Mercure authorization detail (see
<xref target="authorization-details"></xref>). It <bcp14>MUST</bcp14> be a JSON object with a <tt>match</tt> property containing the
topic matcher itself, and <bcp14>MAY</bcp14> have an OPTIONAL <tt>match_type</tt> property containing the matcher
type. The value of <tt>match_type</tt> is case-sensitive and <bcp14>MUST</bcp14> be the name of a matcher type
supported by the hub (see <xref target="matcher-types"></xref>); this document defines <tt>exact</tt> and <tt>urlpattern</tt>.
Unlike unrecognized actions (see <xref target="authorization-details"></xref>), a <tt>match_type</tt> the hub does not
support <bcp14>MUST</bcp14> cause the token to be rejected: the grant cannot be evaluated, and skipping it
would silently alter the effective authorization of the token. If no <tt>match_type</tt> key is
present, the hub <bcp14>MUST</bcp14> assume the <tt>exact</tt>
matcher type. A <tt>match</tt> value of <tt>*</tt> is the reserved wildcard and matches every topic regardless
of <tt>match_type</tt>, including when <tt>match_type</tt> is absent (see <xref target="matcher-types"></xref>).</t>
<t>Any entry that is not a JSON object, or that fails to parse or validate as a topic matcher,
<bcp14>MUST</bcp14> cause the token to be rejected with a 401 status code and <tt>error=&quot;invalid_token&quot;</tt> as
defined in <xref target="authorization-details"></xref>.</t>
</section>

<section anchor="payloads"><name>Payloads</name>
<t>User-defined data can be attached to a subscription and made available through the subscription
API and in subscription events. See <xref target="subscription-events"></xref>.</t>
<t>A Mercure authorization detail with the <tt>subscribe</tt> action <bcp14>MAY</bcp14> carry a <tt>payload</tt> JSON
object. The <tt>payload</tt> of the first <tt>subscribe</tt> authorization detail whose <tt>topics</tt> matches the
subscription's own matcher (the <tt>match</tt> or <tt>match_&lt;matcher-type&gt;</tt> query parameter value) <bcp14>MUST</bcp14> be
included under the <tt>payload</tt> key of the JSON object describing the subscription, both in the
subscription API and in subscription events. A <tt>subscribe</tt> detail whose <tt>topics</tt> contains the <tt>*</tt>
wildcard matches every subscription and can serve as a default.</t>
<t>Matching here treats the subscription's own matcher string as if it were a topic: for example,
the <tt>urlpattern</tt> matcher <tt>https://example.com/bar/:id</tt> in a <tt>subscribe</tt> detail matches the
subscription created by <tt>match_urlpattern=https://example.com/bar/:id</tt>, and an <tt>exact</tt> matcher
matches a subscription whose matcher string is byte-for-byte identical to it.</t>
<t>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 <bcp14>SHOULD</bcp14> appear before broader ones.</t>
<t>Privacy: Payloads are forwarded to other authorized subscribers via subscription events (see
<xref target="subscription-events"></xref>). Issuers <bcp14>MUST NOT</bcp14> 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.</t>
<t>Example access token claims carrying payloads:</t>

<sourcecode type="json"><![CDATA[{
    "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"}
        }
    ]
}
]]></sourcecode>
<t>For instance, a payload can carry coarse-grained metadata such as a tenant identifier or a
display label for the subscription. Issuers <bcp14>MUST</bcp14> consider the privacy note above before
including any identifier of the subscriber, since payloads are visible to other authorized
subscribers via subscription events.</t>
</section>
</section>

<section anchor="reconciliation"><name>Reconnection, State Reconciliation, and Event Sourcing</name>
<t>The protocol allows reconciliation of state after a reconnection. It can also be used to
implement an event store <xref target="EventSourcing"></xref>.</t>
<t>To allow re-establishment in case of connection loss, events dispatched by the hub <bcp14>MUST</bcp14>
include an <tt>id</tt> property. The value of this property <bcp14>SHOULD</bcp14> be an IRI <xref target="RFC3987"></xref>. A UUID
<xref target="RFC9562"></xref> or a DID <xref target="DID"></xref> <bcp14>MAY</bcp14> be used.</t>
<t>Per the Server-Sent Events specification, the subscriber tries to reconnect automatically in
case of connection loss. During reconnection, the subscriber <bcp14>MUST</bcp14> send the last received
event ID in a <tt>Last-Event-ID</tt> HTTP request header <xref target="HTML"></xref>.</t>
<t>To fetch any update dispatched between the initial resource generation by the publisher and the
connection to the hub, the subscriber <bcp14>MUST</bcp14> send the event ID provided during discovery
either as a <tt>Last-Event-ID</tt> header or as a <tt>last_event_id</tt> query parameter. See <xref target="discovery"></xref>.</t>
<t><tt>EventSource</tt> implementations may not allow setting HTTP headers on the first connection (before
a reconnection), and web browser implementations do not allow it.</t>
<t>To work around this, the hub <bcp14>MUST</bcp14> also accept the last event ID in a query parameter named
<tt>last_event_id</tt>.</t>
<t>If both the <tt>Last-Event-ID</tt> HTTP header and the <tt>last_event_id</tt> query parameter are present, the
HTTP header <bcp14>MUST</bcp14> take precedence.</t>
<t>If the <tt>Last-Event-ID</tt> HTTP header or the <tt>last_event_id</tt> query parameter is present, the hub
<bcp14>SHOULD</bcp14> send all events published after the one bearing this identifier to the subscriber,
subject to authorization.</t>
<t>The authorization rules defined in <xref target="subscribers"></xref> apply to replayed events identically to live
events: the hub <bcp14>MUST</bcp14> re-evaluate each candidate replayed event against the current access
token before dispatching it. Events whose <tt>private</tt> flag is set and that the token does not
authorize the subscriber to read (see <xref target="authorization-details"></xref>) <bcp14>MUST NOT</bcp14> be dispatched,
regardless of any authorization that may have applied at publication time.</t>
<t>The reserved value <tt>earliest</tt> requests that the hub send all updates it has for the subscribed
topics. The hub <bcp14>MAY</bcp14> ignore this request according to its own policy. Hub-generated
identifiers are IRIs or fragments (see above) and publishers are forbidden from supplying
<tt>earliest</tt> as an update ID (see <xref target="publication"></xref>), so <tt>earliest</tt> cannot collide with an event
identifier.</t>
<t>If more than one update in the hub's history shares the same ID, in violation of the
recommendation in <xref target="publication"></xref>, a <tt>Last-Event-ID</tt> bearing that value <bcp14>MUST</bcp14> resolve to the
earliest such update in the hub's history. The hub <bcp14>MUST</bcp14> then send all updates published
after that one, including any later update carrying the same, reused ID.</t>
<t>The hub <bcp14>MAY</bcp14> discard some events for operational reasons. When the request contains a
<tt>Last-Event-ID</tt> HTTP header or a <tt>last_event_id</tt> query parameter, the hub <bcp14>MUST</bcp14> set a
<tt>Mercure-Last-Event-ID</tt> field on the HTTP response. This document defines the
<tt>Mercure-Last-Event-ID</tt> response field and registers it in <xref target="iana-considerations"></xref> rather than
reusing <tt>Last-Event-ID</tt>, whose registration <xref target="HTML"></xref> defines request semantics only.</t>
<t>The value of this response field <bcp14>MUST</bcp14> be the identifier of the event preceding the first
event sent to the subscriber, or the reserved value <tt>earliest</tt> 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).</t>
<t>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).</t>
<t>Note: Event identifiers are cursors, and the hub exposes them to subscribers through the
<tt>last-event-id</tt> attribute of the <tt>rel=&quot;mercure&quot;</tt> Link header provided during discovery
(see <xref target="discovery"></xref>) and by the subscription API (see <xref target="subscription-api"></xref>), and through the
<tt>Mercure-Last-Event-ID</tt> 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 <xref target="RFC9562"></xref>), its approximate timing
and ordering. Operators handling sensitive private updates <bcp14>SHOULD</bcp14> generate opaque, random
event identifiers (e.g., UUIDv4 <xref target="RFC9562"></xref>), or expose an encrypted form of an internal ordered
identifier, so that the cursor discloses nothing beyond what the subscriber already knows.</t>
<t>The subscriber <bcp14>SHOULD NOT</bcp14> 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 <xref target="RFC6902"></xref> format, or when using the
hub as an event store), lost updates can cause data loss.</t>
<t>To detect data loss, the subscriber <bcp14>MAY</bcp14> compare the value of the <tt>Mercure-Last-Event-ID</tt>
response field with the last event ID it requested. In case of data loss, the subscriber
<bcp14>SHOULD</bcp14> re-fetch the original topic.</t>
<t>Note: Native <tt>EventSource</tt> implementations do not expose HTTP response headers. However,
polyfills and Server-Sent Events clients in most programming languages do.</t>
<t>The hub <bcp14>MAY</bcp14> also specify the reconnection time using the <tt>retry</tt> key, as defined by the
Server-Sent Events format.</t>
</section>

<section anchor="active-subscriptions"><name>Active Subscriptions</name>
<t>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 <bcp14>SHOULD</bcp14> advertise it
with the <tt>mercure_subscriptions</tt> metadata member (see <xref target="protected-resource-metadata"></xref>).</t>
<t>Variables are templated and expanded following <xref target="RFC6570"></xref>.</t>

<section anchor="subscription-events"><name>Subscription Events</name>
<t>If the hub supports the active subscriptions feature, it <bcp14>MUST</bcp14> publish an update every time a
subscription is created or terminated.</t>
<t>The topic of these updates <bcp14>MUST</bcp14> be the path of the hub's URL followed by an expansion of
<tt>/subscriptions/{match_type}/{match}/{subscriber}</tt> — for the default hub URL,
<tt>/.well-known/mercure/subscriptions/{match_type}/{match}/{subscriber}</tt> — with the following
variables:</t>

<ul spacing="compact">
<li><tt>{match_type}</tt>: the topic matcher type used for this subscription. The value <bcp14>MUST</bcp14> be the
matcher type name in its canonical case as defined in <xref target="matcher-types"></xref> (e.g., <tt>urlpattern</tt>,
<tt>exact</tt>). URL path components are case-sensitive.</li>
<li><tt>{match}</tt>: the topic matcher used for this subscription</li>
<li><tt>{subscriber}</tt>: a unique identifier for the subscriber. Subscribers and publishers
<bcp14>MUST NOT</bcp14> forge, supply, or override this value through query parameters, headers,
request bodies, or any other client-controlled channel. It is <bcp14>RECOMMENDED</bcp14> that the
hub generate a random identifier (for example, a UUID <xref target="RFC9562"></xref>) for each connection.
The hub <bcp14>MAY</bcp14> instead derive the identifier from information it has cryptographically
validated, but the derivation <bcp14>MUST</bcp14> yield an identifier unique per connection: the
pair formed by the token's issuer and its <tt>sub</tt> claim (after validation per
<xref target="token-validation"></xref>) 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 <xref target="subscriber-identifier-assignment"></xref>). A derivation
<bcp14>MUST</bcp14> also incorporate the issuer: <tt>sub</tt> 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 <tt>sub</tt> value to the
other subscribers authorized for subscription events. Distinct subscribers <bcp14>MUST</bcp14>
receive distinct identifiers. A single subscriber holding several active subscriptions
through one connection reuses the same identifier across them.</li>
</ul>
<t>Note: Because strings containing reserved characters (e.g., URIs, URL Patterns, and URI
Templates) can be used for the <tt>{match}</tt> and <tt>{subscriber}</tt> variables, per <xref target="RFC6570"></xref> the
value of each variable <bcp14>MUST</bcp14> be percent-encoded exactly once during expansion, encoding the
raw matcher or subscriber string as a whole (any <tt>%</tt> characters it already contains are
themselves encoded). Hubs <bcp14>MUST NOT</bcp14> double-encode an already-encoded value.</t>
<t><tt>{subscriber}</tt> <bcp14>SHOULD</bcp14> be an IRI <xref target="RFC3987"></xref>. A UUID <xref target="RFC9562"></xref> or a
DID <xref target="DID"></xref> <bcp14>MAY</bcp14> also be used.</t>
<t>The content of the update <bcp14>MUST</bcp14> be a JSON <xref target="RFC8259"></xref> document containing at least the
following properties:</t>

<ul spacing="compact">
<li><tt>id</tt>: the identifier of this update; <bcp14>MUST</bcp14> be the same value as the subscription update's
topic.</li>
<li><tt>type</tt>: the fixed value <tt>subscription</tt>.</li>
<li><tt>match_type</tt>: the topic matcher type used for this subscription. The value is case-sensitive
and <bcp14>MUST</bcp14> be the matcher type name in its canonical case as defined in <xref target="matcher-types"></xref>.</li>
<li><tt>match</tt>: the topic matcher used for this subscription.</li>
<li><tt>subscriber</tt>: the identifier of the subscriber. It <bcp14>SHOULD</bcp14> be an IRI.</li>
<li><tt>active</tt>: <tt>true</tt> when the subscription is active, <tt>false</tt> when it is terminated.</li>
<li><tt>payload</tt> (optional): the content of the <tt>payload</tt> field associated with this subscription
in the subscriber's access token (see <xref target="payloads"></xref>).</li>
</ul>
<t>The document <bcp14>MAY</bcp14> contain other properties.</t>
<t>To restrict subscription events to authorized subscribers, the subscription update <bcp14>MUST</bcp14> be
marked as <tt>private</tt>.</t>
<t>The hub <bcp14>MUST</bcp14> dispatch subscription events with the update <tt>type</tt> field — the SSE <tt>event</tt>
property (see <xref target="publication"></xref>) — set to the reserved value <tt>mercure</tt>. A subscriber receiving
several topics through a single connection can then distinguish subscription events from
application updates without inspecting the payload (for example, with
<tt>addEventListener(&quot;mercure&quot;, ...)</tt>). Publishers cannot produce updates carrying this event type
(see <xref target="publication"></xref>), so a subscriber can trust that an event of this type was generated by the
hub. This SSE <tt>event</tt> value is distinct from the JSON <tt>type</tt> property above, which identifies
the kind of document (<tt>subscription</tt> here, <tt>subscriptions</tt> for a collection, see
<xref target="subscription-api"></xref>).</t>
<t>Example:</t>

<sourcecode type="json"><![CDATA[{
   "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%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"}
}
]]></sourcecode>
</section>

<section anchor="subscription-api"><name>Subscription API</name>
<t>If the hub supports subscription events (see <xref target="subscription-events"></xref>), it <bcp14>SHOULD</bcp14> also expose
active subscriptions through a web API.</t>
<t>For instance, subscribers interested in maintaining a list of active subscriptions can call the
web API to retrieve them, then use subscription events (see <xref target="subscription-events"></xref>) to keep the
list up to date.</t>
<t>The web API <bcp14>MUST</bcp14> expose endpoints whose paths are the path of the hub's URL followed by
the patterns below (shown expanded for the default hub URL):</t>

<ul spacing="compact">
<li><tt>/.well-known/mercure/subscriptions</tt>: the collection of subscriptions.</li>
<li><tt>/.well-known/mercure/subscriptions/{match_type}/{match}</tt>: the collection of subscriptions
for the given topic matcher.</li>
<li><tt>/.well-known/mercure/subscriptions/{match_type}/{match}/{subscriber}</tt>: a specific
subscription.</li>
</ul>
<t>To access these URLs, clients <bcp14>MUST</bcp14> be authorized according to the rules defined in
<xref target="authorization"></xref>. The topic to authorize is the requested URL in relative form: its absolute
path (for example, <tt>/.well-known/mercure/subscriptions/{match_type}/{match}</tt>), which is the
same form used for subscription event topics (see <xref target="subscription-events"></xref>); any query component
of the request URL is not part of the topic. The hub <bcp14>MUST</bcp14>
verify that a Mercure authorization detail in the access token grants the <tt>subscribe</tt> action
on this relative topic, evaluated with the matcher rules of <xref target="matcher-types"></xref>: <tt>exact</tt> 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
<xref target="url-pattern"></xref>. 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 <tt>/.well-known/mercure/subscriptions</tt>, the per-matcher collection URL, and the
single-subscription URL are each matched as a topic, so a token whose <tt>subscribe</tt> matcher selects
a broader set (for example a <tt>urlpattern</tt> covering the subscriptions namespace, or the reserved
<tt>*</tt>) grants access to the corresponding endpoints. If no detail grants <tt>subscribe</tt> on the
requested URL, the hub <bcp14>MUST</bcp14> answer <tt>403</tt> as defined in <xref target="authorization"></xref>.</t>
<t>The web API <bcp14>MUST</bcp14> set the <tt>Content-Type</tt> HTTP header to <tt>application/json</tt>. When the hub
serves cross-origin subscribers, it <bcp14>MUST</bcp14> expose the <tt>Link</tt> response header to them (for
example, through the <tt>Access-Control-Expose-Headers</tt> header <xref target="FETCH"></xref>) so they can read the
<tt>last-event-id</tt> attribute defined below; see <xref target="subscription"></xref>.</t>
<t>URLs returning a single subscription (following the pattern
<tt>/.well-known/mercure/subscriptions/{match_type}/{match}/{subscriber}</tt>) <bcp14>MUST</bcp14> expose the same
document as described in <xref target="subscription-events"></xref>. If the requested subscription does not
exist, the hub <bcp14>MUST</bcp14> return a <tt>404</tt> HTTP status code.</t>
<t>If the requested subscription is no longer active, the hub <bcp14>MAY</bcp14> either return the
document with the <tt>active</tt> property set to <tt>false</tt> or return a <tt>404</tt> status code. Likewise,
collection endpoints <bcp14>MAY</bcp14> include terminated subscriptions with <tt>active</tt> set to <tt>false</tt> or
omit them.</t>
<t>Collection endpoints <bcp14>MUST</bcp14> return JSON documents containing at least the following
properties:</t>

<ul spacing="compact">
<li><tt>id</tt>: the URL used to retrieve the document.</li>
<li><tt>type</tt>: the fixed value <tt>subscriptions</tt>.</li>
<li><tt>subscriptions</tt>: an array of subscription documents as described in <xref target="subscription-events"></xref>.</li>
</ul>
<t>In addition, every endpoint <bcp14>MUST</bcp14> carry the reconciliation cursor as a <tt>last-event-id</tt> target
attribute on the <tt>rel=&quot;mercure&quot;</tt> Link header <xref target="RFC8288"></xref>, following the same mechanism as
discovery (see <xref target="discovery"></xref>). The value is the identifier of the last event dispatched by the hub
at the time of this request (see <xref target="reconciliation"></xref>), or <tt>earliest</tt> if no events have been
dispatched yet. It <bcp14>SHOULD</bcp14> be passed back to the hub as the <tt>last_event_id</tt> query parameter
(see <xref target="reconciliation"></xref>) 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 <xref target="subscription-events"></xref> without modification.</t>
<t>Subscription events are a homogeneous stream: they are always delivered under the reserved
<tt>mercure</tt> event type with a JSON body (see <xref target="subscription-events"></xref>). For consistency with
discovery (see <xref target="discovery"></xref>), the hub <bcp14>SHOULD</bcp14> also set the <tt>type</tt> and <tt>content-type</tt>
attributes on the same <tt>rel=&quot;mercure&quot;</tt> Link header, with the values <tt>mercure</tt> and
<tt>application/json</tt> respectively.</t>
<t>Active subscription collections can be large. Hubs <bcp14>MAY</bcp14> truncate or paginate collection
responses according to an implementation-defined policy; each returned document <bcp14>MUST</bcp14> remain
valid as described above. Pagination mechanisms are out of scope for this specification.</t>
<t>Because data returned by this web API is volatile, clients <bcp14>SHOULD</bcp14> validate that a cached
response is still fresh before using it.</t>
<t>Examples:</t>

<sourcecode type="http"><![CDATA[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%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%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%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"}
      }
   ]
}
]]></sourcecode>

<sourcecode type="http"><![CDATA[GET /.well-known/mercure/subscriptions/urlpattern/https%3A%2F%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%2Fexample.com%2F%3Aselector",
   "type": "subscriptions",
   "subscriptions": [
      {
         "id": "/.well-known/mercure/subscriptions/urlpattern/https%3A%2F%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%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"}
      }
   ]
}
]]></sourcecode>

<sourcecode type="http"><![CDATA[GET /.well-known/mercure/subscriptions/urlpattern/https%3A%2F%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%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"}
}
]]></sourcecode>
</section>
</section>

<section anchor="discovery"><name>Discovery</name>

<section anchor="hub-discovery"><name>Hub Discovery</name>
<t>The discovery mechanism aims at identifying the URL of one or more hubs designated by the publisher.</t>
<t>The URL of the hub <bcp14>SHOULD</bcp14> be the &quot;well-known&quot; <xref target="RFC8615"></xref> fixed path <tt>/.well-known/mercure</tt>,
which gives publishers and subscribers a default requiring no configuration. The hub URL
advertised through discovery is authoritative: it <bcp14>MAY</bcp14> be any HTTPS URL, enabling
deployments behind path prefixes and several hubs sharing an origin. Protected resource
metadata (see <xref target="protected-resource-metadata"></xref>) is derived from the hub URL per <xref target="RFC9728"></xref>
whatever its path, and the reserved namespace and subscription-event topics follow the path
of the hub URL (see <xref target="publication"></xref> and <xref target="subscription-events"></xref>). Examples throughout this document
assume the default hub URL.</t>
<t>If the publisher is a server, it <bcp14>SHOULD</bcp14> 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 <bcp14>MUST</bcp14> notify each hub, and the subscriber <bcp14>MAY</bcp14> subscribe to one
or more of them.</t>
<t>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.</t>
<t>The publisher <bcp14>SHOULD</bcp14> include at least one Link Header <xref target="RFC8288"></xref> with <tt>rel=mercure</tt> (a hub
link header). The target URL of such links <bcp14>MUST</bcp14> be a hub implementing the Mercure protocol.</t>
<t>Note: A compromised publisher can advertise a malicious hub URL and capture the access tokens of
subscribers that connect to it. Subscribers <bcp14>SHOULD</bcp14> restrict accepted hub URLs to origins
they have a basis to trust (for example, hubs sharing the publisher's registered domain) and
<bcp14>MAY</bcp14> verify the hub's identity through out-of-band means before transmitting credentials.</t>
<t>The publisher <bcp14>MAY</bcp14> provide the following target attributes in the Link Headers:</t>

<ul spacing="compact">
<li><tt>last-event-id</tt>: the identifier of the last event dispatched by the publisher at the time
the resource was generated. If provided, it <bcp14>MUST</bcp14> be passed to the hub through a query
parameter named <tt>last_event_id</tt>; this ensures that updates dispatched between the resource
generation and the connection to the hub are not lost. See <xref target="reconciliation"></xref>.</li>
<li><tt>content-type</tt>: the content type of the updates that will be pushed by the hub. If omitted,
the subscriber <bcp14>MUST</bcp14> assume that the content type matches that of the original resource.
The <tt>content-type</tt> attribute is especially useful to indicate that partial updates will be
pushed, in formats such as JSON Patch <xref target="RFC6902"></xref> or JSON Merge Patch <xref target="RFC7396"></xref>. Because the
<tt>data</tt> field of a Server-Sent Event carries no content type of its own, this attribute lets a
subscriber process the payload without content sniffing.</li>
<li><tt>type</tt>: the Server-Sent Events <tt>event</tt> field value (see <xref target="subscription"></xref>) that the updates
pushed for this resource will carry. A subscriber using the <tt>EventSource</tt> interface <xref target="HTML"></xref>
can register a listener for this type; if omitted, the subscriber <bcp14>MUST</bcp14> assume the default
event type (the events delivered to the <tt>EventSource</tt> <tt>message</tt> handler). It is
subject to the same character constraints as the publication <tt>type</tt> field (see
<xref target="publication"></xref>). A publisher advertising its own resource <bcp14>MUST NOT</bcp14> use the reserved
<tt>mercure</tt> type, which the hub generates for subscription events; the hub advertises <tt>mercure</tt>
on the subscription API, where the stream consists of those events (see <xref target="subscription-api"></xref>
and <xref target="subscription-events"></xref>).</li>
</ul>
<t>The <tt>content-type</tt> and <tt>type</tt> 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, <bcp14>MUST</bcp14> 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 <xref target="subscription"></xref>).</t>
<t>All these attributes are optional.</t>
<t>Minimal example:</t>

<sourcecode type="http"><![CDATA[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"}
]]></sourcecode>
</section>

<section anchor="protected-resource-metadata"><name>Protected Resource Metadata</name>
<t>As an OAuth 2.0 protected resource, the hub <bcp14>SHOULD</bcp14> publish OAuth 2.0 Protected Resource
Metadata <xref target="RFC9728"></xref> at the path derived from its URL as defined in that specification (for the
hub URL <tt>/.well-known/mercure</tt>, the metadata is served at
<tt>/.well-known/oauth-protected-resource/.well-known/mercure</tt>). Publishing the document is a
<bcp14>SHOULD</bcp14>; a document that is published <bcp14>MUST</bcp14> include the <tt>resource</tt> member (required by
<xref target="RFC9728"></xref>) and <bcp14>MAY</bcp14> include the others:</t>

<ul spacing="compact">
<li><tt>resource</tt> (required): the hub's resource identifier, which is the value clients <bcp14>MUST</bcp14>
place in the access token <tt>aud</tt> claim (see <xref target="token-validation"></xref>). Clients <bcp14>MUST</bcp14> verify that
this value equals the resource identifier they used to derive the well-known URL <xref target="RFC9728"></xref>.</li>
<li><tt>authorization_servers</tt> (optional): issuer identifiers of the authorization servers that can
issue tokens for the hub <xref target="RFC8414"></xref>. Omitted when tokens are self-issued.</li>
<li><tt>authorization_details_types_supported</tt>: the authorization detail types the hub understands.
The metadata member is defined by <xref target="RFC9728"></xref>; its values are <tt>authorization_details</tt> type
identifiers per <xref target="RFC9396"></xref>. For hubs implementing this specification, the array contains
<tt>https://mercure.rocks/authorization-detail</tt> (see <xref target="authorization-details"></xref>).</li>
<li><tt>bearer_methods_supported</tt>: the <xref target="RFC6750"></xref> token presentation methods the hub actually
accepts (see <xref target="presenting-the-access-token"></xref>): <tt>header</tt> <xref target="RFC9728"></xref>. The cookie mechanism is
not a <xref target="RFC6750"></xref>
bearer method, so it is not listed here; it is advertised by the separate <tt>mercure_cookie</tt>
member below.</li>
<li><tt>mercure_cookie</tt> (optional): a string, the name of the cookie in which the hub also accepts
the access token (a Mercure extension to <xref target="RFC6750"></xref>; see <xref target="cookie"></xref>). A client that cannot set
an <tt>Authorization</tt> header (for example, a web browser using <tt>EventSource</tt>) presents the token
by setting a cookie of this name. The cookie mechanism is advertised as a dedicated metadata
member rather than a value of <tt>bearer_methods_supported</tt>, whose values are constrained to the
<xref target="RFC6750"></xref> methods. This member is omitted when the hub does not offer cookie authorization.</li>
<li><tt>mercure_matcher_types_supported</tt> (optional): a JSON array of strings listing the names of
the topic matcher types the hub supports (see <xref target="matcher-types"></xref>). When omitted, the
supported set is exactly the two types defined by this document, <tt>exact</tt> and <tt>urlpattern</tt>;
the member is only needed when the hub supports registered additional types.</li>
<li><tt>mercure_subscriptions</tt> (optional): a boolean. When <tt>true</tt>, the hub implements the active
subscriptions feature (see <xref target="active-subscriptions"></xref>). This member is omitted when the hub
does not implement it.</li>
</ul>
<t>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 <tt>topic</tt>
subscribe query parameter and a bespoke token claim) do not publish protected resource
metadata; clients needing to coexist with them <bcp14>MAY</bcp14> treat the absence of this metadata as a
hint that the hub implements such a revision.</t>
<t>When a request carries no access token, the hub's <tt>WWW-Authenticate: Bearer</tt> challenge
<bcp14>SHOULD</bcp14> include a <tt>resource_metadata</tt> parameter pointing to this document (see
<xref target="error-responses"></xref>), so that clients can discover the resource identifier and authorization
server without prior configuration.</t>
</section>

<section anchor="topic-discovery"><name>Topic Discovery</name>
<t>The discovery mechanism <bcp14>MAY</bcp14> also be used to identify the canonical URL for the topic that
subscribers are expected to use for subscriptions.</t>
<t>The publisher <bcp14>MAY</bcp14> include one Link Header <xref target="RFC8288"></xref> with <tt>rel=self</tt> (the self link
header). It <bcp14>SHOULD</bcp14> contain the canonical URL for the topic. If the link with <tt>rel=self</tt> is
omitted, the current URL of the resource <bcp14>MAY</bcp14> be used as a fallback.</t>
<t>Links embedded in HTML or XML documents as defined in the WebSub recommendation
<xref target="W3C.REC-websub-20180123"></xref> <bcp14>MAY</bcp14> also be supported by subscribers. If both a header and an
embedded link are provided, the header <bcp14>MUST</bcp14> be preferred.</t>

<section anchor="content-negotiation"><name>Content Negotiation</name>
<t>For practical purposes, the <tt>rel=self</tt> URL <bcp14>SHOULD</bcp14> offer a single representation. The hub has
no way to know which Media Type (<xref target="RFC6838"></xref>) or language was requested by the subscriber upon
discovery, and therefore cannot select a representation on its behalf.</t>
<t>Content negotiation can, however, be performed by returning a different <tt>rel=self</tt> URL based on
the HTTP headers of the discovery request. For example, a request to <tt>/books/foo</tt> with an
<tt>Accept</tt> header containing <tt>application/ld+json</tt> could return a <tt>rel=self</tt> value of
<tt>/books/foo.jsonld</tt>.</t>
<t>The example below illustrates how a topic URL can return different <tt>Link</tt> headers depending on
the <tt>Accept</tt> header.</t>

<sourcecode type="http"><![CDATA[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"}
]]></sourcecode>

<sourcecode type="http"><![CDATA[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>
]]></sourcecode>
<t>The same technique can be used to return a different <tt>rel=self</tt> URL depending on the language
requested by the <tt>Accept-Language</tt> header.</t>

<sourcecode type="http"><![CDATA[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"}}
]]></sourcecode>
</section>
</section>
</section>

<section anchor="encryption"><name>Encryption</name>
<t>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 <bcp14>MAY</bcp14> be necessary to prevent
eavesdropping by the hub.</t>
<t>To prevent the hub from reading the message content, the publisher <bcp14>MAY</bcp14> encrypt the message
before sending it. The publisher <bcp14>SHOULD</bcp14> use JSON Web Encryption <xref target="RFC7516"></xref> 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 <xref target="RFC7517"></xref>; the hub is not
involved in this exchange.</t>
<t>Update encryption is considered a best practice to prevent mass surveillance, especially when
the hub is managed by an external provider.</t>
<t>Implementations <bcp14>SHOULD</bcp14> restrict JWE algorithms to those whose security properties remain
acceptable at the time of deployment; in particular, <tt>RSA1_5</tt> <bcp14>MUST NOT</bcp14> be used, due to
padding oracle vulnerabilities <xref target="RFC8017"></xref>.
At the time of writing, key management algorithms <tt>ECDH-ES+A256KW</tt> and <tt>RSA-OAEP-256</tt>, and
content encryption algorithm <tt>A256GCM</tt>, are <bcp14>RECOMMENDED</bcp14>.</t>
<t>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 <bcp14>SHOULD</bcp14> include a freshness indicator (such as a timestamp
or nonce) inside the encrypted payload and require subscribers to validate it.</t>
<t>Long-lived JWE keys do not provide forward secrecy: compromise of such keys decrypts all past
traffic encrypted under them. Publishers handling sensitive data <bcp14>SHOULD</bcp14> rotate JWE keys
periodically and <bcp14>MAY</bcp14> use ephemeral key agreement (e.g., <tt>ECDH-ES</tt>) to bound the impact of
a future key compromise.</t>
</section>

<section anchor="using-http"><name>Using HTTP</name>
<t>This protocol follows the guidance of <xref target="RFC9205"></xref>. It uses standard HTTP methods, standard status
codes, and registered media types, and does not overload their semantics. Clients discover a hub
from <tt>rel=mercure</tt> Web Linking <xref target="RFC8288"></xref> relations (see <xref target="discovery"></xref>) rather than a mandated
well-known location: the fixed path <tt>/.well-known/mercure</tt> is only a <bcp14>SHOULD</bcp14> default, and the
authoritative hub URL advertised through discovery <bcp14>MAY</bcp14> 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.</t>
</section>

<section anchor="iana-considerations"><name>IANA Considerations</name>

<section anchor="well-known-uris-registry"><name>Well-Known URIs Registry</name>
<t>The &quot;mercure&quot; well-known URI described in <xref target="discovery"></xref> is already registered in the
&quot;Well-Known URIs&quot; registry. IANA is requested to update the reference of the existing
registration to this document:</t>

<ul spacing="compact">
<li>URI Suffix: mercure</li>
<li>Change Controller: IETF</li>
<li>Specification document(s): This specification, <xref target="discovery"></xref></li>
<li>Status: permanent</li>
<li>Related information: N/A</li>
</ul>
</section>

<section anchor="link-relation-types-registry"><name>Link Relation Types Registry</name>
<t>The &quot;mercure&quot; link relation type described in <xref target="discovery"></xref> is already registered in the &quot;Link
Relation Types&quot; registry. IANA is requested to update the reference of the existing
registration to this document:</t>

<ul spacing="compact">
<li>Relation Name: mercure</li>
<li>Description: The Mercure Hub to use to subscribe to updates of this resource.</li>
<li>Reference: This specification, <xref target="discovery"></xref></li>
</ul>
</section>

<section anchor="oauth-protected-resource-metadata-registry"><name>OAuth Protected Resource Metadata Registry</name>
<t>The following values are to be registered in the &quot;OAuth Protected Resource Metadata&quot; registry
established by <xref target="RFC9728"></xref>:</t>

<ul>
<li>Metadata Name: mercure_cookie</li>
<li>Metadata Description: String naming the cookie in which the Mercure hub also accepts the
access token</li>
<li>Change Controller: IETF</li>
<li><t>Specification Document(s): This specification, <xref target="protected-resource-metadata"></xref></t>
</li>
<li><t>Metadata Name: mercure_matcher_types_supported</t>
</li>
<li><t>Metadata Description: JSON array listing the names of the Mercure topic matcher types
supported by the hub</t>
</li>
<li><t>Change Controller: IETF</t>
</li>
<li><t>Specification Document(s): This specification, <xref target="protected-resource-metadata"></xref></t>
</li>
<li><t>Metadata Name: mercure_subscriptions</t>
</li>
<li><t>Metadata Description: Boolean indicating that the Mercure hub implements the active
subscriptions feature</t>
</li>
<li><t>Change Controller: IETF</t>
</li>
<li><t>Specification Document(s): This specification, <xref target="protected-resource-metadata"></xref></t>
</li>
</ul>
</section>

<section anchor="http-field-name-registry"><name>HTTP Field Name Registry</name>
<t>IANA is requested to register the following entry in the &quot;Hypertext Transfer Protocol (HTTP)
Field Name Registry&quot; defined by <xref target="RFC9110"></xref>:</t>

<ul spacing="compact">
<li>Field Name: Mercure-Last-Event-ID</li>
<li>Status: permanent</li>
<li>Structured Type: N/A</li>
<li>Reference: This specification, <xref target="reconciliation"></xref></li>
<li>Comments: Response field carrying the identifier of the event preceding the first event
sent, or the reserved value <tt>earliest</tt></li>
</ul>
</section>

<section anchor="mercure-topic-matcher-types-registry"><name>Mercure Topic Matcher Types Registry</name>
<t>IANA is requested to establish a &quot;Mercure Topic Matcher Types&quot; registry. New registrations
follow the Specification Required policy <xref target="RFC8126"></xref>.</t>
<t>A matcher type name <bcp14>MUST</bcp14> consist of lowercase ASCII letters and digits and <bcp14>MUST</bcp14> begin
with a lowercase letter. The name is case-sensitive and is used verbatim as the suffix of the
<tt>match_&lt;matcher-type&gt;</tt> subscribe query parameter (see <xref target="subscription"></xref>) and as the <tt>match_type</tt>
value in authorization details (see <xref target="topic-matcher-list"></xref>). The designated experts verify that
the defining specification states the matching semantics precisely, bounds the evaluation cost
of a crafted matcher (see <xref target="url-pattern-denial-of-service"></xref> for the kind of exposure to
consider), and does not conflict with the reserved wildcard <tt>*</tt> (see <xref target="matcher-types"></xref>).</t>
<t>A registration provides:</t>

<ul spacing="compact">
<li>Matcher Type Name: the name, subject to the syntax above.</li>
<li>Description: a brief description of the matching semantics.</li>
<li>Change Controller: &quot;IETF&quot; for registrations in the IETF stream, otherwise the party
responsible for the registration.</li>
<li>Reference: the specification defining the matcher type.</li>
</ul>
<t>Note that the name syntax above admits no name equal to the reserved wildcard <tt>*</tt> (see
<xref target="matcher-types"></xref>), so no registration can shadow it.</t>
<t>Initial registrations:</t>
<table>
<thead>
<tr>
<th>Matcher Type Name</th>
<th>Description</th>
<th>Change Controller</th>
<th>Reference</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>exact</tt></td>
<td>Byte-for-byte comparison of topic and matcher</td>
<td>IETF</td>
<td>This specification, <xref target="exact-matching"></xref></td>
</tr>

<tr>
<td><tt>urlpattern</tt></td>
<td>Matching per the URL Pattern Living Standard</td>
<td>IETF</td>
<td>This specification, <xref target="url-pattern"></xref></td>
</tr>
</tbody>
</table></section>

<section anchor="mercure-actions-registry"><name>Mercure Actions Registry</name>
<t>IANA is requested to establish a &quot;Mercure Actions&quot; registry for the values of the <tt>actions</tt>
property of Mercure authorization details (see <xref target="authorization-details"></xref>). New registrations
follow the Specification Required policy <xref target="RFC8126"></xref>.</t>
<t>An action name <bcp14>MUST</bcp14> consist of lowercase ASCII letters and digits. Hubs ignore actions
they do not recognize (see <xref target="authorization-details"></xref>), so registering a new action does not
require deployed hubs to change.</t>
<t>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 <tt>topics</tt>
of its authorization detail, and does not duplicate an already registered action.</t>
<t>A registration provides:</t>

<ul spacing="compact">
<li>Action Name: the name, subject to the syntax above.</li>
<li>Description: a brief description of the operation the action authorizes.</li>
<li>Change Controller: &quot;IETF&quot; for registrations in the IETF stream, otherwise the party
responsible for the registration.</li>
<li>Reference: the specification defining the action.</li>
</ul>
<t>Initial registrations:</t>
<table>
<thead>
<tr>
<th>Action Name</th>
<th>Description</th>
<th>Change Controller</th>
<th>Reference</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>publish</tt></td>
<td>Send updates for the matching topics</td>
<td>IETF</td>
<td>This specification, <xref target="publishers"></xref></td>
</tr>

<tr>
<td><tt>subscribe</tt></td>
<td>Receive private updates for the matching topics</td>
<td>IETF</td>
<td>This specification, <xref target="subscribers"></xref></td>
</tr>
</tbody>
</table></section>
</section>

<section anchor="security-considerations"><name>Security Considerations</name>
<t>The recommendations of the OAuth 2.0 Security Best Current Practice <xref target="RFC9700"></xref> apply to
deployments of this protocol; this section highlights the considerations specific to Mercure.</t>
<t>The confidentiality of the secret key(s) used to sign access tokens is a primary concern. Such
keys <bcp14>MUST</bcp14> be stored securely and <bcp14>MUST</bcp14> be revoked immediately in the event of a breach.</t>
<t>A valid access token allows any client that holds it to subscribe to or publish on the hub. Its
confidentiality <bcp14>MUST</bcp14> therefore be ensured: access tokens <bcp14>MUST</bcp14> only be transmitted over
secure connections.</t>
<t>When the client is a web browser, the access token <bcp14>SHOULD NOT</bcp14> be exposed to JavaScript, to
provide resilience against cross-site scripting (XSS) attacks <xref target="OWASP-XSS"></xref>.
For this reason, <tt>HttpOnly</tt> cookies <bcp14>SHOULD</bcp14> be preferred as the authorization mechanism in
that case.</t>
<t>In the event of a breach, revoking access tokens before their expiration is often difficult.
Short-lived tokens are therefore strongly <bcp14>RECOMMENDED</bcp14>.</t>
<t>The hub's publishing endpoint can be targeted by cross-site request forgery (CSRF) attacks
<xref target="OWASP-CSRF"></xref> when the cookie-based authorization mechanism is used. Implementations supporting
that mechanism <bcp14>MUST</bcp14> mitigate such attacks: the <tt>SameSite</tt> cookie attribute recommended in
<xref target="cookie"></xref> is the first line of defense, and because some deployed user agents do not enforce
it, hubs <bcp14>SHOULD</bcp14> also verify that the source origin conveyed by the <tt>Origin</tt> or <tt>Referer</tt>
HTTP header matches the target origin, rejecting the request when neither header is available.
CSRF prevention techniques are described in depth in <xref target="OWASP-CSRF-Prevention"></xref>.</t>
<t>Access tokens <bcp14>MUST NOT</bcp14> be passed in URLs (for example, via the <tt>access_token</tt> query
parameter, which this specification does not accept; see
<xref target="presenting-the-access-token"></xref>). 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 <xref target="RFC9700"></xref>.</t>

<section anchor="token-validation-1"><name>Token Validation</name>
<t>Access tokens are validated as JWT access tokens before authorization details are evaluated (see
<xref target="token-validation"></xref>). Failure to verify the <tt>typ</tt> is <tt>at+jwt</tt>, to reject <tt>alg: none</tt>, to bind
<tt>alg</tt> to the key type, to enforce <tt>exp</tt>/<tt>nbf</tt>, or to verify <tt>aud</tt> enables token forgery, token
type confusion (for example, accepting an ID Token), replay across contexts, and
algorithm-confusion attacks.</t>
</section>

<section anchor="server-sent-events-field-injection"><name>Server-Sent Events Field Injection</name>
<t>Topic strings and the <tt>id</tt>, <tt>type</tt>, and <tt>retry</tt> 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 <xref target="terminology"></xref> and
<xref target="publication"></xref> prevent this injection. The <tt>data</tt> field may contain line breaks legitimately;
it is not constrained the same way, so the hub <bcp14>MUST</bcp14> serialize it as one <tt>data:</tt> field per
line (see <xref target="publication"></xref>) rather than emitting the raw value. A hub that writes the value
without this line-splitting would let a <tt>data</tt> value containing <tt>\nevent:</tt> or <tt>\nid:</tt> inject a
forged field, so this serialization is a security requirement, not only a formatting one.</t>
</section>

<section anchor="reserved-hub-namespace"><name>Reserved Hub Namespace</name>
<t>The path subtree of the hub URL (<tt>/.well-known/mercure/</tt> for the default hub URL) is
reserved for resources generated by the hub
itself (see <xref target="publication"></xref>). A publisher with broad scope publishing under this prefix could
forge subscription events (see <xref target="subscription-events"></xref>) 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,
<tt>https://hub.example.com/.well-known/mercure/subscriptions/...</tt>) bypass it, and skipping
normalization would let percent-encoded variants (for example, <tt>/.well-known/%6Dercure/...</tt>)
do the same.</t>
<t>The <tt>mercure</tt> SSE event type is reserved the same way. The hub sets it on the events it
generates (see <xref target="subscription-events"></xref>), and a subscriber may route on it (for example, with
<tt>addEventListener(&quot;mercure&quot;, ...)</tt>). Because the SSE <tt>event</tt> field carries the publisher-supplied
<tt>type</tt> field, a publisher could otherwise emit an update with <tt>type</tt> set to <tt>mercure</tt> on any
topic and have it delivered to that listener as a forged hub event. Hubs <bcp14>MUST</bcp14> therefore
reject a publish request whose <tt>type</tt> is <tt>mercure</tt> (see <xref target="publication"></xref>). 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.</t>
</section>

<section anchor="authorization-on-event-replay"><name>Authorization on Event Replay</name>
<t>When a subscriber reconnects with a <tt>Last-Event-ID</tt> header or <tt>last_event_id</tt> query parameter,
the same authorization rules apply to replayed events as to live events (see <xref target="reconciliation"></xref>
and <xref target="subscribers"></xref>). A subscriber whose authorized scope has shrunk between publication and
reconnection does not receive private events outside its scope at reconnection time. The
<tt>Mercure-Last-Event-ID</tt> response field is a cursor, however, and <bcp14>MAY</bcp14> contain the identifier
of an event the subscriber is not authorized to receive. Operators handling sensitive private updates
<bcp14>SHOULD</bcp14> use opaque, random event identifiers so that this identifier discloses nothing
beyond the event's existence.</t>
</section>

<section anchor="subscriber-identifier-assignment"><name>Subscriber Identifier Assignment</name>
<t>The <tt>{subscriber}</tt> 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 <xref target="subscription-events"></xref>). 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 <tt>sub</tt> 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 <tt>active: false</tt> event that
misrepresents the state of the others, and the corresponding subscription API resource becomes
ambiguous. Deriving the identifier from <tt>sub</tt> 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 <tt>sub</tt> also disclose that claim's
value to every subscriber authorized for the corresponding subscription events; hub-generated
random identifiers avoid that disclosure.</t>
</section>

<section anchor="private-update-audience"><name>Private Update Audience</name>
<t>A private update is delivered to a subscriber only when the subscriber's access token grants the
<tt>subscribe</tt> action on at least one of its topics (see <xref target="subscribers"></xref> and
<xref target="authorization-details"></xref>). 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 <xref target="authorization"></xref>), present no token and therefore
never receive private updates.</t>
<t>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 <bcp14>MUST NOT</bcp14> attach to a private update
an alternate topic matchable by an audience broader than the audience intended to read the
update's content (see <xref target="subscribers"></xref> 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.</t>
</section>

<section anchor="url-pattern-denial-of-service"><name>URL-Pattern Denial of Service</name>
<t>URL Pattern compiles internally to a regular expression. Naive implementations on engines such
as PCRE are vulnerable to catastrophic backtracking. The mitigations required in <xref target="url-pattern"></xref>
— a linear-time engine (such as RE2 <xref target="re2"></xref>) or a per-evaluation cost or time limit — bound this
exposure.</t>
</section>

<section anchor="payload-privacy"><name>Payload Privacy</name>
<t>Payloads carried in Mercure authorization details are included in subscription events and
forwarded to other authorized subscribers (see <xref target="payloads"></xref>). 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.</t>
</section>

<section anchor="topic-normalization"><name>Topic Normalization</name>
<t>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.,
<tt>example.com</tt> versus a host containing Cyrillic look-alike characters). The normalization
guidance in <xref target="exact-matching"></xref> addresses this.</t>
</section>

<section anchor="resource-limits"><name>Resource Limits</name>
<t>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 <tt>topic</tt> fields per publish request, the number of
topic matcher query parameters per request, the number of Mercure authorization details and of
entries in their <tt>topics</tt> arrays, individual pattern length, concurrent subscriptions per token,
and concurrent connections per client and in total (see <xref target="subscription"></xref>) — bound this exposure.</t>
</section>

<section anchor="hub-trust"><name>Hub Trust</name>
<t>Subscribers obtain hub URLs from publishers via the discovery mechanism (see <xref target="discovery"></xref>) 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 <xref target="discovery"></xref>, 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 <tt>aud</tt> claim (see <xref target="token-validation"></xref>) 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.</t>
</section>

<section anchor="protected-resource-metadata-and-authorization-server-selection"><name>Protected Resource Metadata and Authorization Server Selection</name>
<t>When the hub advertises an authorization server through protected resource metadata <xref target="RFC9728"></xref>
(see <xref target="discovery"></xref>), a client that is misled into using an inappropriate authorization server may
expose itself to an adversary-in-the-middle. Clients <bcp14>SHOULD</bcp14> validate protected resource
metadata as described in <xref target="RFC9728"></xref> and obtain it only from the deterministically derived,
TLS-protected well-known location. Hubs and clients fetching metadata or key sets by URL
<bcp14>SHOULD</bcp14> take precautions against server-side request forgery, such as refusing requests to
internal address ranges.</t>
</section>

<section anchor="bearer-tokens-and-sender-constraint"><name>Bearer Tokens and Sender Constraint</name>
<t>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 <bcp14>MAY</bcp14> additionally
sender-constrain tokens, for example with DPoP <xref target="RFC9449"></xref> or mutual-TLS-bound tokens, so that a
captured token is unusable without the corresponding proof-of-possession key.</t>
</section>

<section anchor="publish-request-replay"><name>Publish Request Replay</name>
<t>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 <bcp14>SHOULD</bcp14> include a freshness indicator in the
update (for example, checking the unique <tt>id</tt> recommended in <xref target="publication"></xref> against previously
seen values, or a timestamp) and reject duplicates.</t>
</section>

<section anchor="jwe-algorithms-and-replay"><name>JWE Algorithms and Replay</name>
<t>JWE-protected updates are subject to algorithm-selection pitfalls and to replay. The algorithm
restrictions and freshness guidance in <xref target="encryption"></xref> address these.</t>
</section>
</section>

<section anchor="privacy-considerations"><name>Privacy Considerations</name>
<t>The general privacy guidance of <xref target="RFC6973"></xref> applies. The following considerations are specific
to this protocol:</t>

<ul spacing="compact">
<li>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
<xref target="subscription-events"></xref>); token issuers <bcp14>SHOULD NOT</bcp14> grant <tt>subscribe</tt> on the
subscriptions namespace more broadly than the tracking use case requires.</li>
<li>Payloads embedded in access tokens are broadcast to every subscriber authorized for the
corresponding subscription events (see <xref target="payloads"></xref> and <xref target="payload-privacy"></xref>).</li>
<li>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 <bcp14>SHOULD NOT</bcp14> be encoded in topic strings.</li>
<li>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 <xref target="encryption"></xref>) bounds what a curious or compromised hub learns to this
metadata.</li>
<li>Event identifiers act as cursors that can reveal the existence and approximate timing of
updates a subscriber is not authorized to read; see <xref target="authorization-on-event-replay"></xref>.</li>
</ul>
</section>

<section anchor="implementation-status"><name>Implementation Status</name>
<t>[RFC Editor Note: Please remove this entire section prior to publication as an RFC.]</t>
<t>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 <xref target="RFC7942"></xref>. 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, &quot;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.&quot;</t>
<t>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.</t>

<section anchor="mercure-rocks-hub"><name>Mercure.rocks Hub</name>
<t>Organization responsible for the implementation:</t>
<t>Dunglas Services SAS</t>
<t>Implementation Name and Details:</t>
<t>Mercure.rocks, available at <eref target="https://mercure.rocks">https://mercure.rocks</eref></t>
<t>Brief Description:</t>
<t>This is the reference implementation of the Mercure hub. It is written in Go and is optimized for
performance.</t>
<t>Level of Maturity:</t>
<t>Widely used.</t>
<t>Coverage:</t>
<t>All the features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation follows the latest draft.</t>
<t>Licensing:</t>
<t>All code is covered under the GNU Affero Public License version 3 or later.</t>
<t>Implementation Experience:</t>
<t>Used in production.</t>
<t>Contact Information:</t>
<t>Kévin Dunglas, <eref target="mailto:contact@mercure.rocks">contact@mercure.rocks</eref> <eref target="https://mercure.rocks">https://mercure.rocks</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with all major browsers and server-side tools.</t>
</section>

<section anchor="freddie"><name>Freddie</name>
<t>Implementation Name and Details:</t>
<t>Freddie, <eref target="https://github.com/bpolaszek/freddie">https://github.com/bpolaszek/freddie</eref></t>
<t>Brief Description:</t>
<t>Freddie is a PHP implementation of the Mercure Hub Specification.</t>
<t>Level of Maturity:</t>
<t>Stable.</t>
<t>Coverage:</t>
<t>All the features of the protocol except the subscription events.</t>
<t>Version compatibility:</t>
<t>The implementation follows the latest draft.</t>
<t>Licensing:</t>
<t>All code is covered under the GNU General Public License v3.0.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/bpolaszek/freddie">https://github.com/bpolaszek/freddie</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with all major browsers and server-side tools.</t>
</section>

<section anchor="ilshidur-node-mercure"><name>Ilshidur/node-mercure</name>
<t>Implementation Name and Details:</t>
<t>Ilshidur/node-mercure, <eref target="https://github.com/Ilshidur/node-mercure">https://github.com/Ilshidur/node-mercure</eref></t>
<t>Brief Description:</t>
<t>Hub and Publisher implemented in Node.</t>
<t>Level of Maturity:</t>
<t>Beta, not suitable for production.</t>
<t>Coverage:</t>
<t>All the features of the protocol except the subscription events.</t>
<t>Version compatibility:</t>
<t>The implementation currently follows the revision 5 of the draft.</t>
<t>Licensing:</t>
<t>All code is covered under the GNU Public License version 3 or later.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/Ilshidur/node-mercure">https://github.com/Ilshidur/node-mercure</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with all major browsers and server-side tools.</t>
</section>

<section anchor="symfony"><name>Symfony</name>
<t>Implementation Name and Details:</t>
<t>Symfony Mercure Component, available at <eref target="https://symfony.com/doc/current/components/mercure.html">https://symfony.com/doc/current/components/mercure.html</eref></t>
<t>Brief Description:</t>
<t>This is a publisher library written in PHP. It also provides support for Mercure in the Symfony web
framework.</t>
<t>Level of Maturity:</t>
<t>Widely used.</t>
<t>Coverage:</t>
<t>All the publisher features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation follows the latest draft.</t>
<t>Licensing:</t>
<t>All code is covered under the MIT license.</t>
<t>Implementation Experience:</t>
<t>Used in production.</t>
<t>Contact Information:</t>
<t><eref target="https://symfony.com">https://symfony.com</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the Mercure.rocks Hub.</t>
</section>

<section anchor="api-platform"><name>API Platform</name>
<t>Implementation Name and Details:</t>
<t>API Platform, available at <eref target="https://api-platform.com/docs/core/mercure/">https://api-platform.com/docs/core/mercure/</eref></t>
<t>Brief Description:</t>
<t>The API Platform framework lets developers create async APIs implementing the Mercure protocol
and generate clients for these APIs.</t>
<t>Level of Maturity:</t>
<t>Widely used.</t>
<t>Coverage:</t>
<t>All the publisher and consumer features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation follows the latest draft.</t>
<t>Licensing:</t>
<t>All code is covered under the MIT license.</t>
<t>Implementation Experience:</t>
<t>Used in production.</t>
<t>Contact Information:</t>
<t><eref target="https://api-platform.com">https://api-platform.com</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="laravel-mercure-broadcaster"><name>Laravel Mercure Broadcaster</name>
<t>Implementation Name and Details:</t>
<t>Laravel Mercure Broadcaster, available at
<eref target="https://github.com/mvanduijker/laravel-mercure-broadcaster">https://github.com/mvanduijker/laravel-mercure-broadcaster</eref></t>
<t>Brief Description:</t>
<t>Laravel broadcaster for Mercure. Use the Mercure protocol as transport for Laravel Broadcast.</t>
<t>Level of Maturity:</t>
<t>Production</t>
<t>Coverage:</t>
<t>All the publisher features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation currently follows the revision 5 of the draft. An open Pull Request adds support
for the latest version of the draft.</t>
<t>Licensing:</t>
<t>All code is covered under the MIT license.</t>
<t>Implementation Experience:</t>
<t>Used in production.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/mvanduijker/laravel-mercure-broadcaster">https://github.com/mvanduijker/laravel-mercure-broadcaster</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="dart-mercure"><name>dart_mercure</name>
<t>Implementation Name and Details:</t>
<t>dart_mercure, available at <eref target="https://github.com/wallforfry/dart_mercure">https://github.com/wallforfry/dart_mercure</eref></t>
<t>Brief Description:</t>
<t>Publisher and Subscriber library for Dart / Flutter.</t>
<t>Level of Maturity:</t>
<t>Stable</t>
<t>Coverage:</t>
<t>All the publisher and subscriber features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation follows the latest draft.</t>
<t>Licensing:</t>
<t>All code is covered under the BSD 2-Clause &quot;Simplified&quot; License.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/wallforfry/dart_mercure">https://github.com/wallforfry/dart_mercure</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="pymercure"><name>pymercure</name>
<t>Implementation Name and Details:</t>
<t>pymercure, available at <eref target="https://github.com/vitorluis/python-mercure">https://github.com/vitorluis/python-mercure</eref></t>
<t>Brief Description:</t>
<t>Publisher and Subscriber library for Python.</t>
<t>Level of Maturity:</t>
<t>Alpha</t>
<t>Coverage:</t>
<t>All the publisher and subscriber features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation currently follows the revision 5 of the draft. An open Pull Request adds support
for the latest version of the draft.</t>
<t>Licensing:</t>
<t>All code is covered under the BSD 2-Clause &quot;Simplified&quot; License.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/vitorluis/python-mercure">https://github.com/vitorluis/python-mercure</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="amphp-mercure-publisher"><name>Amphp Mercure Publisher</name>
<t>Implementation Name and Details:</t>
<t>Amphp Mercure Publisher, available at <eref target="https://github.com/eislambey/amp-mercure-publisher">https://github.com/eislambey/amp-mercure-publisher</eref></t>
<t>Brief Description:</t>
<t>Async Mercure publisher based on Amphp.</t>
<t>Level of Maturity:</t>
<t>Stable</t>
<t>Coverage:</t>
<t>All the publisher features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation currently follows the revision 5 of the draft. An open Pull Request adds support
for the latest version of the draft.</t>
<t>Licensing:</t>
<t>All code is covered under the MIT license.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/eislambey/amp-mercure-publisher">https://github.com/eislambey/amp-mercure-publisher</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="java-library-for-mercure"><name>Java Library for Mercure</name>
<t>Implementation Name and Details:</t>
<t>Java Library for Mercure, available at <eref target="https://github.com/vitorluis/java-mercure">https://github.com/vitorluis/java-mercure</eref></t>
<t>Brief Description:</t>
<t>Java library to publish messages to a Mercure Hub!</t>
<t>Level of Maturity:</t>
<t>Alpha</t>
<t>Coverage:</t>
<t>All the publisher features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation currently follows the revision 5 of the draft. An open Pull Request adds support
for the latest version of the draft.</t>
<t>Licensing:</t>
<t>All code is covered under the MIT license.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/vitorluis/java-mercure">https://github.com/vitorluis/java-mercure</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="yii-2-mercure-behavior"><name>Yii 2 Mercure behavior</name>
<t>Implementation Name and Details:</t>
<t>Yii 2 Mercure behavior, available at <eref target="https://github.com/bizley/mercure-behavior">https://github.com/bizley/mercure-behavior</eref></t>
<t>Brief Description:</t>
<t>Yii 2 behavior to automatically publish updates to a Mercure hub.</t>
<t>Level of Maturity:</t>
<t>Stable</t>
<t>Coverage:</t>
<t>All the publisher features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation currently follows the revision 5 of the draft.</t>
<t>Licensing:</t>
<t>All code is covered under the Apache License 2.0.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/bizley/mercure-behavior">https://github.com/bizley/mercure-behavior</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="github-action-for-mercure"><name>GitHub Action for Mercure</name>
<t>Implementation Name and Details:</t>
<t>GitHub Action for Mercure, available at
<eref target="https://github.com/marketplace/actions/github-action-for-mercure">https://github.com/marketplace/actions/github-action-for-mercure</eref></t>
<t>Brief Description:</t>
<t>Send a Mercure update when a GitHub event occurs.</t>
<t>Level of Maturity:</t>
<t>Stable</t>
<t>Coverage:</t>
<t>All the publisher features of the protocol.</t>
<t>Version compatibility:</t>
<t>The implementation currently follows the latest version of the draft.</t>
<t>Licensing:</t>
<t>All code is covered under the GNU Public License version 3 or later.</t>
<t>Contact Information:</t>
<t><eref target="https://github.com/Ilshidur/action-mercure">https://github.com/Ilshidur/action-mercure</eref></t>
<t>Interoperability:</t>
<t>Reported compatible with the reference implementation of the Mercure Hub.</t>
</section>

<section anchor="other-implementations"><name>Other Implementations</name>
<t>Other implementations can be found on GitHub: <eref target="https://github.com/topics/mercure">https://github.com/topics/mercure</eref></t>
</section>
</section>

<section anchor="acknowledgements"><name>Acknowledgements</name>
<t>Parts of this specification, especially <xref target="discovery"></xref> have been adapted from the WebSub
recommendation <xref target="W3C.REC-websub-20180123"></xref>. The editor wishes to thank all the authors of this
specification.</t>
</section>

</middle>

<back>
<references><name>References</name>
<references><name>Normative References</name>
<reference anchor="FETCH" target="https://fetch.spec.whatwg.org/review-drafts/2026-06/">
  <front>
    <title>Fetch Living Standard (Review Draft, June 2026)</title>
    <author>
      <organization>The Web Hypertext Application Technology Working Group (WHATWG)</organization>
    </author>
    <date year="2026" month="June"></date>
  </front>
</reference>
<reference anchor="HTML" target="https://html.spec.whatwg.org/review-drafts/2026-01/">
  <front>
    <title>HTML Living Standard (Review Draft, January 2026)</title>
    <author>
      <organization>The Web Hypertext Application Technology Working Group (WHATWG)</organization>
    </author>
    <date year="2026" month="January"></date>
  </front>
</reference>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.ietf-httpbis-rfc6265bis.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3629.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3986.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3987.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5891.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6265.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6570.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6585.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6749.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6750.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6838.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7515.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7516.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7517.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7519.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8126.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8259.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8288.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8414.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8615.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8725.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9068.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9110.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9396.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9700.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9728.xml"/>
<reference anchor="UNICODE" target="https://www.unicode.org/versions/Unicode17.0.0/">
  <front>
    <title>The Unicode Standard, Version 17.0.0</title>
    <author>
      <organization>The Unicode Consortium</organization>
    </author>
    <date year="2025" month="September"></date>
  </front>
</reference>
<reference anchor="URL" target="https://url.spec.whatwg.org/review-drafts/2026-02/">
  <front>
    <title>URL Living Standard (Review Draft, February 2026)</title>
    <author>
      <organization>The Web Hypertext Application Technology Working Group (WHATWG)</organization>
    </author>
    <date year="2026" month="February"></date>
  </front>
</reference>
<reference anchor="urlpattern" target="https://urlpattern.spec.whatwg.org/review-drafts/2025-09/">
  <front>
    <title>URL Pattern Living Standard (Review Draft, September 2025)</title>
    <author>
      <organization>The Web Hypertext Application Technology Working Group (WHATWG)</organization>
    </author>
    <date year="2025" month="September"></date>
  </front>
</reference>
</references>
<references><name>Informative References</name>
<reference anchor="DID" target="https://www.w3.org/TR/did-core/">
  <front>
    <title>Decentralized Identifiers (DIDs) v1.0</title>
    <author>
      <organization>World Wide Web Consortium (W3C)</organization>
    </author>
    <date year="2022"></date>
  </front>
</reference>
<reference anchor="EventSourcing" target="https://martinfowler.com/eaaDev/EventSourcing.html">
  <front>
    <title>Event Sourcing</title>
    <author fullname="Martin Fowler" initials="M." surname="Fowler"></author>
    <date year="2005" month="December"></date>
  </front>
</reference>
<reference anchor="OWASP-CSRF" target="https://owasp.org/www-community/attacks/csrf">
  <front>
    <title>Cross Site Request Forgery (CSRF)</title>
    <author>
      <organization>OWASP Foundation</organization>
    </author>
    <date year="2024"></date>
  </front>
</reference>
<reference anchor="OWASP-CSRF-Prevention" target="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html">
  <front>
    <title>Cross-Site Request Forgery Prevention Cheat Sheet</title>
    <author>
      <organization>OWASP Foundation</organization>
    </author>
    <date year="2024"></date>
  </front>
</reference>
<reference anchor="OWASP-XSS" target="https://owasp.org/www-community/attacks/xss/">
  <front>
    <title>Cross Site Scripting (XSS)</title>
    <author>
      <organization>OWASP Foundation</organization>
    </author>
    <date year="2024"></date>
  </front>
</reference>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.10008.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.4287.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6902.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6973.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7396.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7942.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8017.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9205.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9449.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9457.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9562.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml4/reference.W3C.REC-activitypub-20180123.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml4/reference.W3C.REC-json-ld11-20200716.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml4/reference.W3C.REC-websub-20180123.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml4/reference.W3C.REC-xml-20081126.xml"/>
<reference anchor="re2" target="https://github.com/google/re2/wiki/Syntax">
  <front>
    <title>RE2: a principled approach to regular expression matching</title>
    <author>
      <organization>Google</organization>
    </author>
    <date year="2024"></date>
  </front>
</reference>
<reference anchor="streams" target="https://streams.spec.whatwg.org/">
  <front>
    <title>Streams Living Standard</title>
    <author>
      <organization>The Web Hypertext Application Technology Working Group (WHATWG)</organization>
    </author>
    <date year="2026"></date>
  </front>
</reference>
<reference anchor="xhr" target="https://xhr.spec.whatwg.org/review-drafts/2025-08/">
  <front>
    <title>XMLHttpRequest Living Standard (Review Draft, August 2025)</title>
    <author>
      <organization>The Web Hypertext Application Technology Working Group (WHATWG)</organization>
    </author>
    <date year="2025" month="August"></date>
  </front>
</reference>
</references>
</references>

<section anchor="changes-from-pre-standardization-deployments"><name>Changes from Pre-Standardization Deployments</name>
<t>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 <xref target="discovery"></xref>: a hub that
publishes no protected resource metadata (see <xref target="protected-resource-metadata"></xref>) likely implements
one of these earlier revisions.</t>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Pre-standardization</th>
<th>This specification</th>
</tr>
</thead>

<tbody>
<tr>
<td>Authorization grant</td>
<td><tt>mercure</tt> JWT claim with <tt>publish</tt> and <tt>subscribe</tt> arrays of topic selectors</td>
<td>RFC 9396 <tt>authorization_details</tt> entry with <tt>type</tt> <tt>https://mercure.rocks/authorization-detail</tt>, an <tt>actions</tt> array (<tt>publish</tt>, <tt>subscribe</tt>), and a <tt>topics</tt> array (see <xref target="authorization-details"></xref>)</td>
</tr>

<tr>
<td>Token type</td>
<td>any signed JWT</td>
<td>JWT access token with <tt>typ</tt> <tt>at+jwt</tt> (see <xref target="token-validation"></xref>)</td>
</tr>

<tr>
<td>Topic matching</td>
<td>raw topic selectors passed as <tt>topic</tt> query parameters</td>
<td><tt>exact</tt> and <tt>urlpattern</tt> matcher types selected with the <tt>match</tt>/<tt>match_&lt;matcher-type&gt;</tt> query parameters or the <tt>match_type</tt> member (see <xref target="matcher-types"></xref>)</td>
</tr>

<tr>
<td>Reconnection query parameter</td>
<td><tt>lastEventID</tt></td>
<td><tt>last_event_id</tt> (see <xref target="reconciliation"></xref>)</td>
</tr>

<tr>
<td>Reconnection response field</td>
<td><tt>Last-Event-ID</tt></td>
<td><tt>Mercure-Last-Event-ID</tt> (see <xref target="reconciliation"></xref>)</td>
</tr>

<tr>
<td>Authorization cookie</td>
<td><tt>mercureAuthorization</tt></td>
<td><tt>__Secure-mercure_access_token</tt> (see <xref target="cookie"></xref>)</td>
</tr>

<tr>
<td>Token in the URL</td>
<td><tt>authorization</tt> query parameter</td>
<td>not accepted; <tt>Authorization</tt> header or cookie (see <xref target="presenting-the-access-token"></xref>)</td>
</tr>
</tbody>
</table></section>

</back>

</rfc>
