This document extends the Linked Web Storage (LWS) protocol with a mechanism for clients to receive timely updates about changes to resources. Three notification channels are defined: Server-Sent Events, WebSocket, and Webhooks. All channels share a common notification data model and are discoverable via the storage description resource.

This is an unofficial proposal.

Introduction

The [[!LWS-PROTOCOL|Linked Web Storage]] protocol defines a resource-centric storage system built on HTTP. While the core protocol supports creating, reading, updating, and deleting resources, it does not define a mechanism for clients to learn about changes without polling.

This specification extends LWS with a notification mechanism that allows clients to subscribe to resource changes and receive updates through one of three channels:

All three channels share a common notification envelope and are discoverable via the storage description resource.

Terminology

The terms "storage description resource", "resource manager", "requesting agent", "container", and "data resource" are defined by [[!LWS-PROTOCOL]].

The terms "controlled identifier document", "verification method", and "verification relationship" are defined by Controlled Identifiers 1.0 [[!CID-1.0]].

This specification defines the following additional terms:

Discovery

A storage MAY support notifications. A storage that supports notifications MUST advertise a service object in the service array of its storage description resource with type equal to NotificationService.

The service object MUST include a serviceEndpoint property whose value is the URI of the subscription endpoint. The service object MUST include a subscriptionType property whose value is an array of strings, each identifying a supported subscription type.

Additional properties MAY be present on the service object.

{
  "@context": ["https://www.w3.org/ns/lws/v1"],
  "id": "https://storage.example/",
  "type": "Storage",
  "service": [{
    "type": "NotificationService",
    "serviceEndpoint": "https://notification.example/subscriptions",
    "subscriptionType": [
      "WebhookSubscription",
      "WebSocketSubscription",
      "EventSourceSubscription"
    ]
  }]
}
      

A server is not required to support all subscription types. The subscriptionType array advertises exactly which types are available.

Notification Envelope

A notification is represented as a JSON-LD document with a Notification type. The envelope carries metadata about the delivery context and wraps an Activity Streams 2.0 [[ACTIVITYSTREAMS-CORE]] [[ACTIVITYSTREAMS-VOCABULARY]] activity describing the event.

Envelope Properties

A notification envelope has the following properties:

{
  "@context": [
    "https://www.w3.org/ns/lws/v1",
    "https://www.w3.org/ns/activitystreams"
  ],
  "type": "Notification",
  "phase": "PostCommit",
  "storage": "https://storage.example/",
  "activity": {
    "id": "ec4dcc48-6581-4232-81c9-584525a98693",
    "type": ["Delete"],
    "object": {
      "id": "https://storage.example/alice/notes/shopping.txt",
      "type": ["DataResource"]
    },
    "origin": "https://storage.example/alice/notes/",
    "published": "2026-03-26T10:30:00Z"
  }
}
        

Activity Properties

Each activity object within the activity property MUST conform to the Activity Streams 2.0 data model and MUST include the following properties:

The following properties are OPTIONAL:

Activity Types

A server MUST support the following Activity Streams 2.0 activity types to indicate LWS resource changes:

Other activity types MAY also be supported.

Batching Notifications

A server MAY combine multiple activities into a single notification envelope by providing an array of activity objects as the value of the activity property.

{
  "@context": [
    "https://www.w3.org/ns/lws/v1",
    "https://www.w3.org/ns/activitystreams"
  ],
  "type": "Notification",
  "phase": "PostCommit",
  "storage": "https://storage.example/",
  "activity": [
    {
      "id": "a1b2c3d4-5678-9abc-def0-1234567890ab",
      "type": ["Create"],
      "object": {
        "id": "https://storage.example/alice/notes/meeting.txt",
        "type": ["DataResource"]
      },
      "target": "https://storage.example/alice/notes/",
      "published": "2026-03-26T10:30:00Z"
    },
    {
      "id": "b2c3d4e5-6789-abcd-ef01-234567890abc",
      "type": ["Update"],
      "object": {
        "id": "https://storage.example/alice/profile",
        "type": ["DataResource"]
      },
      "published": "2026-03-26T10:30:01Z"
    }
  ]
}
        

Subscriptions

To receive notifications, a subscriber creates a subscription by sending an authenticated POST request to the serviceEndpoint of the NotificationService. The request body MUST conform to the application/lws+json media type.

Subscription Request

A subscription request MUST contain the following fields:

A subscription request MAY contain the following fields:

A subscription request MAY contain additional fields as required by the specific subscription type.

POST /subscriptions HTTP/2
Host: notification.example
Authorization: Bearer <access-token>
Content-Type: application/lws+json

{
  "@context": ["https://www.w3.org/ns/lws/v1"],
  "type": "EventSourceSubscription",
  "phase": "PostCommit",
  "topic": [
    "https://storage.example/alice/notes/",
    "https://storage.example/alice/profile"
  ]
}
        

