This document defines a WebSocket sub-protocol called the Web Thing Protocol, for monitoring and controlling connected devices over the World Wide Web. The Web Thing Protocol is intended as a dedicated real-time protocol for the Web of Things, to enable a WoT Consumer to communicate with one or more WoT Things over a WebSocket connection.
This document defines a WebSocket [[WEBSOCKETS-PROTOCOL]] sub-protocol for monitoring and controlling connected devices over the Web.
The Web of Things (WoT) is a collection of standardised technology building blocks that help provide interoperability on the Internet of Things (IoT). The WoT Thing Description specification [[wot-thing-description11]] defines a metadata format for describing the capabilities of "Things" (connected devices) on the Web. The WoT Discovery specification [[wot-discovery]] defines mechanisms for discovering Things on the Web. This specification complements those building blocks by defining a dedicated real-time protocol for communicating with Things over the Web.
WebSockets [[WEBSOCKETS-PROTOCOL]] provide a way to upgrade a standard HTTP [[HTTP11]] request to a full-duplex, persistent, real-time communication channel over a single TCP connection. This can be used to create a versatile and efficient two-way channel with which a WoT Consumer can communicate with one or more Things [[wot-architecture11]] 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.
Whilst many other WebSocket sub-protocols exist, what makes the Web Thing Protocol unique is that it is specifically designed around the Web of Things information model and set of operations [[wot-thing-description11]], as well as being targeted specifically at Web of Things use cases [[wot-usecases]]. It can therefore be thought of as being native to the Web of Things.
The sub-protocol defines message payload formats for each of the well-known operation types defined in the WoT interaction model [[wot-architecture11]], and other messages needed for WebSocket communication.
This specification is intended to complement deliverables of the WoT Working Group, including WoT Architecture [[wot-architecture11]], WoT Thing Description [[wot-thing-description11]], WoT Discovery [[wot-discovery]], WoT Binding Templates [[wot-binding-templates]] and WoT Profile [[wot-profile]]. It is intended to implement use cases and requirements defined in the Web Thing Protocol Use Cases & Requirements community report.
Whilst this document is not on a standards track, the Web Thing Protocol is intended to eventually join a standards track at the W3C or another standards body such as the IETF.
Fundamental WoT terminology such as Thing or Web Thing, Consumer or WoT Consumer, WoT Thing Description or Thing Description, Interaction Model, Interaction Affordance, Property, Action and Event are defined in the Terminology section of the WoT Architecture specification [[wot-architecture11]].
The Web Thing Protocol is intended to operate within three key deployment models depending on whether a Web Thing is hosted by a connected device, by an on-premises Web of Things gateway, or an off-premises Web of Things cloud service.
These three deployment models are common across a range of different application domains.
For a broader discussion of deployment models for the Web of Things in general, see Common Deployment Patterns in Web of Things (WoT) Architecture 1.1 [[wot-architecture11]].
The direct deployment model is when a Consumer communicates directly with an IoT device using the Web Thing Protocol.
In this deployment model, the IoT device itself must act as an HTTP and WebSockets server which serves its own Thing Description and can maintain a WebSocket connection with a Consumer.
The gateway deployment model is when a Consumer communicates with a connected device via a gateway on the same premises as the device, which bridges another protocol to the Web Thing Protocol.
In this deployment model, a gateway acts as an HTTP and WebSockets server which exposes a Web Thing for a connected device and translates WebSocket messages into messages of the device's native protocol (e.g. Zigbee, Z-Wave, Matter, Modbus or BACnet) over a local area network (LAN) or personal area network (PAN).
A gateway can also act as a Consumer which consumes Web Things using the direct deployment model, and can be used to proxy a Web Thing from one network to another.
The cloud deployment model is when a Consumer communicates with an IoT device via cloud service on the internet, which communicates with the device using some other protocol on the back end.
In this deployment model, a cloud service hosted in a data centre acts as an HTTP and WebSockets server which exposes a Web Thing for an IoT device and translates WebSocket messages into messages using another IoT protocol (e.g. AMQP or MQTT).
The cloud deployment model differs from the gateway deployment model in that the Web Thing is hosted on different premises to the IoT device it represents and communicates with the device over the internet.
A cloud service can also act as a Consumer which consumes Web Things that use the direct or gateway deployment model, and can be used to proxy a Web Thing from one origin to another.
In order to communicate with a Web Thing, a WoT Consumer [[wot-architecture11]] MUST locate one or more WebSocket [[WEBSOCKETS-PROTOCOL]] endpoints provided by the Thing for a given set of Interaction Affordances [[wot-thing-description11]].
The URL of a WebSocket endpoint to be used for a given interaction MUST be obtained from a Thing Description [[wot-architecture11]] by locating a Form inside the corresponding Interaction Affordance for which:
base
URL
[[wot-thing-description11]] where applicable, the URI scheme [[RFC3986]] of the value of its href
member
[[wot-thing-description11]] is "ws"
or &"wss"
subprotocol
member has a value of "webthingprotocol"
To open a WebSocket on a Thing, an HTTP GET
request [[RFC9110]] MUST be upgraded to
a WebSocket connection using a standard WebSocket protocol handshake [[WEBSOCKETS-PROTOCOL]],
specifying the "webthingprotocol" sub-protocol.
GET wss://mythingserver.com/things/robot Host: mythingserver.com Origin: https://mythingserver.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: webthingprotocol Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: webthingprotocol
Sub-protocol name to be confirmed, see IANA Considerations.
A WebSocket can be opened from a web page using the JavaScript WebSocket API [[WEBSOCKETS-API]] which will take care of the handshake detailed above and allow messages to be sent and received.
const socket = new WebSocket('wss://mywebthingserver/things/robot', 'webthingprotocol');
A single WebSocket [[WEBSOCKETS-PROTOCOL]] connection from a WoT Consumer MAY be shared between multiple Interaction Affordances of a Thing. A single WebSocket connection from a WoT Consumer MAY also be shared between multiple Things.
Before opening a new WebSocket connection, a WoT Consumer SHOULD check whether it already has an open connection to the same WebSocket endpoint URL.
If an existing connection to the same WebSocket endpoint URL exists, then that connection SHOULD be re-used rather than opening an additional socket.
If an existing connection to the same WebSocket endpoint URL exists but is using a different set of credentials
for its given SecurityScheme
[[wot-thing-description11]] (e.g. a different Bearer Token), then the WoT Consumer MUST NOT re-use the
connection.
All messages MUST be a JSON object [[JSON]].
Member | Type | Assignment | Description |
---|---|---|---|
thingID |
string | Mandatory | The ID (URI) of the Thing to which the message relates. |
messageID |
string | Mandatory | A unique identifier (UUID) for the current message. |
messageType |
string | Mandatory | A string which denotes the type of message (one of request , response or
notification ).
|
operation |
string | Mandatory | A string which denotes the type of WoT operation
[[wot-thing-description11]] to which the message relates (one of
readproperty , writeproperty , observeproperty ,
unobserveproperty , invokeaction , queryaction ,
cancelaction , subscribeevent , unsubscribeevent ,
readallproperties , writeallproperties , readmultipleproperties ,
writemultipleproperties , observeallproperties ,
unobserveallproperties , subscribeallevents , unsubscribeallevents , or
queryallactions ).
|
correlationID |
string | Optional | A unique identifer (UUID) which is shared between messages corresponding to the same operation, e.g. a request and a response. |
The top level JSON object MUST contain a thingID
member with the value set to a unique identifier of
the Web Thing to which the message relates.
If the Thing Description of the Web Thing contains an id
member then the value of that
id
member MUST be used as the unique identifier assigned to thingID
.
If the Thing Description of the Web Thing does not contain an id
member then the URL
[[URL]] from which the Thing Description was retrieved MAY be used as the thingID
value
instead. The value of the thingID
member MUST be a valid URI [[URI]] serialised as a string.
The top level JSON object MUST contain a messageID
member with the value set to a unique identifier
for the current message in UUIDv4 format [[rfc9562]].
The top level JSON object MUST contain a messageType
member, with its value set to one of
request
, response
or notification
.
Message type | Direction | Description |
---|---|---|
request |
Consumer ➡ Thing | A message sent from a Consumer to a Thing (e.g. to request a reading of a property, invoke an action or subscribe to an event) |
response |
Thing ➡ Consumer | A message sent from a Thing to a Consumer in response to a request (e.g. to respond with a property reading, provide the final response to an action or confirm a subscription to an event) |
notification |
Thing ➡ Consumer | A message pushed from a Thing to a Consumer (e.g. an event, change in property value, or change in action state) |
The top level JSON object MUST contain an operation
member, with its value set to one of the
well-known WoT operation names from
the Thing Description specification [[wot-thing-description11]].
The lifecycle of an operation consists of a series of messages in a sequence (e.g. a request followed by a response, or a request followed by a response then one or more notifications). Each type of operation follows a particular sequence of message types, outlined in the table below:
The top level JSON object MAY contain a correlationID
member which provides a unique identifier in
UUIDv4 format [[rfc9562]] which is shared between messages corresponding to the same WoT operation (e.g. a
property read request and response, or an event subscription request and event notification). If a request message
contains a correlatonID
member then any response and notification messages which correspond to the
same operation MUST also include a correlationID
member with the same value.
All date and time values MUST use the date-time
format
defined in [[RFC3339]].
2025-01-15T12:08:00.42Z
In order to reduce ambiguity, RFC 3339 only permits an hour with a value between 00 and 23 (not 24), and time zones expressed as a numerical offset relative to UTC. The suffix "Z" when applied to a time denotes a UTC offset of 00:00.
readproperty
To request a property reading from a Thing, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "readproperty" | A string which denotes that this message relates to a readproperty operation. |
name |
string | Mandatory | The name of the Property to read, as per its key in the properties member of
the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "c370da58-69ae-4e83-bb5a-ac6cfb2fed54", "messageType": "request", "operation": "readproperty", "name": "on", "correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to readproperty
it MUST
attempt to read the value of the Property with the given name
.
Upon successfully reading the value of the requested Property, the Thing MUST send a message to the
requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "readproperty" | A string which denotes that this message relates to a readproperty operation.
|
name |
string | Mandatory | The name of the Property being read, as per its key in the properties member of
the
Thing Description.
|
value |
any | Mandatory | The current value of the Property being read, with a type and structure conforming to the data schema of the corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the property reading took place. |
{ "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" }
writeproperty
To set the value of a Property of a Thing, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "writeproperty" | A string which denotes that this message relates to a writeproperty operation. |
name |
string | Mandatory | The name of the Property whose value should be set, as per its key in the
properties member of the Thing Description.
|
value |
any | Mandatory | The desired new value of the Property, with a type and structure conforming to the data schema of the corresponding PropertyAffordance in the Thing Description. |
{ "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" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to writeproperty
it MUST
attempt to set the Property with the name provided in the name
member to
the value provided in the value
member.
Upon successfully submitting the value of the requested Property, the Thing MUST send a message to the
requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "writeproperty" | A string which denotes that this message relates to a writeproperty operation.
|
name |
string | Mandatory | The name of the Property being written, as per its key in the properties member of
the
Thing Description.
|
value |
any | Optional | The value which has been successfully assigned to the Property being written, with a type and structure conforming to the data schema of the corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the property write took place. |
{ "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" }
If the Thing can confirm that the requested value has been set successfully then the response message
to a writeproperty
request SHOULD contain a value
member with its value set to the
value which has been successfully set.
If the Thing can not confirm that the requested value has been set successfully (e.g. in the case of a
write-only property or a device that is temporarily asleep so the write has been queued) then
the response message to a writeproperty
request MUST NOT contain a
value
member.
If a Consumer attempts to set the value of a numerical Property to a value which conforms to the Property's data schema but to a level of precision the Thing does not support, (e.g. 3.14159), then the Thing MAY respond with the actual value set (e.g. 3.14).
readallproperties
To request a reading of all of a Thing's readable Properties at once, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "readallproperties" | A string which denotes that this message relates to a readallproperties operation. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "e144349c-5cd7-4002-938f-aac5adf99789", "messageType": "request", "operation": "readallproperties", "correlationID": "c08075a4-d53c-4f51-b251-a6b6922c2b1f" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to readallproperties
it MUST
attempt to read the value of all of its readable Properties (i.e. all
Property Affordances where
writeOnly
[[wot-thing-description11]] is not set to true
).
Upon successfully reading the value of all of its readable Properties, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "readallproperties" | A string which denotes that this message relates to a readallproperties operation.
|
values |
object | Mandatory | The values of all Properties contained in an object keyed by Property name, with the value of each Property conforming to the data schema of the corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the property readings took place. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "79057736-3e0e-4dc3-b139-a33051901ee2", "messageType": "response", "operation": "readallproperties", "values": { "on": true, "level": 50 }, "timestamp": "2025-05-22T17:15:35.636Z", "correlationID": "c08075a4-d53c-4f51-b251-a6b6922c2b1f" }
If a Thing receives a readallproperties
request message from a Consumer
but the reading of at least one of the properties unexpectedly fails, then it MUST send an
error response to the Consumer with status
set to
500
.
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "1876e481-5fc2-4b39-93b4-1f434f7b6a47",
"messageType": "response",
"operation": "readallproperties",
"error": {
"status": 500,
"type": "https://w3c.github.io/web-thing-protocol/errors#500",
"title": "Internal Server Error"
"detail": "Reading the 'level' property unexpectedly failed"
}
"timestamp": "2025-05-22T17:15:36.636Z",
"correlationID": "c08075a4-d53c-4f51-b251-a6b6922c2b1f"
}
If a readallproperties
operation only partially fails (i.e. some Properties can be
read but others can not), a Thing MAY include the successfully read values in the error
response, in a values
member keyed by Property name.
readmultipleproperties
To request a reading of multiple readable Properties of a Thing at once, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "readmultipleproperties" | A string which denotes that this message relates to a readmultipleproperties operation.
|
names |
array | Mandatory | An array of strings specifying the names of the Properties to read, as per their keys in the
properties member of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "dacaea0e-43c2-4fc0-bd66-5a807ca52324", "messageType": "request", "operation": "readmultipleproperties", "names": ["on", "level"], "correlationID": "c0503f71-288f-4460-b308-c4cc0009cd89" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to readmultipleproperties
it MUST
attempt to read the value of all the Properties specified in the names
member of the
message.
Upon successfully reading the values of the specified Properties, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "readmultipleproperties" | A string which denotes that this message relates to a readmultipleproperties operation.
|
values |
object | Mandatory | The values of the read Properties contained in an object keyed by Property name, with the value of each Property conforming to the data schema of the corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the property readings took place. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "695a2626-be5f-4f91-8719-105abc52aa0f", "messageType": "response", "operation": "readmultipleproperties", "values": { "on": true, "level": 50 }, "timestamp": "2025-05-22T18:04:33.531Z", "correlationID": "c0503f71-288f-4460-b308-c4cc0009cd89" }
If a Thing receives a readmultipleproperties
request message from a Consumer with
a
names
member which is empty, contains an invalid Property name, or the name of a
writeOnly
[[wot-thing-description11]] Property, then it MUST send an error response
to
the Consumer with status
set to 400
.
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "72e6f397-8ae9-4882-ac7d-303a953527a5",
"messageType": "response",
"operation": "readmultipleproperties",
"error": {
"status": 400,
"type": "https://w3c.github.io/web-thing-protocol/errors#400",
"title": "Bad Request"
"detail": "No property found with the name 'volume'"
}
"timestamp": "2025-05-22T18:04:33.531Z",
"correlationID": "c0503f71-288f-4460-b308-c4cc0009cd89"
}
If a Thing receives a readmultipleproperties
request message from a Consumer with
a
names
member containing valid Property names but the reading of at least one of the
properties
unexpectedly fails, then it MUST send an error response to
the Consumer with status
set to 500
.
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "cf0240ca-e21c-49b8-bca4-e27002525ea1",
"messageType": "response",
"operation": "readmultipleproperties",
"error": {
"status": 500,
"type": "https://w3c.github.io/web-thing-protocol/errors#500",
"title": "Internal Server Error"
"detail": "Reading the 'level' property unexpectedly failed"
}
"timestamp": "2025-05-22T18:04:33.531Z",
"correlationID": "c0503f71-288f-4460-b308-c4cc0009cd89"
}
If a readmultipleproperties
operation only partially fails (i.e. some Properties can be
read but others can not), a Thing MAY include the successfully read values in the error
response, in a values
member keyed by Property name.
writeallproperties
To set the value of all Properties of a Thing at once, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "writeallproperties" | A string which denotes that this message relates to a writeallproperties operation.
|
values |
object | Mandatory | An object keyed by Property name, containing the desired value of each Property, with a type and structure conforming to the data schema of each corresponding PropertyAffordance in the Thing Description. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "60c33fc4-ed99-446e-a2e3-f3b5be82c34b", "messageType": "request", "operation": "writeallproperties", "values": { "on": true, "level": 75 }, "correlationID": "551aaa52-ff95-4fb5-a657-2b5f430d47ff" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to writeallproperties
it MUST
attempt to write the values of all writeable Properties using the values specified in the
values
member of the message.
Upon successfully writing the values of all writeable Properties, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "writeallproperties" | A string which denotes that this message relates to a writeallproperties operation.
|
values |
object | Mandatory | An object keyed by Property name, containing the values which have been successfully assigned to writeable Properties, with the type and structure of each value conforming to the data schema in its corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the property readings took place. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "74022347-72c2-4fa1-9e34-1a662bdff85e", "messageType": "response", "operation": "writeallproperties", "values": { "on": true, "level": 75 }, "timestamp": "2025-08-13T17:11:00.531Z", "correlationID": "551aaa52-ff95-4fb5-a657-2b5f430d47ff" }
If a Thing receives a writeallproperties
request message from a Consumer which
does not contain values for all writeable properties, contains an invalid Property name, contains the
name
of a readOnly
[[wot-thing-description11]] Property, or contains a value which does not conform to the data schema of
the
corresponding PropertyAffordance then it MUST send an error response
to the Consumer with status
set to 400
.
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "6dcfce6a-b0cd-48e7-992a-b96d5cb2b872",
"messageType": "response",
"operation": "writeallproperties",
"error": {
"status": 400,
"type": "https://w3c.github.io/web-thing-protocol/errors#400",
"title": "Bad Request"
"detail": "No property found with the name 'volume'"
}
"timestamp": "2025-08-13T18:31:43.531Z",
"correlationID": "551aaa52-ff95-4fb5-a657-2b5f430d47ff"
}
If a Thing receives a writeallproperties
request message from a Consumer with
a values
member containing valid names and values for all writeable Properties, but the
writing of at least one of the properties unexpectedly fails, then it MUST send an
error response to the Consumer with status
set to
500
.
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "35caf0d3-05f6-4a15-8ddf-a765b5280b06",
"messageType": "response",
"operation": "writeallproperties",
"error": {
"status": 500,
"type": "https://w3c.github.io/web-thing-protocol/errors#500",
"title": "Internal Server Error"
"detail": "Writing the 'level' property unexpectedly failed"
}
"timestamp": "2025-08-13T19:04:36.426Z",
"correlationID": "551aaa52-ff95-4fb5-a657-2b5f430d47ff"
}
If a writeallproperties
operation only partially fails (i.e. some Properties are
written successfully but others are not), a Thing MUST include the successfully written values
in the error response, in a values
member keyed by Property name.
writemultipleproperties
To set the value of multiple Properties of a Thing at once, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "writemultipleproperties" | A string which denotes that this message relates to a writemultipleproperties operation.
|
values |
object | Mandatory | An object keyed by Property name, containing the desired value of each Property, with a type and structure conforming to the data schema of each corresponding PropertyAffordance in the Thing Description. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "02c576a2-5adf-4924-b2c8-2222396a3d5c", "messageType": "request", "operation": "writemultipleproperties", "values": { "on": false, "level": 25 }, "correlationID": "e851583f-e152-4cad-9bb3-7bd3b24f8525" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to writemultipleproperties
it MUST
attempt to write the values of the Properties specified in the values
member of
the message.
Upon successfully writing the values of the specified Properties, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "writemultipleproperties" | A string which denotes that this message relates to a writemultipleproperties operation.
|
values |
object | Mandatory | An object keyed by Property name, containing the values which have been successfully assigned to Properties, with the type and structure of each value conforming to the data schema in its corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the property readings took place. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "9c5d6eaf-4624-4464-8cc0-09472443ae82", "messageType": "response", "operation": "writemultipleproperties", "values": { "on": false, "level": 25 }, "timestamp": "2025-08-17T18:35:00.246Z", "correlationID": "e851583f-e152-4cad-9bb3-7bd3b24f8525" }
If a Thing receives a writemultipleproperties
request message from a Consumer with
a values
member which is empty, contains an invalid Property name, contains the name of a
readOnly
[[wot-thing-description11]] Property, or contains a value which does not conform to the data schema of
the corresponding PropertyAffordance then it MUST send an error response
to the Consumer with status
set to 400
.
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "b0f9f6a3-3558-4680-8b3d-4c017c669433",
"messageType": "response",
"operation": "writemultipleproperties",
"error": {
"status": 400,
"type": "https://w3c.github.io/web-thing-protocol/errors#400",
"title": "Bad Request"
"detail": "No property found with the name 'volume'"
}
"timestamp": "2025-08-19T18:38:35.613Z",
"correlationID": "e851583f-e152-4cad-9bb3-7bd3b24f8525"
}
If a Thing receives a writemultipleproperties
request message from a Consumer with
a values
member containing valid names and values of writeable Properties, but the
writing of at least one of the properties unexpectedly fails, then it MUST send an
error response to the Consumer with status
set to
500
.
If a writemultipleproperties
operation only partially fails (i.e. some Properties are
written
successfully but others are not), a Thing MUST include the successfully written values in the error
response, in a values
member keyed by Property name.
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "8620b30a-03db-4332-a139-53bef96e4e47",
"messageType": "response",
"operation": "writemultipleproperties",
"error": {
"status": 500,
"type": "https://w3c.github.io/web-thing-protocol/errors#500",
"title": "Internal Server Error"
"detail": "Writing the 'level' property unexpectedly failed"
},
"values": {
"on": "true"
},
"timestamp": "2025-08-19T18:40:35.360Z",
"correlationID": "e851583f-e152-4cad-9bb3-7bd3b24f8525"
}
observeproperty
To observe a Property, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "observeproperty" | A string which denotes that this message relates to an observeproperty operation. |
name |
string | Mandatory | The name of the Property to be observed, as per its key in the properties member
of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "f160098e-8c71-4025-9cb8-49fa038f5076", "messageType": "request", "operation": "observeproperty", "name": "level", "correlationID": "3b380f3c-4fb8-4dc0-8ef2-ef2c2b528931" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to observeproperty
it MUST
attempt to register an observation subscription to the Property with the given name
.
If a Thing receives an observeproperty
request for a Property which already has
an active observation subscription over the same WebSocket connection, the Thing MUST
replace the existing observation subscription with a new one, using the correllationID
of the
last observeproperty
request received (i.e. last subscription wins).
If an observeproperty
request replaces an observation subscription that was previously registered
by an observeallproperties
operation then that does not impact active observation subscriptions
for other Properties, which should continue to be registered using their current correllationID
.
Conversely, an observeallproperties
request may replace an observation subscription previously
registered by an observeproperty
operation over the same WebSocket connection, which would impact
any active observation subscriptions for other Properties on the same connection.
Upon successfully registering an observation subscription to the requested Property, the Thing MUST
send a message to the requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "observeproperty" | A string which denotes that this message relates to an observeproperty operation.
|
name |
string | Mandatory | The name of the Property being observed, as per its key in the properties member
of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "4df7faaf-6099-4766-a615-cb49883edfd4", "messageType": "response", "operation": "observeproperty", "name": "level", "correlationID": "3b380f3c-4fb8-4dc0-8ef2-ef2c2b528931" }
Whilst a property observation subscription is registered, whenever a change in the value of the observed Property occurs, the Thing MUST send a message to the observing Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "notification" | A string which denotes that this message is a notification sent from a Thing to a Consumer. |
operation |
string | "observeproperty" | A string which denotes that this message relates to an observeproperty operation.
|
name |
string | Mandatory | The name of the Property being observed, as per its key in the properties member
of the Thing Description.
|
value |
any | Mandatory | The current value of the Property being observed, with a type and structure conforming to the data schema of the corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the change in value took place. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "c9cce575-1923-441b-88cb-e58d4f7e615f", "messageType": "notification", "operation": "observeproperty", "name": "level", "value": 42, "timestamp": "2025-08-20T11:54:25.535Z", "correlationID": "3b380f3c-4fb8-4dc0-8ef2-ef2c2b528931" }
Because a duplicate observation subscription replaces a previous subscription to the same Property,
a Thing should only ever send one notification message per WebSocket connection for each change in
property value. Notification messages always use the correllationID
of the currently
active observation subscription (which may have replaced a previous subscription).
unobserveproperty
To stop observing a Property, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "unobserveproperty" | A string which denotes that this message relates to an unobserveproperty operation.
|
name |
string | Mandatory | The name of the Property to stop observing, as per its key in the properties
member
of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "85afa9c1-b5d1-479a-9a80-ce272b156dff", "messageType": "request", "operation": "unobserveproperty", "name": "level", "correlationID": "6180212a-8b28-49fa-9bfc-89c8c43ce887" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to unobserveproperty
it MUST
attempt to remove any observation subscription to the Property with the given name
over the current WebSocket connection.
If an unobserveproperty
request removes an observation subscription that was previously
registered by an observeallproperties
operation then that does not impact active observation
subscriptions for other Properties that may have been registered by the same operation.
Upon successfully removing an observation subscription to the requested Property, the Thing MUST
send a message to the requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "unobserveproperty" | A string which denotes that this message relates to an unobserveproperty operation.
|
name |
string | Mandatory | The name of the Property no longer being observed, as per its key in the
properties member of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "2e6a9454-ac50-4c8a-9c1e-4380c5a05963", "messageType": "response", "operation": "unobserveproperty", "name": "level", "correlationID": "6180212a-8b28-49fa-9bfc-89c8c43ce887" }
If a Thing receives an unobserveproperty
request for a Property, but that
Property does not have any active observation subscriptions over the current WebSocket connection, then
it SHOULD still send a success response since the intended outcome has been achieved.
observeallproperties
To observe all Properties of a Thing, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "observeallproperties" | A string which denotes that this message relates to an observeallproperties operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "b8988514-5a26-474f-9895-df20db848dd1", "messageType": "request", "operation": "observeallproperties", "correlationID": "e8948c71-b460-46f8-b4e5-f93b04c6e67b" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to observeallproperties
it MUST
attempt to register an observation subscription for each Property of the Thing.
If a Thing receives an observeallproperties
request and there are already active
Property observation subscriptions on the same WebSocket connection, the Thing MUST replace
the existing observation subscriptions with new ones, using the correllationID
of the
observeallproperties
request received (i.e. last subscription wins).
The observeallproperties
operation can be thought of as a batch operation which adds individual
observation subscriptions to each Property. Those observation subscriptions may later be individually replaced
by new observation subscriptions using observeproperty
operations, or individually cancelled
using unobserveproperty
operations.
Upon successfully registering an observation subscription to all Properties, the Thing MUST
send a message to the requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "observeallproperties" | A string which denotes that this message relates to an observeallproperties operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "0855374a-328d-4a7a-8d46-560d915985f0", "messageType": "response", "operation": "observeallproperties", "correlationID": "e8948c71-b460-46f8-b4e5-f93b04c6e67b" }
Whilst an observation subscription to all properties is registered, whenever a change in the value of any Property of the Thing occurs, the Thing MUST send a message to the observing Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "notification" | A string which denotes that this message is a notification sent from a Thing to a Consumer. |
operation |
string | "observeallproperties" | A string which denotes that this message relates to an observeallproperties operation.
|
name |
string | Mandatory | The name of the Property whose value changed, as per its key in the properties
member of the Thing Description.
|
value |
any | Mandatory | The current value of the Property that changed, with a type and structure conforming to the data schema of the corresponding PropertyAffordance in the Thing Description. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the change in value took place. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "c351f404-f058-48fe-9c6c-9555fffb4619", "messageType": "notification", "operation": "observeallproperties", "name": "level", "value": 42, "timestamp": "2025-09-03T12:24:54.532Z", "correlationID": "e8948c71-b460-46f8-b4e5-f93b04c6e67b" }
Because a duplicate observation subscription replaces a previous subscription to the same Property,
a Thing should only ever send one notification message per WebSocket connection for each change in
property value. Notification messages always use the correllationID
of the currently
active observation subscription (which may have replaced a previous subscription).
unobserveallproperties
To stop observing all Properties of a Thing, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "unobserveallproperties" | A string which denotes that this message relates to an unobserveallproperties operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "38794cc1-ff97-494c-b662-7fcf951802a7", "messageType": "request", "operation": "unobserveallproperties", "correlationID": "51f2692b-b707-4097-9077-e313761406aa" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to unobserveallproperties
it MUST
attempt to remove observation subscriptions from all Properties of the Thing over the
current WebSocket connection.
The unobserveallproperties
operation can be thought of as a batch operation which removes
all individual observation subscriptions that have been registered with a Thing over a given WebSocket
connection, regardless of whether those observation subscriptions were registered using an
observeproperty
operation or an observeallproperties
operation.
Upon successfully removing observation subscriptions from all Properties, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "unobserveallproperties" | A string which denotes that this message relates to an unobserveallproperties operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "ec6ad624-74fe-402e-a009-114b779d1bb0", "messageType": "response", "operation": "unobserveallproperties", "correlationID": "51f2692b-b707-4097-9077-e313761406aa" }
If a Thing receives an unobserveallproperties
request but the Thing does not have
any active observation subscriptions over the current WebSocket connection, then it SHOULD still send a
success response since the intended outcome has been achieved.
invokeaction
To invoke an Action on a Thing, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "invokeaction" | A string which denotes that this message relates to an invokeaction operation. |
name |
string | Mandatory | The name of the Action to invoke, as per its key in the actions member of
the Thing Description.
|
input |
any | Optional | An input to the Action, with a type and structure conforming to the data schema defined in the
input member of the corresponding ActionAffordance in the Thing Description.
|
{ "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" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to invokeaction
it MUST
attempt to invoke the Action with the given name
, using the provided input
(if any).
If a Thing receives an invokeaction
request for a synchronous Action
(an Action who's ActionAffordance in the Thing's Thing Description has a
synchronous
member set to true
),
then upon successful completion of the Action the Thing MUST send a
synchronous action response.
If a Thing receives an invokeaction
request for an asynchronous Action
(an Action who's ActionAffordance in the Thing's Thing Description has a
synchronous
member set to false
),
then upon acceptance of the invokeaction
request the Thing MUST send an
asynchronous action response.
If the ActionAffordance describing an Action in a Thing Description does not have a
synchronous
member, then when the Thing receives
an invokeaction
request for that Action it MAY respond with either a
synchronous action response or an
asynchronous action response.
For synchronous Actions a Thing includes the output
of the Action directly in the response
to an invokeaction
request, and therefore does not respond to that request until the Action is
complete.
For asynchronous Actions a Thing responds immediately to an invokeaction
request with a
status
member containing a unique actionID
, which can then be used to query or
cancel the Action instance using a separate queryaction
or cancelaction
operation
respectively.
If a Consumer sends an invokeaction
request for an Action whose ActionAffordance has a
synchronous
member set to false
, it should therefore expect to receive a response
containing a status
member rather than an output
member.
Upon successful completion of a synchronous Action, the Thing MUST send a message to the
requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "invokeaction" | A string which denotes that this message relates to an invokeaction operation.
|
name |
string | Mandatory | The name of the Action that was invoked, as per its key in the actions member of
the Thing Description.
|
output |
any | Optional | The output (if any) of the Action invoked, with a type and structure conforming to the data
schema specified in the output member of the corresponding ActionAffordance in the
Thing Description.
|
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the action was completed. |
{ "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" }
Once a Thing has accepted an invokeaction
request for an asynchronous Action
it MUST send a message to the requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "invokeaction" | A string which denotes that this message relates to an invokeaction operation.
|
name |
string | Mandatory | The name of the Action that was invoked, as per its key in the actions member of
the Thing Description.
|
status |
object | Mandatory | An ActionStatus object representing the current
status of the ongoing action, with its state member set to pending or
running .
|
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the action request was accepted. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "a0058872-fe8f-4655-b43e-4e57bdcb8942", "messageType": "response", "operation": "invokeaction", "name": "fade", "status": { "actionID": "d95100b9-decc-4b11-81b8-81870597ebeb", "state": "pending", "timeRequested": "2025-09-03T18:14:55.641Z" }, "timestamp": "2025-09-03T18:14:56.642Z", "correlationID": "50d5f441-cb07-4513-80b4-cd062cddc1e0" }
If the Thing fails to complete the requested Action, and has not already sent a response, it MUST send an error response message to the requesting Consumer with an error code appropriate for the type of error encountered.
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "5478d805-bb72-432d-8c84-f05b114eeaba", "messageType": "response", "operation": "invokeaction", "error": { "status": "500", "type": "https://w3c.github.io/web-thing-protocol/errors#500", "title": "Internal Server Error" "detail": "The Thing was unable to complete the requested 'fade' action" } "timestamp": "2025-09-03T18:14:55.641Z", "correlationID": "50d5f441-cb07-4513-80b4-cd062cddc1e0" }
A Thing should not send an error response to an asynchronous Action request if an initial
response has already been sent. If a Thing
encounters an error when executing an asynchronous Action after the initial response has been sent
then the error should be reported via a queryaction
operation instead.
queryaction
To query the status of an ongoing asynchronous Action, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "queryaction" | A string which denotes that this message relates to a queryaction operation. |
actionID |
string | Mandatory | A unique identifier in UUIDv4 format [[rfc9562]] which identifies the individual action instance being
queried, as provided in the corresponding invokeaction response.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "f9dc3a42-188e-47f3-a0ed-cd3c3fb93207", "messageType": "request", "operation": "queryaction", "actionID": "d95100b9-decc-4b11-81b8-81870597ebeb", "correlationID": "f6293376-5ddb-43ef-ba21-aa34ceb48a58" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to queryaction
it MUST
attempt to query the status of the Action instance with the actionID
provided in the
message.
Upon successfully querying the status of an asynchronous Action instance, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "queryaction" | A string which denotes that this message relates to a queryaction operation.
|
name |
string | Mandatory | The name of the Action that was queried, as per its key in the actions member of
the Thing Description.
|
status |
object | Mandatory | An ActionStatus object representing the current
status of the Action instance. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the action status was reported. |
If the queried Action instance is currently pending (the action request has been accepted but
execution
has not yet started) then the Thing MUST respond with state
set to pending
.
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "84df00ce-dbe0-41fa-acf5-cdadcefd414e", "messageType": "response", "operation": "queryaction", "name": "fade", "status": { "actionID": "d95100b9-decc-4b11-81b8-81870597ebeb", "state": "pending", "timeRequested": "2025-09-09T17:34:50.361Z", }, "timestamp": "2025-09-09T17:34:51.531Z", "correlationID": "f6293376-5ddb-43ef-ba21-aa34ceb48a58" }
If the queried Action instance is currently running (execution has started but not finished) then the
Thing MUST respond with state
set to running
.
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "b35e2161-2e66-4d91-9f4f-43cd05b36c32", "messageType": "response", "operation": "queryaction", "name": "fade", "status": { "actionID": "d95100b9-decc-4b11-81b8-81870597ebeb", "state": "running", "timeRequested": "2025-09-09T17:34:50.361Z", }, "timestamp": "2025-09-09T17:34:52.564Z", "correlationID": "f6293376-5ddb-43ef-ba21-aa34ceb48a58" }
If the queried Action instance has completed successfully (execution has finished and the output,
if any, is available) then the Thing MUST respond with state
set to
completed
,
and output
set to the output of the Action, if any, with a type and structure conforming
to the data schema specified in the output
member of the corresponding ActionAffordance in the
Thing Description.
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "a350c37e-b241-4848-9e3f-c6f21ce3f93c", "messageType": "response", "operation": "queryaction", "name": "fade", "status": { "actionID": "d95100b9-decc-4b11-81b8-81870597ebeb", "state": "completed", "timeRequested": "2025-09-09T17:34:50.361Z", "timeEnded": "2025-09-09T17:34:57.642Z", "output": true, }, "timestamp": "2025-09-09T17:34:58.564Z", "correlationID": "f6293376-5ddb-43ef-ba21-aa34ceb48a58" }
If the queried Action instance failed to execute then the Thing MUST respond with a
status
containing an error
member with its value set to an object conforming to the
Problem Details Format [[RFC9457]], including an error code appropriate for the type of error encountered.
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "120d4e13-01b3-450d-9223-fd2a8abcc20f", "messageType": "response", "operation": "queryaction", "name": "fade", "status": { "state": "failed", "timeRequested": "2025-09-09T17:34:50.361Z", "timeEnded": "2025-09-09T17:34:51.531Z", "error": { "actionID": "d95100b9-decc-4b11-81b8-81870597ebeb", "status": 500, "type": "https://w3c.github.io/web-thing-protocol/errors#500", "title": "Internal Server Error", "detail": "The Thing was unable to complete the requested 'fade' action" } }, "timestamp": "2025-09-09T17:34:58.564Z", "correlationID": "f6293376-5ddb-43ef-ba21-aa34ceb48a58" }
A Thing MUST only send one response message for each queryaction
request.
If the Thing fails to query the status of the specified Action instance (e.g. because no
Action with the provided actionID
exists), then the Thing MUST send an
error response to the Consumer with an error code appropriate for the
type of error encountered.
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "00d8803c-9eed-4097-b6a7-6ba83c7197f4", "messageType": "response", "operation": "queryaction", "error": { "status": 404, "type": "https://w3c.github.io/web-thing-protocol/errors#404", "title": "Not Found" "detail": "No action instance with the specified actionID could be found" } "timestamp": "2025-09-09T17:34:58.564Z", "correlationID": "f6293376-5ddb-43ef-ba21-aa34ceb48a58" }
If the Thing experiences an error querying an Action then the error
member is included at the top
level of the queryaction
response message. If the Thing experiences an error executing
the Action then the error member is included in the status
member of the response message.
This helps to differentiate between an error querying the Action status vs. an error executing the Action.
cancelaction
To cancel an ongoing asynchronous Action, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "cancelaction" | A string which denotes that this message relates to a cancelaction operation. |
actionID |
string | Mandatory | A unique identifier in UUIDv4 format [[rfc9562]] which identifies the individual action instance being
cancelled, as provided in the corresponding invokeaction response.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "4672b11e-dc55-465b-a991-0d8de2295fa3", "messageType": "request", "operation": "cancelaction", "actionID": "c1e116a4-7832-4338-a72c-330c871b991a", "correlationID": "03a3cf77-2afc-42a3-bae7-463046dbc736" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to cancelaction
it MUST
attempt to cancel the Action instance with the actionID
provided in the
message.
Upon successfully cancelling the an asynchronous Action instance, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "cancelaction" | A string which denotes that this message relates to a cancelaction operation.
|
actionID |
string | Mandatory | A unique identifier in UUIDv4 format [[rfc9562]] which identifies the individual action instance that
was cancelled, as provided in the corresponding invokeaction response.
|
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the action was cancelled. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "58433430-cb92-4212-b3e8-aff22e8cb977", "messageType": "response", "operation": "cancelaction", "actionID": "c1e116a4-7832-4338-a72c-330c871b991a", "correlationID": "03a3cf77-2afc-42a3-bae7-463046dbc736" "timestamp": "2025-10-03T12:01:53.351Z", }
queryallactions
To query the status of all ongoing asynchronous Actions, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "queryallactions" | A string which denotes that this message relates to a queryallactions operation. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "1af62634-4eca-49cb-b448-95110619ee00", "messageType": "request", "operation": "queryallactions", "correlationID": "4c1c0ebc-775f-4b17-8f0f-e25c415f033d" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to queryallactions
it MUST
attempt to query the status of all ongoing and recently completed Action instances the Consumer
has permission to access across all Action affordances of the Thing.
Upon successfully querying the status of all asynchronous Actions, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "queryallactions" | A string which denotes that this message relates to a queryallactions operation.
|
statuses |
object | Mandatory | An object, keyed by Action name, with the value of each member being an array of
ActionStatus objects, enumerating a list of the
statuses of ongoing and recently completed Action instances for that Action.
|
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the action statuses were reported. |
Each array in the statuses
object MUST be sorted in reverse chronological order by
the time action instances were requested, such that the most recently requested action instance appears
first.
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "65cc48cd-7bf9-4e08-8b5a-91443704b0c7", "messageType": "response", "operation": "queryallactions", "statuses": { "fade": [ { "actionID": "d95100b9-decc-4b11-81b8-81870597ebeb", "timeRequested": "2025-09-09T17:38:13.631Z", "status": "pending" }, { "actionID": "66546f79-84bd-4ac9-b728-bb4168a17330", "timeRequested": "2025-09-09T17:36:00.363Z", "status": "running" }, { "actionID": "c1e116a4-7832-4338-a72c-330c871b991a", "status": "completed", "timeRequested": "2025-09-09T17:34:50.361Z", "timeEnded": "2025-09-09T17:34:55.361Z", "output": true, } ], "disco": [] }, "correlationID": "4c1c0ebc-775f-4b17-8f0f-e25c415f033d" }
When an Action instance is cancelled with a cancelaction
operation, its ActionStatus object is
deleted and need not be retained. For all other Action instances it is assumed that once an action is
completed the Thing will store its ActionStatus object so that its status
may later be queried with a queryaction
or queryallactions
operation. It is not
expected that ActionStatus objects should be retained indefinitely, they may be stored in volatile memory
and/or periodically pruned. The length of time for which to retain ActionStatus objects is expected to be
implementation-specific and may depend on application-specific requirements or resource constraints. It is
recommended that at least the last instance of a completed action status should be stored.
subscribeevent
To subscribe to an Event, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "subscribeevent" | A string which denotes that this message relates to a subscribeevent operation. |
name |
string | Mandatory | The name of the Event to be subscribed to, as per its key in the events member
of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "c347b27b-f567-4faf-b9b7-4c9e577b10fa", "messageType": "request", "operation": "subscribeevent", "name": "overheated", "correlationID": "206a6935-5978-47a5-a327-1ce1c656728b" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to subscribeevent
it MUST
attempt to register a subscription to the Event with the given name
.
If a Thing receives a subscribeevent
request for an Event which already has
an active subscription over the same WebSocket connection, the Thing MUST
replace the existing subscription with a new one, using the correllationID
of the
last subscribeevent
request received (i.e. last subscription wins).
If a subscribeevent
request replaces a subscription that was previously registered
by a subscribeallevents
operation then that does not impact active subscriptions
for other Events, which should continue to be registered using their current
correllationID
.
Conversely, a subscribeallevents
request may replace an subscription previously
registered by a subscribeevent
operation over the same WebSocket connection, which would impact
any active subscriptions for other Events on the same connection.
An EventAffordance in a Thing Description can include a subscription
member which defines
a data schema for the payload needed to register an event subscription. That data schema is not needed for
this sub-protocol because the sub-protocol itself defines the subscription payload format.
Upon successfully registering a subscription to the requested Event, the Thing MUST
send a message to the requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "subscribeevent" | A string which denotes that this message relates to a subscribeevent operation.
|
name |
string | Mandatory | The name of the Event being subscribed to, as per its key in the events member
of the Thing Description.
|
{ "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 a event subscription is registered, whenever an Event of the given name
occurs,
the Thing MUST send a message to the observing Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "event" | A string which denotes that this message is a notification sent from a Thing to a Consumer. |
operation |
string | "subscribeevent" | A string which denotes that this message relates to a subscribeevent operation.
|
name |
string | Mandatory | The name of the Event that occurred, as per its key in the events member
of the Thing Description.
|
data |
any | Optional | The Event payload, if any, with a type and structure conforming to the data schema defined in the
data member of the corresponding EventAffordance in the Thing Description.
|
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time at which the event occurred. |
{ "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" }
Because a duplicate subscription replaces a previous subscription to the same Event,
a Thing should only ever send one notification message per WebSocket connection for each instance of
an event. Notification messages always use the correllationID
of the currently
active subscription (which may have replaced a previous subscription).
unsubscribeevent
To cancel a subscription to an Event, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "unsubscribeevent" | A string which denotes that this message relates to an unsubscribeevent operation.
|
name |
string | Mandatory | The name of the Event to unsubscribe from, as per its key in the events
member of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "9378f35b-c6d8-46de-a0a1-5929110dffcb", "messageType": "request", "operation": "unsubscribeevent", "name": "overheated", "correlationID": "78e8ebe0-f315-48c7-ba6b-1dbc6531b538" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to unsubscribeevent
it MUST
attempt to cancel any subscriptions to the Event with the given name
over the
current WebSocket connection.
If an unsubscribeevent
request cancels a subscription that was previously
registered by a subscribeallevents
operation then that does not impact active
subscriptions for other Events that may have been registered by the same operation.
An EventAffordance in a Thing Description can include a cancellation
member which defines
a data schema for the payload needed to cancel an event subscription. That data schema is not needed for this
sub-protocol because the sub-protocol itself defines the payload format.
Upon successfully cancelling a subscription to an Event, the Thing MUST
send a message to the requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "unsubscribeevent" | A string which denotes that this message relates to an unsubscribeevent operation.
|
name |
string | Mandatory | The name of the Event that is no longer subscribed to, as per its key in the
events member of the Thing Description.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "915de7d6-5704-4475-bda3-3d99e34ad1eb", "messageType": "response", "operation": "unsubscribeevent", "name": "overheated", "correlationID": "78e8ebe0-f315-48c7-ba6b-1dbc6531b538" }
If a Thing receives an unsubscribeevent
request for an Event, but that
Event does not have any active subscriptions over the current WebSocket connection, then
it SHOULD still send a success response since the intended outcome has been achieved.
subscribeallevents
To subscribe to all Events of a Thing, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "subscribeallevents" | A string which denotes that this message relates to a subscribeallevents operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "f3363a15-1193-435c-9351-b8c2708280b3", "messageType": "request", "operation": "subscribeallevents", "correlationID": "65972ee4-d26a-4eb3-a7e2-7f2bc797401f" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to subscribeallevents
it MUST
attempt to register a subscription for each Event of the Thing.
If a Thing receives a subscribeallevents
request and there are already active
Event subscriptions on the same WebSocket connection, the Thing MUST replace
the existing subscriptions with new ones, using the correllationID
of the
subscribeallevents
request received (i.e. last subscription wins).
The subscribeallevents
operation can be thought of as a batch operation which adds individual
subscriptions to each Event. Those subscriptions may later be individually replaced
by new subscriptions using subscribeevent
operations, or individually cancelled
using unsubscribeevent
operations.
Upon successfully registering a subscription to all Events, the Thing MUST
send a message to the requesting Consumer
containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "subscribeallevents" | A string which denotes that this message relates to a subscribeallevents operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "eca78220-2ba2-411d-940d-905bfbef49b4", "messageType": "response", "operation": "subscribeallevents", "correlationID": "65972ee4-d26a-4eb3-a7e2-7f2bc797401f" }
Whilst a subscription to all Events is registered, whenever an event occurs, the Thing MUST send a message to the observing Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "notification" | A string which denotes that this message is a notification sent from a Thing to a Consumer. |
operation |
string | "subscribeallevents" | A string which denotes that this message relates to a subscribeallevents operation.
|
name |
string | Mandatory | The name of the Event that occurred, as per its key in the events
member of the Thing Description.
|
data |
any | Optional | The Event payload, if any, with a type and structure conforming to the data schema defined in the
data member of the corresponding EventAffordance in the Thing Description.
|
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time at which the event occurred. |
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "808c5b62-4207-4755-ac2b-5f8c66594547", "messageType": "notification", "operation": "subscribeallevents", "name": "overheated", "value": 90, "timestamp": "2025-10-08T11:59:32.325Z", "correlationID": "65972ee4-d26a-4eb3-a7e2-7f2bc797401f" }
Because a duplicate event subscription replaces a previous subscription to the same Event,
a Thing should only ever send one notification message per WebSocket connection for each instance of an
event. Notification messages always use the correllationID
of the currently
active subscription (which may have replaced a previous subscription).
unsubscribeallevents
To cancel subscriptions to all Events of a Thing, a Consumer MUST send a message to the Thing which contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "request" | A string which denotes that this message is a request sent from a Consumer to a Thing. |
operation |
string | "unsubscribeallevents" | A string which denotes that this message relates to an unsubscribeallevents operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "061c34de-4b93-4afe-9d18-f9646bddb568", "messageType": "request", "operation": "unsubscribeallevents", "correlationID": "e28fd02b-4d30-44c4-9517-2948737b22f3" }
When a Thing receives a message from a Consumer with messageType
set to
request
and operation
set to unsubscribeallevents
it MUST
attempt to cancel subscriptions to all Events of the Thing over the current WebSocket
connection.
The unsubscribeallevents
operation can be thought of as a batch operation which cancels
all individual event subscriptions that have been registered with a Thing over a given WebSocket
connection, regardless of whether those subscriptions were registered using a
subscribeevent
operation or a subscribeallevents
operation.
Upon successfully cancelling subscriptions from all Events, the Thing MUST send a message to the requesting Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
operation |
string | "unsubscribeallevents" | A string which denotes that this message relates to an unsubscribeallevents operation.
|
{ "thingID": "https://mythingserver.com/things/mylamp1", "messageID": "3ba79d05-d546-4687-9662-0b85b5f52092", "messageType": "response", "operation": "unsubscribeallevents", "correlationID": "e28fd02b-4d30-44c4-9517-2948737b22f3" }
If a Thing receives an unsubscribeallevents
request but the Thing does not have
any active event subscriptions over the current WebSocket connection, then it SHOULD still send a
success response since the intended outcome has been achieved.
An ActionStatus object is used to represent the status of an Action instance in
invokeaction
, queryaction
and queryallactions
operations
and contains the following members:
Member | Type | Assignment | Description |
---|---|---|---|
actionID |
string | Mandatory | A unique identifier in UUIDv4 format [[rfc9562]] which identifies the individual action instance, for use when querying or cancelling it. |
state |
string | Mandatory | A string representing the current state of the action. Set to either pending ,
running , completed or failed .
|
output |
object | Optional | The output (if any) of the Action invoked, with a type and structure conforming to the data
schema specified in the output member of the corresponding ActionAffordance in the
Thing Description.
|
error |
object | Optional | An object conforming to the Problem Details Format [[RFC3339]]. |
timeRequested |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time at which the Thing received the request to execute the Action. |
timeEnded |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time at which the Thing successfully completed executing the Action, or faield to execute the Action. |
If a Thing experiences an error when attempting to carry out an operation requested by a Consumer, it MUST send a response message to the Consumer containing the following members:
Member | Type | Assignment | Description |
---|---|---|---|
messageType |
string | "response" | A string which denotes that this message is a response sent from a Thing to a Consumer. |
error |
object | Mandatory | An object conforming to the Problem Details Format [[RFC9457]]. |
timestamp |
string | Optional | A timestamp in date-time format [[RFC3339]] set to the time the error occurred. |
{
"thingID": "https://mythingserver.com/things/mylamp1",
"messageID": "79057736-3e0e-4dc3-b139-a33051901ee2",
"messageType": "response",
"operation": "readproperty",
"name": "on",
"error": {
"status": 404,
"type": "https://w3c.github.io/web-thing-protocol/errors#404",
"title": "Not Found"
"detail": "No property found with the name 'on'"
}
"timestamp": "2024-01-13T23:20:50.52Z",
"correlationID": "5afb752f-8be0-4a3c-8108-1327a6009cbd"
}
If the error experienced could be described by one of the common error types described in the following table then that error type SHOULD be used:
status |
type |
title |
Description |
---|---|---|---|
400 | https://w3c.github.io/web-thing-protocol/errors#400 | Bad Request | The request message was invalid (e.g. is missing mandatory members or contains an unknown operation type). |
403 | https://w3c.github.io/web-thing-protocol/errors#403 | Forbidden | The requesting Consumer is not have permission to carry out the requested operation. |
404 | https://w3c.github.io/web-thing-protocol/errors#404 | Not Found | The interaction affordance referenced in the request message can not be found (e.g. no Property
with the given name exists).
|
500 | https://w3c.github.io/web-thing-protocol/errors#500 | Internal Server Error | The Thing experienced an unexpected condition which prevented it from fulfilling the requested operation. |
503 | https://w3c.github.io/web-thing-protocol/errors#503 | Service Unavailable | The Thing or interaction affordance is currently not able to fulfil the requested operation (e.g. because it is overloaded or undergoing a firmware update). |
The URLs given for error types in the table above are placeholders and will be replaced in the final version of this specification.
The error
member of an error response message MAY contain a detail
member with its
value set to a string containing additional human-readable information about the specific instance of the
error.
A Thing MAY use its own error types where one of the common error types above does not sufficiently
explain the error, as long as the error
member of the response message conforms to the Problem
Details Format [[RFC9457]].
{
"thingID": "https://mythingserver.com/things/teapot",
"messageID": "07de640d-1b10-4857-b1ae-0c29d62acedc",
"messageType": "response",
"operation": "invokeaction",
"name": "brewCoffee",
"error": {
"status": 418,
"type": "https://mythingserver.com/errors#418",
"title": "I'm a teapot"
"detail": "This device can not brew coffee because it is a teapot"
}
"timestamp": "2025-05-22T07:21:50.53Z",
"correlationID": "61aa2164-b1ef-4585-aa3f-626a7f4a185f"
}
This specification proposes the registration of a sub-protocol in the IANA "WebSocket Subprotocol Name Registry". The name of the sub-protocol and the published URL of its definition are to be confirmed, but currently the name "webthingprotocol" and this document are used as a placeholder and draft proposal.