Introducing the Web Thing Protocol

I am excited to share that the W3C Web Thing Protocol Community Group has published the first draft of the Web Thing Protocol WebSocket sub-protocol, a dedicated real-time protocol for the Web of Things.

The Web Thing Protocol provides a WebSocket sub-protocol for monitoring and controlling connected devices over the Web, and I believe could be a key enabling technology for an open ecosystem of IoT web services.

The Web of Things

The Web of Things extends the World Wide Web of pages into a web of physical objects in the real world, by giving connected devices URLs on the web. I think of the Web of Things as being to the Internet of Things what the Web is to the Internet, providing a unifying application layer for monitoring and controlling connected devices using web technologies.

Existing building blocks of the Web of Things include:

  • The W3C WoT Thing Description specification which defines a JSON-based metadata format for describing “Things” (connected devices) and their capabilities.
  • The W3C WoT Discovery specification which defines mechanisms for discovering Things on the Web.
  • The draft W3C WoT Profiles specification which defines interoperability profiles that provide out-of-the-box interoperability for Things which conform to certain constraints.

Motivation

When we started building the Krellian Cloud cloud service, we needed a way for our Krellian Hub commercial IoT gateway to stream data from hundreds of sensors in a commercial building to the cloud in real-time, to create a digital twin of the building in order to model how it is being used and identify potential optimisations.

The WoT Profiles specification (of which I am also an editor) currently provides two ways to observe properties and subscribe to events from devices over the Web:

  • HTTP SSE Profile – Using the existing W3C/WHATWG Server-Sent Events standard to upgrade an HTTP request sent from a WoT Consumer to a WoT Thing to an open channel through which data can be pushed to the Consumer as changes occur, but requiring a separate TCP connection per interaction affordance and only providing uni-directional communication.
  • HTTP Webhook Profile – Using the common Webhook design pattern such that a WoT Thing acts as an HTTP client which sends property changes and events to a WoT Consumer acting as an HTTP server, but requiring a new TCP socket to be opened for each message sent, and the reversal of the usual client/server roles of Things and Consumers.

My conclusion from trying to use these Profiles for Krellian Hub and Krellian Cloud was that…

  • Server-Sent Events are good for listening to high frequency events from a small number of devices.
  • Webhooks are good for listening to low frequency events from a large number of devices.

…but neither solution scales well for frequent events from a large number of devices.

You can see a more detailed analysis in this talk that I gave to the Web of Things Community Group last year:

What we really needed was a web-based protocol that could stream a high frequency of messages to and from a large number of devices over a single persistent connection.

Enter WebSockets…

WebSockets

WebSockets are an existing W3C/WHATWG/IETF standard which provide a way to upgrade a standard HTTP request to a full-duplex, persistent, real-time communication channel over a TCP socket.

This can be used to create a versatile and efficient two-way channel with which a WoT Consumer can communicate with one or more WoT Things to carry out the full set of WoT operations. However, since a WebSocket is essentially just a raw TCP socket with no semantics of its own, a sub-protocol needs to be defined in order for a Consumer and Thing to communicate.

The Web Thing Protocol

I started the W3C Web Thing Protocol Community Group in 2019 with the mission of defining “a common protocol for communicating with connected devices over the web, to enable ad-hoc interoperability on the Web of Things”. One of the deliverables of the group was a WebSocket sub-protocol.

We published a Use Cases & Requirements document in 2023, but it is only recently, driven by the commercial needs of my startup, Krellian, and made possible by funding from a StandICT.eu fellowship, that I have been able to dedicate significant time to working on the WebSocket sub-protocol specification.

I’ve been very fortunate to be joined in this endeavour by an active community of contributors, with special thanks going to Ege Korkan from Siemens, Robert Winkler from Deutsche Telekom, Henk Spaaij, Christian Paul, Daniel Peintner and Cristiano Aguzzi for their many code reviews and invaluable feedback on the specification.

The Web Thing Protocol specification complements the existing building blocks of the Web of Things by defining a dedicated real-time protocol for communicating with one or more WoT Things over the Web.

It is a WebSocket sub-protocol specifically designed around the Web of Things information model, making it native to the Web of Things. The sub-protocol defines message payload formats for each of the operation types defined in the WoT Thing Description specification and makes it possible to carry out the full set of WoT operations on one or more WoT Things over a single WebSocket connection.

The Web Thing Protocol is intended to be used by IoT gateways, cloud services, and even directly by connected devices themselves. Often it will likely be used as a web-based abstraction on top of another underlying IoT protocol like Zigbee, Z-Wave, HomeKit, Matter, Modbus or BACnet – to enable an open ecosystem of IoT web services which link together otherwise incompatible IoT platforms.

Part of the inspiration for this sub-protocol comes from the legacy Mozilla IoT Web Thing WebSocket API used by WebThings Gateway (and actually even further back than that to an experimental new architecture for Firefox OS where web applications would use HTTP & WebSockets to access local hardware features of a device). Its design has also been informed by contributors’ experiences using other existing IoT protocols and APIs.