Subscription Scope

A subscription to a container is recursive: the subscriber receives notifications for the container itself and for all resources transitively contained in that container. A subscription to a data resource applies only to that individual resource.

Subscription Authorization

A server MUST enforce resource authorization when creating a subscription. If a subscriber does not have the equivalent of read access to all resources listed in the topic array, the server MUST reject the subscription request.

Authorization MUST also be enforced at notification delivery time. A server MUST NOT deliver a notification about a resource that the subscriber is not authorized to read at the time the event occurs. This applies to all resources within the scope of a subscription, including resources in subcontainers.

If the subscriber's access to a resource or container within the scope of a subscription is revoked after the subscription is created, the server MUST stop delivering notifications for the affected resources. The server SHOULD NOT terminate the entire subscription unless the subscriber has lost access to all resources in the topic array.

Subscription-time authorization verifies that a subscriber has a legitimate basis for receiving notifications. Delivery-time authorization ensures that notifications remain consistent with the subscriber's current permissions, accounting for authorization changes that may occur after the subscription was created.

Subscription Response

The response to a successful subscription creation request MUST conform to the application/lws+json media type and MUST contain the following fields:

WebSocket Notifications

The WebSocket notification channel allows a subscriber to receive notifications over a persistent WebSocket connection.

Creating a WebSocket Subscription

When a client creates a WebSocket notification subscription, the subscription body MUST include a type field equal to WebSocketSubscription.

A successful response body has these additional requirements:

{
  "@context": ["https://www.w3.org/ns/lws/v1"],
  "type": "WebSocketSubscription",
  "subscription": "wss://notification.example/ws/1c2d3e4f5a6b"
}
        

Establishing a WebSocket Connection

After obtaining a capability URL, the subscriber opens a WebSocket connection to that URL. The server sends notification envelopes as WebSocket text messages, serialized as JSON.

const ws = new WebSocket(capabilityUrl);
ws.onmessage = (event) => {
  const notification = JSON.parse(event.data);
  // handle notification
};
        

The lifecycle of a WebSocket subscription is tied to the connection. When the WebSocket connection is closed, the subscription is terminated.

Server-Sent Event Notifications

The Server-Sent Event (SSE) notification channel allows a subscriber to receive notifications over a persistent HTTP connection using the EventSource API.

Creating an EventSource Subscription

When a client creates an EventSource notification subscription, the subscription body MUST include a type field equal to EventSourceSubscription.

A successful response body has these additional requirements:

{
  "@context": ["https://www.w3.org/ns/lws/v1"],
  "type": "EventSourceSubscription",
  "subscription": "https://notification.example/sse/0d8b4c2e1a3f"
}
        

Establishing an EventSource Connection

After obtaining a capability URL, the subscriber establishes a persistent SSE connection to that URL. The server responds with Content-Type: text/event-stream and pushes notification envelopes as events. Each event MUST include an id field to support reconnection via the Last-Event-ID header.

const source = new EventSource(capabilityUrl);
source.onmessage = (event) => {
  const notification = JSON.parse(event.data);
  // handle notification
};
        

The lifecycle of an EventSource subscription is tied to the connection. When the connection is closed, the subscription is terminated.

Webhook Notifications

Webhooks enable server-to-server notification delivery. After a webhook subscription is created, the LWS storage server delivers notifications to the subscriber's registered inbox when relevant changes occur.

Creating a Webhook Subscription

When a client creates a webhook notification subscription, the subscription body MUST include a type field equal to WebhookSubscription. In addition, a server MUST support the following fields:

POST /subscriptions HTTP/2
Host: notification.example
Authorization: Bearer <access-token>
Content-Type: application/lws+json

{
  "@context": ["https://www.w3.org/ns/lws/v1"],
  "type": "WebhookSubscription",
  "phase": "PostCommit",
  "topic": [
    "https://storage.example/alice/notes/",
    "https://storage.example/alice/profile"
  ],
  "inbox": "https://receiver.example/hooks/lws",
  "expires": "2026-06-09T12:00:00Z"
}
        

A successful response body has these additional requirements:

HTTP/2 200 OK
Content-Type: application/lws+json
Location: https://notification.example/subscriptions/9e8d7c6b5a4f

{
  "@context": ["https://www.w3.org/ns/lws/v1"],
  "type": "WebhookSubscription",
  "subscription": "https://notification.example/subscriptions/9e8d7c6b5a4f",
  "expires": "2026-06-09T12:00:00Z"
}
        

Webhook Authentication

When a server delivers a notification to an inbox, the subscriber needs to verify that the request is authentic.

A server SHOULD sign each outbound request using HTTP Message Signatures [[!RFC9421]]. When an inbox receives a signed message, it MUST verify the signature using the notification server's public key, discoverable via the storage description resource.

