sharedSignals(), can run either or both roles:
- Transmitter. Receivers create a stream, choose events and a delivery method, and theAuth sends them signed Security Event Tokens (SETs, RFC 8417).
- Receiver. theAuth verifies SETs from a transmitter you trust and runs handlers. By default it revokes local sessions when the transmitter says a session, all sessions, or an account is gone.

One transmitter, many receivers. Each stream picks its events and delivery.
Why you would use it
Without shared signals, a disabled account in one system stays signed in everywhere else until each session expires. With them, the system that knows tells the others in seconds.Revoke everywhere
Ban a user once and every connected app drops their sessions.
React to risk
Credential compromise or a device falling out of compliance can force re-authentication.
Standard, not custom
Interoperates with any SSF receiver or transmitter, not only theAuth.
Safe by default
Signed events, replay protection, SSRF-safe delivery, no events until you opt in.
How it works

From an admin action to a revoked session on the other side.
Try it in two steps
1
Run a transmitter
Add
sharedSignals({ transmitter }) and wire bridge.onAdminAction (see Install and Emit from existing paths). Banning a user now emits account-disabled and sessions-revoked.2
Run a receiver
In the other app add
sharedSignals({ receiver: { issuer, audience } }) and register a stream with the transmitter pointing at POST /ssf/receive. Sessions for the banned user are revoked there with no extra code.Install
theauth-shared-signals. When it is present theAuth creates three tables: theauth_ssf_streams, theauth_ssf_subjects and theauth_ssf_outbox.
Publish the public key at jwksUri yourself. If you use the OIDC provider with the same key, its JWKS endpoint already does.
Transmitter
Endpoints
Paths are relative to your mount path.endpointBaseUrl must be the absolute URL these are served at, because it is advertised in discovery.
Every management and poll endpoint calls your
authenticate function. A receiver only sees its own streams. Each endpoint declares a rate limit in its metadata.
Streams
events_delivered is the intersection of events_requested and the events the transmitter supports. Delivery is urn:ietf:rfc:8935 (push) or urn:ietf:rfc:8936 (poll). For poll streams the response carries the poll URL in delivery.endpoint_url.
authorization_header is stored as given and is never returned.
Subjects
Subject identifiers follow RFC 9493:iss_sub, email, opaque and complex (with user, device, session, application, tenant, org_unit and group members).
defaultSubjects sets the mode of new streams. With ALL (the default) every subject is delivered and subjects/remove excludes one. With NONE nothing is delivered until subjects/add includes a subject. A stream that follows a user also receives events whose complex subject has that user as its user member.
Emitting events
emit signs one SET per matching stream, stores it, and delivers it. Builders exist for every supported event:
Push delivery

Pick push for latency, poll when the receiver cannot expose a public URL.
application/secevent+jwt with the stream’s Authorization header. A 2xx marks the SET delivered. 408, 429, 5xx and network errors are retried with exponential backoff (default 1 s doubling to 5 min, up to 5 attempts). Other statuses, including an RFC 8935 400 error, and redirects fail the SET without retry.
Push URLs go through the same checks as other outbound fetches in theAuth: https only, no credentials, default port, no localhost or metadata hostnames, and every resolved address must be public. The check runs when the stream is configured and again before each attempt. Redirects are never followed.
delivery options:
Retries in
background mode run on in-process timers. If the process exits, pending SETs stay in theauth_ssf_outbox; call signals.transmitter.processDue() from a scheduled job to resume them. Serverless runtimes that freeze after the response should use inline or run processDue() on a schedule.
Poll delivery
POST /ssf/poll/:stream_id accepts maxEvents, returnImmediately, ack and setErrs and answers { "sets": { "<jti>": "<jwt>" }, "moreAvailable": false }. Unacknowledged SETs are returned again. returnImmediately: false waits up to longPollMs (default 0, so it answers at once).
Receiver
POST /ssf/receive (configurable with path). Keys come from, in order: verificationKey, a static jwks, or discovery. Discovery fetches ${issuer}/.well-known/ssf-configuration (or configurationUrl), requires its issuer to equal the configured issuer, then fetches jwks_uri, all through the SSRF-safe fetcher. An unknown kid triggers at most one refetch every 30 seconds.
Checks on every SET: signature, typ of secevent+jwt, iss, aud, an events object, no exp, an iat no older than maxAgeSeconds (default 3600), and a jti not seen before. Replays are acknowledged with 202 and not dispatched again. Failures answer 400 with an RFC 8935 err such as invalid_key, invalid_issuer or invalid_audience.
The replay cache is per process memory by default. Pass replayStorage (any SecondaryStorage) when you run several instances.
Default session revocation
When the app has a session manager, these events revoke local sessions unless you setrevokeSessions: false:
User mapping: an
iss_sub from the configured issuer maps sub to the local user id, an email is looked up in the user table, and an opaque id is taken as the user id. Pass resolveUserId to replace this. Handlers you supply take precedence over the defaults for the same event type.
Emit from existing paths
The bridge turns existing callbacks into events. It is opt in: nothing is emitted until you pass the callback.iss_sub with the transmitter issuer and the user id; pass subjectForUser to sharedSignals to change it.
Limits
- Subject formats beyond
iss_sub,email,opaqueandcomplex(account,phone_number,did,uri, and so on) are rejected. - Per-subject stream status is not supported (
501). Subjectverifiedis stored but not enforced. - The stream-updated event is not sent.
- Streams are authorized by your
authenticatefunction only. There is no built-in OAuth scope model for management calls. - Delivery is at least once. Receivers should treat
jtias an idempotency key. - The platform
fetchresolves the push hostname again when connecting, so a DNS rebinding attacker with a very short TTL can race the address check. Pass afetchImplthat pins the validated address to close this. - Database support follows the rest of the SDK: tables use the shared SQLite-style schema definitions.