So how does it work..?

Opening a Connection

On the Web of Things, a connected device or “Thing” is described by a Thing Description, which is a piece of JSON metadata that describes a device and its capabilities. The Thing Description contains lists of “properties”, “actions” and “events” (types of “interaction affordances“) that the Thing exposes. Each affordance is given a name, data schemas which describe the format of any input and output data, and “forms” which describe how to communicate with them. A form contains a URL endpoint, a list of operations that endpoint supports (e.g. readproperty, invokaction or subscribeevent), and any other metadata needed to carry out those operations.

To open a connection with a Thing using the Web Thing Protocol, a Consumer (e.g. a web app, native app or cloud service) looks for a form inside an interaction affordance in a Thing Description which has a href member set to a WebSocket URL (i.e. a URL with the ws:// or wss:// URI scheme), and a subprotocol member set to the value "webthingprotocol".

For example, the fragment of a Thing Description below shows the description of a property called “on”, which has a type of “boolean” (i.e. true or false), the URL endpoint "wss://myweblamp.io/", and supports the readproperty, writeproperty, observeproperty and unobserveproperty operations.

"properties": {
    "on": {
      "type": "boolean",
      "title": "On/Off",
      "description": "Whether the lamp is turned on",
      "forms": [
        {
          "href": "wss://myweblamp.io/",
          "op": [
            "readproperty",
            "writeproperty",
            "observeproperty",
            "unobserveproperty"
          ],
          "subprotocol": "webthingprotocol"
        }
      ]
    }
}

To connect to this Thing to carry out an operation on the “on” property, a Consumer would open a WebSocket using the endpoint URL, requesting the use of the webthingprotocol sub-protocol.

Most programming languages already have a WebSocket implementation which makes this easy to do, for example in JavaScript you can use the built-in WebSocket API to open a connection:

const socket = new WebSocket('wss://myweblamp.io/', 'webthingprotocol');

Behind the scenes the WebSocket implementation carries out a special protocol handshake where it asks to upgrade a standard HTTP request to a persistent WebSocket connection, and negotiates what sub-protocol to use.

When a Thing is using the Web Thing Protocol, all interaction affordances of a Thing can share the same WebSocket URL. The specification says that Consumers should re-use a WebSocket connection for all affordances of a Thing, or even multiple Things, which share the same WebSocket URL. This helps to reduce the number of TCP sockets that a Consumer needs to keep open at any one time.

Sending and Receiving Messages

Once a connection has been opened with a Thing, a Consumer can start sending messages to it.

In the Web Thing Protocol, every message is a JSON object. Messages contain some common members such as:

  • thingID – The URI identifying the Thing being communicated with
  • messageID – A UUID for the current message, provided by the sender of the message
  • messageType – One of request (sent from Consumer ➡ Thing), response (sent from Thing ➡ Consumer in response to a request), or notification (pushed from Thing ➡ Consumer)
  • operation – The name of the WoT operation being carried out (e.g. readproperty, invokeaction, subscribeproperty)
  • correllationID – An optional UUID provided by the Consumer, which the Thing must then also provide in response or notification messages relating to the same operation

Messages may also contain other members depending on the type of operation, of which there are some examples in the sections below.

Each operation has a specific lifecycle, which usually consists of either a request followed by a response, or a request followed by a response and then one or more notifications.

Messages are sent to the Thing following the WebSocket protocol, with the JSON object serialised as a string. In JavaScript the WebSocket API makes this easy to do, e.g.:

const request = {
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "messageType": "request",
  "operation": "readproperty",
  "name": "on",
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
}
socket.send(request.toString());

In order to listen to responses from the Thing, in JavaScript the Consumer would register an event listener for a “message” event on the WebSocket object. The response can then be parsed from a string into a JSON object.

socket.addEventListener("message", (event) => {
  console.log("Message from Thing ", event.data);
  const response = JSON.parse(event.data);
  ...
});

Reading and Writing Properties

Things can have “properties” which represent the physical state of a device, such as an on/off state, a temperature value, or brightness value.

To read a property of a Thing, a Consumer sends a request message specifying the readproperty operation and the name of the property to read, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54",
  "messageType": "request",
  "operation": "readproperty",
  "name": "on",
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
}

The Thing then responds with a message containing the value of the property, with a type and format conforming to the data schema of the property in the Thing Description e.g.:

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "79057736-3e0e-4dc3-b139-a33051901ee2",
  "messageType": "response",
  "operation": "readproperty",
  "name": "on",
  "value": true,
  "timestamp": "2024-01-13T23:20:50.52Z",
  "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
}

Similarly, to write a property of a Thing a Consumer would send a request message specifying the writeproperty operation and the name and value of the property to write, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "97d22676-6d45-4435-aef5-dd87467a0c44",
  "messageType": "response",
  "operation": "writeproperty",
  "name": "on",
  "value": true,
  "correlationID": "f6cf46a8-9c96-437e-8b53-925b7679a990"
}