A server that supports HTTP Message Signatures MUST include the signing key in the storage description resource. The key MUST be expressed as a verification method in a verificationMethod array, and MUST be referenced from an authentication verification relationship, following the Controlled Identifiers 1.0 [[!CID-1.0]] data model.

{
  "@context": ["https://www.w3.org/ns/lws/v1"],
  "id": "https://storage.example/",
  "type": "Storage",
  "verificationMethod": [{
    "id": "https://storage.example/#key-20260320",
    "type": "JsonWebKey",
    "controller": "https://storage.example/",
    "publicKeyJwk": {
      "kid": "key-20260320",
      "kty": "EC",
      "crv": "P-256",
      "alg": "ES256",
      "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
      "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
    }
  }],
  "authentication": ["https://storage.example/#key-20260320"],
  "service": [{
    "type": "NotificationService",
    "serviceEndpoint": "https://notification.example/subscriptions",
    "subscriptionType": ["WebhookSubscription"]
  }]
}
        

Signature Requirements

A server that signs webhook delivery requests MUST include at least the following components in the signature base:

  • @method — the HTTP method of the request (always POST).
  • @scheme — prevents downgrade from https to http.
  • @authority — the host of the inbox, preventing delivery to an unintended endpoint.
  • @path — the path component of the inbox URL.
  • content-type — the media type of the request body.
  • content-digest — a digest of the request body, computed according to [[!RFC9530]].

The Signature-Input field MUST include the created and keyid parameters. The keyid value MUST correspond to the id of a key in the verificationMethod array of the storage description resource.

Signature Verification

To verify a webhook signature, a receiver MUST perform the following steps:

  1. Extract the keyid value from the Signature-Input field. The keyid value MUST be a URL with a fragment component.
  2. Remove the fragment component from the keyid URL. The resulting URL is the storage identifier.
  3. Dereference the storage identifier to retrieve the storage description resource. The id property of the top-level document map MUST match the storage identifier.
  4. Find the object in the verificationMethod array whose id property matches either the full keyid URL or the fragment component of the keyid URL.
  5. Verify the HTTP Message Signature using the located verification method.

Subscription Management

The value of the serviceEndpoint property for a NotificationService that supports WebhookSubscription MUST be a URL that supports GET operations to list a subscriber's active webhook subscriptions. The resulting serialization MUST conform to the requirements for LWS Containers [[!LWS-PROTOCOL]]. The response SHOULD support LWS Paging.

Each subscription resource listed in this container MUST support GET and DELETE operations. A GET request returns the current state of the subscription. A DELETE request cancels the subscription.

Notification Delivery

The server sends an HTTP POST request to the registered inbox with the notification envelope as the request body. The request body MUST conform to the application/lws+json media type.

Security Considerations

Privacy Considerations

Vocabulary

This document defines the following terms in the https://www.w3.org/ns/lws# namespace:

Term URI Description
Notification lws:Notification Notification envelope type
NotificationService lws:NotificationService Service type for notification discovery
WebSocketSubscription lws:WebSocketSubscription WebSocket subscription type
EventSourceSubscription lws:EventSourceSubscription Server-Sent Event subscription type
WebhookSubscription lws:WebhookSubscription Webhook subscription type
PostCommit lws:PostCommit Notification phase indicating an event occurred after resource persistence
subscriptionType lws:subscriptionType Supported subscription types on a notification service
subscription lws:subscription URL of the subscription resource
phase lws:phase Lifecycle phase of the notification
activity lws:activity Activity Streams 2.0 payload within a notification
topic lws:topic Array of resource URIs included in a subscription
storage lws:storage URI identifying the LWS storage

This document uses the following terms from external vocabularies, which should be added to the LWS JSON-LD context. The Activity Streams 2.0 terms are included in the LWS context with their source IRIs from the https://www.w3.org/ns/activitystreams# namespace.

Term URI Source
Create https://www.w3.org/ns/activitystreams#Create Activity Streams 2.0
Update https://www.w3.org/ns/activitystreams#Update Activity Streams 2.0
Delete https://www.w3.org/ns/activitystreams#Delete Activity Streams 2.0
object https://www.w3.org/ns/activitystreams#object Activity Streams 2.0
actor https://www.w3.org/ns/activitystreams#actor Activity Streams 2.0
target https://www.w3.org/ns/activitystreams#target Activity Streams 2.0
origin https://www.w3.org/ns/activitystreams#origin Activity Streams 2.0
published https://www.w3.org/ns/activitystreams#published Activity Streams 2.0
verificationMethod https://w3id.org/security#verificationMethod Controlled Identifiers 1.0
authentication https://w3id.org/security#authenticationMethod Controlled Identifiers 1.0
controller https://w3id.org/security#controller Controlled Identifiers 1.0
publicKeyJwk https://w3id.org/security#publicKeyJwk Controlled Identifiers 1.0
inbox http://www.w3.org/ns/ldp#inbox Linked Data Platform
expires https://schema.org/expires Schema.org