Application Webhooks
Webhooks are user-defined HTTP callbacks that can send event-triggered notifications or updates to an external web server. You can use webhooks to hook your own processing into LiveSwitch events. LiveSwitch sends out webhook event updates as an HTTP POST request with the body formatted in JSON.
Application Webhooks allow hooking into application-level events.
Webhook Validation
All Application and Channel webhooks include the header X-ApplicationSignature
.
To verify the source of a webhook, the receiver creates a HMAC-SHA256 hash of the webhook's JSON body. That hash uses the LiveSwitch Application's shared secret as its secret key. The receiver then converts the HMAC-SHA256 hash to Base64. The Base64 value must match the value in the X-ApplicationSignature
header for the webhook to be validated.
Webhook Validation Example
The following example is from a Node application using Express and TypeScript. This method accepts a raw JSON body and the request signature. To find the associated shared secret, the Application ID is extracted from the request body. Once the application finds the shared secret, it uses the raw request body as input to generate a HMAC-SHA256 value. If this value matches the requestSignature
, then the webhook is validated.
Important
Store your shared secret in a secure location.
/*
* Core validation code looks up the application secret
* based on the applicationId found in the request body,
* then computes a SHA256 hash to ensure it matches
* the signature found in the x-applicationsignature
* header of the request.
*/
function validate(rawRequestBody:string, requestSignature:string):boolean {
// Parse the raw request in order to extract the applicationId
const webhook = JSON.parse(rawRequestBody);
const applicationId = getApplicationId(webhook);
// If we don't have a signature (X-ApplicationSignature wasn't present)
// AND there is no detected applicationId - then we've received a
// deployment webhook and we cannot verify with an application
// shared secret.
if (!requestSignature && !applicationId) {
console.log("Received unsigned deployment webhook");
return true;
}
// If there is an applicationId found, but no request signature
// this is not a valid webhook request.
if (applicationId && !requestSignature) {
console.error("Received an unsigned application webhook");
return false;
}
// If no applicationId was found, or we do not know the
// shared secret to use for the applicationId, then
// we cannot vaildate the request.
if (!applicationId || !applicationSecrets[applicationId]) {
console.error("Unable to determine application secret");
return false;
}
// For demo purposes we lookup the shared secret from the
// hardcoded applicationId map - for production use this
// should come from configuration. Shared secrets should
// never be stored in source code.
const secret = applicationSecrets[applicationId];
// Create a HMAC-SHA256 hash function with the application's
// shared secret
const hmac = crypto.createHmac("sha256", secret);
// Use the raw request body as input to the function.
// This must be the raw incoming request body. If it
// has been parsed as json in anyway prior you will
// likely not end up with the original value that was
// signed on the server.
hmac.update(rawRequestBody);
// Run the HMAC-SHA256 function and get the result
// as base64
const base64HmacSha256Hash = hmac.digest("base64");
// Trim all trailing `=` characters. All signatures
// will always be in base64 with the trailing padding
// `=` characters trimmed from the end of the string.
const base64HmacSha256HashTrimmed = trimEnd(base64HmacSha256Hash, "=");
// If the trimmed base64 hash does not exactly match
// the value from the X-ApplicationSignature header
// then this is an invald request.
if (base64HmacSha256HashTrimmed !== requestSignature) {
console.error("signature does not match computed hash");
return false;
}
// If the header matches our computed value, then
// we have succesfully validated the request.
return true;
}
Take note of the following when validating webhooks:
- The input of the HMAC-SHA256 function must be the raw JSON request body. If the request went through any parsers that might have changed the JSON, then the input value might be different than what the server used to generate the hash. For example, if the webhook payload included a field like
frameRate: 30.0
that parses toframeRate:30
, the generated HMAC-SHA256 hash will be affected. If you use Express with its built-in JSON middleware, then the request body might be parsed. In this case, use a custom parser to access the unparsed JSON body. - The value in
X-ApplicationSignature
is Base64, with all trailing=
padding characters stripped. After computing the Base64 hash, strip the trailing=
characters before comparing it with theX-ApplicationSignature
header.
Client
Client Registered
This event triggers when a new client connects to an Application ID.
Client Registered sample JSON object
{
"timestamp": 1562614545646,
"origin": "client",
"type": "client.registered",
"client": {
"version": 0.0.0.0,
"applicationId": "my-app-id",
"userId": "d8c5a6527deb4cc6bf4afb908e682e5d",
"userAlias": "Anonymous",
"deviceId": "9ffa994a648940b997ac79db343d0f7d",
"tag": 1,
"sourceLanguage": "typescript",
"machineName": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
"coreCount": 6,
"physicalMemory": "8589934592",
"operatingSystem": "Windows 10",
"architecture": "x64",
"id": "28c9d925bd0043b5aa89e8cd75a5317e",
}
}
Client Unregistered
This event triggers when a client unregisters from the gateway by leaving a channel.
Client Unregistered sample JSON object
{
"timestamp": 1562614545646,
"origin": "client",
"type": "client.unregistered",
"client": {
"version": 0.0.0.0,
"applicationId": "my-app-id",
"userId": "d8c5a6527deb4cc6bf4afb908e682e5d",
"userAlias": "Anonymous",
"deviceId": "9ffa994a648940b997ac79db343d0f7d",
"tag": 1,
"report": [...],
"id": "28c9d925bd0043b5aa89e8cd75a5317e"
}
}
Client Updated
This event triggers when a client's information is updated on the gateway (a username change, for example).
Client Updated sample JSON object
{
"timestamp": 1562614545646,
"origin": "client",
"type": "client.updated",
"client": {
"applicationId": "my-app-id",
"userId": "d8c5a6527deb4cc6bf4afb908e682e5d",
"userAlias": "stephen",
"deviceId": "9ffa994a648940b997ac79db343d0f7d",
"deviceAlias": "camera 1",
"id": "my-app-id/4531e51218b24fb58d5dc62087a93439",
"tag": "1",
"region": "en-us",
"coreCount": 6,
"architecture": "x64",
"machineName": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
"operatingSystem": "Windows 10",
"operatingSystemVersion": "1809",
"physicalMemory": "8589934592",
"sourceLanguage": "typescript",
"version": "0.0.0.0"
}
}
Client Message
This event triggers when a user sends a message to the server.
Client Message sample JSON object
{
"timestamp":1627339445785,
"origin":"client",
"type":"client.message",
"client":{
"id":"d4c1d83ea4c64ec587703bc4867ea1d2",
"applicationId":"2250d2f7fd4a4750ac90df8d5a9f25da",
"userId":"e0117c2672c44f4c80de4326ec520d8b",
"deviceId":"56491ac67cba46e69441b8bd9b9917da"
},
"message":{
"payload":"Sample client message to server"
}
}
Server
Server Message
This event triggers when a server sends a message to an entire application or a specific user, device, or client in the application.
Server Message sample JSON object
{
"timestamp":1627339445317,
"origin":"gateway",
"type":"server.message",
"client":{
"applicationId":"2250d2f7fd4a4750ac90df8d5a9f25da",
"userId":"server-user-id",
"userAlias":"server-user-alias",
"deviceId":"server-device-id",
"deviceAlias":"server-device-alias",
"tag":"server-client-tag",
"roles":[...],
"id":"00000000000000000000000000000000",
},
"message":{
"userId":"e0117c2672c44f4c80de4326ec520d8b", // To a specific user
"deviceId":"56491ac67cba46e69441b8bd9b9917da", // To a specific device
"clientId":"d4c1d83ea4c64ec587703bc4867ea1d2", // To a specific client
"payload":"Sample server message to application"
}
}