The Thing then responds, echoing back the value that was set, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "db25fe4f-bee8-43a7-8ff0-3a1ff6e620b0",
  "messageType": "response",
  "operation": "writeproperty",
  "name": "on",
  "value": true,
  "timestamp": "2024-01-13T23:20:50.52Z",
  "correlationID": "f6cf46a8-9c96-437e-8b53-925b7679a990"
}

Invoking Actions

As well as properties, Things can have “actions”, which are functions that can be carried out on a device. Actions are used when the setting of a property alone is not sufficient to affect a required change in state, e.g. to describe a function which takes a period of time to complete or has a different outcome based on provided arguments or current state.

To invoke an action on a Thing a Consumer sends a request message specifying the invokeaction operation, the the name of the action to be invoked, and optional input data conforming to the input data schema specified in the Thing Description, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "ff29aac5-062a-4583-9044-be2d45d6ad57",
  "messageType": "request",
  "operation": "invokeaction",
  "name": "fade",
  "input": {
    "level": 100,
    "duration": 5
  },
  "correlationID": "50d5f441-cb07-4513-80b4-cd062cddc1e0"
}

There are two types of responses to an invokeaction request – a synchronous response which includes the output (if any) of the action directly in the response (conforming to the output data schema in the Thing Description), or an asynchronous response which provides an actionID which can then be used to query or cancel the status of the action using follow-up operations. Below is an example of the simpler synchronous response:

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "e47c3727-d68b-4284-a2cc-5883d8fa43a4",
  "messageType": "response",
  "operation": "invokeaction",
  "name": "fade",
  "output": true,
  "timestamp": "2025-09-03T18:14:55.541Z",
  "correlationID": "50d5f441-cb07-4513-80b4-cd062cddc1e0"
}

Asynchronous action responses may be used for longer running actions, or where multiple instances of an action are invoked concurrently or in a queue (e.g. a printer queue).

Subscribing to Events

In addition to properties and actions, Things may have “events” which are emitted by a device that are not just a change in the value of a property, e.g. if a device is overheating or is about to reboot.

To subscribe to an event a Consumer sends a request message to a Thing specifying the subscribeevent operation and the name of the event to be subscribed to, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "c347b27b-f567-4faf-b9b7-4c9e577b10fa",
  "messageType": "request",
  "operation": "subscribeevent",
  "name": "overheated",
  "correlationID": "206a6935-5978-47a5-a327-1ce1c656728b"
}

Once an event subscription has been registered successfully, the Thing then responds with a response message confirming the name of the event that has been subscribed to, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "cf13d49b-abe8-45bc-8a3c-b4df9d892366",
  "messageType": "response",
  "operation": "subscribeevent",
  "name": "overheated",
  "correlationID": "206a6935-5978-47a5-a327-1ce1c656728b"
}

Whilst an event subscription is active, whenever an event of that name is emitted by the device, the Thing sends a notification message to the Consumer containing the name of the event that occurred, and an optional data payload which confirms to the data schema defined for that event in the Thing Description, e.g.

{
  "thingID": "https://mythingserver.com/things/mylamp1",
  "messageID": "b15e2e3f-c8b7-4a76-81c3-0f110edf51ca",
  "messageType": "notification",
  "operation": "subscribeproperty",
  "name": "overheated",
  "data": 90,
  "timestamp": "2025-10-08T11:15:53.376Z",
  "correlationID": "206a6935-5978-47a5-a327-1ce1c656728b"
}

Other Operations

The above are just a few of the 18 different operations defined in the Web Thing Protocol specification. Other operations include observing and unobserving properties, querying and cancelling actions, unsubscribing from events, and various batch operations which act on multiple interaction affordances at once.

Next Steps

The Web Thing Protocol is still very much in the early stages, being incubated in the Web Thing Protocol Community Group before hopefully joining a standards track at the W3C (or IETF) to be fully standardised.

Today the Web Thing Protocol Community Group is sending out a call for implementations of the Web Thing Protocol, so that we can put it through its paces in the real world and improve its design through feedback based on real implementation experience and interoperability testing.

One of those implementations will be the WebThings open source project, where the Web Thing Protocol is a critical part of my vision of building a smart building operating system using web technologies.

There’s a lot more we could do to improve the Web Thing Protocol WebSocket sub-protocol, e.g. by adding features like rate-limiting, traceability and quality of service, but potentially also lots of refinements based on implementation feedback.

The charter of the Web Thing Protocol Community Group also covers defining an HTTP sub-protocol, and evaluating the need for other potential Web of Things sub-protocols.

Anyone can join a W3C Community Group for free, so please join us on the Web Thing Protocol Community Group to further refine this powerful new Web of Things protocol.

You can read the latest draft of the specification, subscribe to our public mailing list, file issues on GitHub and chat with us in the #web-thing-protocol channel on the Web of Things Community Discord server.

As with all W3C specifications the Web Thing Protocol is a royalty free open standard which anyone is free to use, so we can’t wait to see what you do with it!