CloudEvents extension for Azure Web PubSub event handler with HTTP protocol
The Web PubSub service delivers client events to the upstream webhook using the CloudEvents HTTP protocol binding.
The data sent from the Web PubSub service to the server is always in CloudEvents binary
format.
- Webhook Validation
- Web PubSub CloudEvents Attribute Extension
- Events
- Blocking events
- Unblocking events
Webhook validation
The Webhook validation follows CloudEvents. The request always contains WebHook-Request-Origin: xxx.webpubsub.azure.com
in the header.
If and only if the delivery target does allow delivery of the events, it MUST reply to the request by including WebHook-Allowed-Origin
header, for example:
WebHook-Allowed-Origin: *
Or:
WebHook-Allowed-Origin: xxx.webpubsub.azure.com
For now, WebHook-Request-Rate and WebHook-Request-Callback aren't supported.
Web PubSub CloudEvents attribute extension
It was also noted that the HTTP specification is now following a similar pattern by no longer suggesting that extension HTTP headers be prefixed with X-.
This extension defines attributes used by Web PubSub for every event it produces.
Attributes
Name | Type | Description | Example |
---|---|---|---|
userId |
string |
The user the connection authed | |
hub |
string |
The hub the connection belongs to | |
connectionId |
string |
The connectionId is unique for the client connection | |
eventName |
string |
The name of the event without prefix | |
subprotocol |
string |
The subprotocol the client is using if any | |
connectionState |
string |
Defines the state for the connection. You can use the same response header to reset the value of the state. Multiple connectionState headers aren't allowed. Do base64 encode the string value if it contains complex characters inside, for example, base64(jsonString) to pass complex object using this attribute. |
|
signature |
string |
The signature for the upstream webhook to validate if the incoming request is from the expected origin. The service calculates the value using both primary access key and secondary access key as the HMAC key: Hex_encoded(HMAC_SHA256(accessKey, connectionId)) . The upstream should check if the request is valid before processing it. |
Events
There are two types of events. One is blocking events that the service waits for the response of the event to continue. One is unblocking events that the service doesn't wait for the response of such event before processing the next message.
- Blocking events
- Unblocking events
System connect
event
ce-type
:azure.webpubsub.sys.connect
Content-Type
:application/json
Request format:
POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/json; charset=utf-8
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.sys.connect
ce-source: /hubs/{hub}/client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub}
ce-eventName: connect
{
"claims": {},
"query": {},
"headers": {},
"subprotocols": [],
"clientCertificates": [
{
"thumbprint": "<certificate SHA-1 thumbprint>",
"content": "-----BEGIN CERTIFICATE-----\r\n...\r\n-----END CERTIFICATE-----"
}
]
}
Success response format:
Header
ce-connectionState
: If this header exists, the connection state of this connection will be updated to the value of the header. Only blocking events can update the connection state. The below sample uses base64 encoded JSON string to store the complex state for the connection.Status code:
204
: Success, with no content.200
: Success, the content SHOULD be a JSON format, with following properties allowed:subprotocols
The
connect
event forwards the subprotocol and authentication information to Upstream from the client. Web PubSub service uses the status code to determine if the request will be upgraded to WebSocket protocol.If the request contains the
subprotocols
property, the server should return one subprotocol it supports. If the server doesn't want to use any subprotocols, it should not send thesubprotocol
property in response. Sending a blank header is invalid.userId
:{auth-ed user ID}
As the service allows anonymous connections, it's the
connect
event's responsibility to tell the service the user ID of the client connection. The service reads the user ID from the response payloaduserId
if it exists. The connection is dropped if the user ID can't be read from the request claims nor theconnect
event's response payload.groups
:{groups to join}
The property provides a convenient way for user to add this connection to one or multiple groups. In this way, there's no need to have another call to add this connection to some group.
roles
:{roles the client has}
The property provides a way for the upstream Webhook to authorize the client. There are different roles to grant initial permissions for PubSub WebSocket clients. Details about the permissions are described in Client permissions.
HTTP/1.1 200 OK
ce-connectionState: eyJrZXkiOiJhIn0=
{
"groups": [],
"userId": "",
"roles": [],
"subprotocol": ""
}
Error response format:
4xx
: Error, the response from Upstream will be returned as the response for the client request.
HTTP/1.1 401 Unauthorized
System connected
event
The service calls the Upstream when the client completes WebSocket handshake and is successfully connected.
ce-type
:azure.webpubsub.sys.connected
Content-Type
:application/json
ce-connectionState
:eyJrZXkiOiJhIn0=
Request body is empty JSON.
Request format:
POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/json; charset=utf-8
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.sys.connected
ce-source: /hubs/{hub}/client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub}
ce-eventName: connected
ce-subprotocol: abc
ce-connectionState: eyJrZXkiOiJhIn0=
{}
Response format:
2xx
: success response.
connected
is an asynchronous event, when the response status code isn't success, the service logs an error.
HTTP/1.1 200 OK
System disconnected
event
disconnected
event is always triggered when the client request completes if the connect event returns 2xx
status code.
ce-type
:azure.webpubsub.sys.disconnected
Content-Type
:application/json
Request format:
POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/json; charset=utf-8
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.sys.disconnected
ce-source: /hubs/{hub}/client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub}
ce-eventName: disconnected
ce-subprotocol: abc
ce-connectionState: eyJrZXkiOiJhIn0=
{
"reason": "{Reason}"
}
reason
The
reason
describes the reason the client disconnects.
Response format:
2xx
: success response.
disconnected
is an asynchronous event, when the response status code isn't success, the service logs an error.
HTTP/1.1 200 OK
User event message
for the simple WebSocket clients
The service invokes the event handler upstream for every WebSocket message frame.
ce-type
:azure.webpubsub.user.message
Content-Type
:application/octet-stream
for binary frame;text/plain
for text frame;
UserPayload is what the client sends.
Request format:
POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/octet-stream | text/plain | application/json
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.message
ce-source: /hubs/{hub}/client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub}
ce-eventName: message
ce-connectionState: eyJrZXkiOiJhIn0=
UserPayload
Success response format
- Status code
204
: Success, with no content.200
: Success, the format of theUserResponsePayload
depends on theContent-Type
of the response.
Content-Type
:application/octet-stream
for binary frame;text/plain
for text frame;- Header
Content-Type
:application/octet-stream
for binary frame;text/plain
for text frame; - Header
ce-connectionState
: If this header exists, the connection state of this connection will be updated to the value of the header. Only blocking events can update the connection state. The below sample uses base64 encoded JSON string to store complex state for the connection.
When the Content-Type
is application/octet-stream
, the service sends UserResponsePayload
to the client using binary
WebSocket frame. When the Content-Type
is text/plain
, the service sends UserResponsePayload
to the client using text
WebSocket frame.
HTTP/1.1 200 OK
Content-Type: application/octet-stream (for binary frame) or text/plain (for text frame)
Content-Length: nnnn
ce-connectionState: eyJrZXkiOiJhIn0=
UserResponsePayload
Error response format
When the status code isn't success, it's considered to be error response. The connection would be dropped if the message
response status code isn't success.
User custom event {custom_event}
for PubSub WebSocket clients
The service calls the event handler webhook for every valid custom event message.
Case 1: send event with text data:
{
"type": "event",
"event": "<event_name>",
"dataType" : "text",
"data": "text data"
}
What the upstream event handler receives like below, the Content-Type
for the CloudEvents HTTP request is text/plain
for dataType
=text
POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: text/plain
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>
ce-subprotocol: json.webpubsub.azure.v1
ce-connectionState: eyJrZXkiOiJhIn0=
text data
Case 2: send event with JSON data:
{
"type": "event",
"event": "<event_name>",
"dataType" : "json",
"data": {
"hello": "world"
},
}
What the upstream event handler receives like below, the Content-Type
for the CloudEvents HTTP request is application/json
for dataType
=json
POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/json
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>
ce-subprotocol: json.webpubsub.azure.v1
ce-connectionState: eyJrZXkiOiJhIn0=
{
"hello": "world"
}
Case 3: send event with binary data:
{
"type": "event",
"event": "<event_name>",
"dataType" : "binary",
"data": "aGVsbG8gd29ybGQ=" // base64 encoded binary
}
What the upstream event handler receives like below, the Content-Type
for the CloudEvents HTTP request is application/octet-stream
for dataType
=binary
POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/octet-stream
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>
ce-subprotocol: json.webpubsub.azure.v1
<binary data>
Success response format
HTTP/1.1 200 OK
Content-Type: application/octet-stream | text/plain | application/json
Content-Length: nnnn
UserResponsePayload
- Status code
204
: Success, with no content.200
: Success, data sending to the PubSub WebSocket client depends on theContent-Type
;
- Header
ce-connectionState
: If this header exists, the connection state of this connection will be updated to the value of the header. Only blocking events can update the connection state. The below sample uses base64 encoded JSON string to store the complex state for the connection. - When Header
Content-Type
isapplication/octet-stream
, the service sendsUserResponsePayload
back to the client usingdataType
asbinary
with payload base64 encoded. A sample response:{ "type": "message", "from": "server", "dataType": "binary", "data" : "aGVsbG8gd29ybGQ=" }
- When the
Content-Type
istext/plain
, the service sendsUserResponsePayload
to the client usingdataType
astext
with payload string. - When the
Content-Type
isapplication/json
, the service sendsUserResponsePayload
to the client usingdataType
=json
withdata
value token as the response payload body.
Error response format
When the status code isn't success, it's considered to be error response. The connection would be dropped if the {custom_event}
response status code isn't success.
Next steps
Use these resources to start building your own application: