Discovery
A client needs two things before it can subscribe to private updates: the URL of the hub, and the authorization requirements of that hub. Mercure exposes both through standard mechanisms, so a generic OAuth 2.0 client library can discover them without Mercure-specific code.
Finding the hub
A resource advertises its hub with a Web Linking Link header (or the equivalent HTML <link> element) carrying rel="mercure":
# Finding the hub GET /books/42 HTTP/2 Host: example.com HTTP/2 200 Link: <https://hub.example.com/.well-known/mercure>; rel="mercure" Content-Type: application/json { "@id": "/books/42", "title": "..." }
The client parses the header, takes the URL with rel="mercure", appends its match* query parameters, and opens an EventSource. Reusing your existing API responses to carry the link keeps subscribers and publishers pointing at the same hub.
Protected resource metadata
The hub is an OAuth 2.0 protected resource, so it publishes OAuth 2.0 Protected Resource Metadata. For a hub at https://hub.example.com/.well-known/mercure, the metadata lives at:
# Protected resource metadata location https://hub.example.com/.well-known/oauth-protected-resource/.well-known/mercure
// GET /.well-known/oauth-protected-resource/.well-known/mercure { "resource": "https://hub.example.com/.well-known/mercure", "bearer_methods_supported": ["header"], "authorization_details_types_supported": [ "https://mercure.rocks/authorization-detail" ], "authorization_servers": ["https://auth.example.com"], "mercure_cookie": "__Secure-mercure_access_token" }
Members:
resource: the hub's resource identifier. This is the value a token'saudclaim must contain (see Authorization).bearer_methods_supported: the RFC 6750 presentation methods the hub accepts:header(theAuthorizationheader). Theaccess_tokenquery parameter is not accepted (RFC 9700).authorization_details_types_supported: always containshttps://mercure.rocks/authorization-detail, the RFC 9396 authorization detail type this hub understands.authorization_servers(optional): the issuer identifiers of the authorization servers that mint tokens for this hub. A client uses these to locate the server, run an OAuth 2.0 flow, and obtain an access token. Advertise an issuer by addingauthorization_serverinside itsissuerblock.mercure_cookie(optional): the name of the cookie in which the hub also accepts the token. A cookie is not an RFC 6750 method, so it has its own member rather than appearing inbearer_methods_supported.
The hub serves this document only when it validates tokens (a pure-anonymous hub has nothing to advertise). The jwks_uri member is intentionally omitted: the hub hosts no JWKS endpoint, and the separate publisher and subscriber key sets can't be expressed as one jwks_uri. To validate tokens against an external key set, point an issuer's verifier at it with jwks_uri (see Configuration).
How the pieces fit together
When a client hits an operation that needs a token without one, the hub answers 401 with a bare WWW-Authenticate: Bearer challenge that includes a resource_metadata parameter pointing at the document above:
# Bearer challenge HTTP/2 401 WWW-Authenticate: Bearer resource_metadata="https://hub.example.com/.well-known/oauth-protected-resource/.well-known/mercure"
A client that doesn't yet have a token follows that parameter, reads authorization_servers, obtains a token from the named authorization server, and retries. See Authorization for the full set of RFC 6750 responses.