Workspace Integrations Guide
Workspace Integrations provide a framework for services to access Webex developer APIs, with a focus on extending the capabilities of Workspaces and Webex devices. These integrations can be public (built by 3rd party vendors that engage with Cisco to provide all customers access to the integration), or they can be private (built by and available only to the customers themselves).
anchorWhat are Workspace Integrations?
anchorFor customer Control Hub full administrator users, the integrations framework provides a way to enable a 3rd party vendor to interact with the Webex APIs, and especially the Webex device xAPI, in a transparent and controlled manner. It's easy to activate and deactivate integrations, understand what permissions they have been granted, and be in control of changes to these permissions as the integrations evolve.
This article describes how to develop and deploy these integrations, for both 3rd party vendors and customers that want to build private integrations.
How to Setup a Workspace Integration
Follow the steps below to set up a new org private Workspace Integration. Every step is detailed further below in this guide.
Describe the application in a manifest file and upload it in Control Hub. This gives you an OAuth client ID and a client secret. For further details, see The Integration Manifest for information regarding the manifest, and Deployment to make the integration available for customers to activate.
Receive an activation JWT. If the manifest specified the manual provisioning flow, this happens by an admin activating the integration in Control Hub and copy-pasting the JWT to your integration, as described in Activation.
Use the OAuth credentials from step 1 and the credentials from step 2 to update the integration status, as described in Update the integration activation status.
At this point, you have everything you need to start interacting with the Webex APIs.
Implement the rest of the Workspace Integrations protocol. See Management.
Use your credentials to interact with the Webex APIs in order to implement your functionality.
To create a public integration there are some additional steps, such as that the manifest needs to be approved and deployed by Cisco, as detailed in the sections below.
Comparison with REST API Integrations
Workspace Integrations are not the same as the integrations described in Integrations. They have things in common, specifically using the same underlying OAuth 2.0 framework, but differ in a few ways:
- Workspace integrations are exclusively managed from Control Hub under Workspaces > Integrations.
- The workspace integration framework was designed to support service to service interaction. The integration runs with the identity of a machine account created in the customer organization, rather than on behalf of a Webex user.
- Workspace integrations support fine grained access to the Webex devices xAPI and webhook change notifications for select xAPI statuses.
anchorThe Integration Manifest
anchorThe integration manifest provides the meta data needed to understand the capabilities of the integration, what permissions it needs and how it should be provisioned. Let's have a look at an example and break down the fields:
Example Manifest
{
"id": "ac6b6972-538e-11ec-bf63-0242ac130002",
"manifestVersion": 1,
"displayName": "ACME Device Integration",
"vendor": "ACME INC.",
"email": "example-app@example.com",
"description": "The ACME integration will do magic with your Webex device sensor data",
"descriptionUrl": "https://example.com/webexintegration/details",
"tocUrl": "https://example.com/webexintegration/toc",
"availability": "global",
"apiAccess": [
{
"scope": "spark-admin:devices_read",
"access": "required",
"role": "id_readonly_admin"
},
{
"scope": "spark-admin:workspaces_read",
"access": "required",
"role": "id_readonly_admin"
},
{
"scope": "spark:xapi_statuses",
"access": "required"
},
{
"scope": "spark:xapi_commands",
"access": "required"
}
],
"xapiAccess": {
"status": [
{
"path": "RoomAnalytics.*",
"access": "required"
},
{
"path": "Peripherals.ConnectedDevice[*].RoomAnalytics.*",
"access": "required"
},
{
"path": "Standby.State",
"access": "required"
},
{
"path": "SystemUnit.State.NumberOfActiveCalls",
"access": "required"
}
],
"commands": [
{
"path": "Message.Send",
"access": "required"
}
],
"events": [
{
"path": "BootEvent",
"access": "required"
},
{
"path": "UserInterface.Message.Prompt.Response",
"access": "required"
}
]
},
"provisioning": {
"type": "manual"
}
}
This example describes an integration from ACME, INC. It's a global integration, which means it will have to be approved and deployed by Cisco, and thus made available to any customer that wants to activate it. For customers building their own integrations, the availability field would be set to org_private, allowing the manifest to be uploaded in Control Hub.
The apiAccess shows four required scopes, that would give access to reading device and workspace details, reading statuses and executing commands for the Webex Devices over the xAPI. The status, command and event details are specified in the xapiAccess. The example is requesting access to:
- All status under RoomAnalytics for both the device itself and the attached peripherals (like the Room Navigator), the device standby state and how many active calls the device has.
- The UserInterface.Message.Prompt.Display command that displays a prompt on the device screen with a title, text and up to five options for the user.
- Two events: the boot event that is sent when a device reboots and the UserInterface.Message.Prompt.Response following a selection by the user for the message prompt.
The provisioning section shows that the integration is deploy type manual, which means the Control Hub admin will need to copy an activation code and provide this to the integration.
This is how the manifest will render in Control Hub:

Manifest Details
Field | Required / Optional | Value space | Description |
---|---|---|---|
id | Required | UUID | The Id of the integration. Generated by the integration developer, needs to be globally unique. |
manifestVersion | Required | Integer | The manifest version. When the manifest changes, the version must be incremented. |
displayName | Required | String | A display name for the integration |
vendor | Required | String | The name of the vendor / company that created the integration |
Required | String | An email address from the integration vendor | |
description | Required | String | A description of what the integration does and what value it will provide to the customer |
descriptionUrl | Optional | String | A URL to a more detailed description of what the integration does and what value it provides to the customer |
tocUrl | Required (when global ) | URL | A URL to a web page listing the terms and conditions for the integration. The admin will have to accept these when enabling the integration from Control Hub. Note that this URL is not required for private integrations. |
availability | Required | global or org_private | Describes if the integration is global and available to all customers, or org_private , which means it only applies to a specific customer organization. Note that only org_private integrations can be manually uploaded by a Control Hub admin. global integrations are deployed by Cisco on behalf of the vendor. |
apiAccess | Required | Array | A list of Webex API scopes the integration is requesting. These scopes allow access to specific public Webex developer APIs. |
apiAccess.scope | Required | String | The scope requested. See Webex API scopes for examples. |
apiAccess.access | Required | required or optional | Is the scope required or optional. Optional scopes can be opted out by the Control Hub admin. |
apiAccess.role | Optional | id_full_admin , id_readonly_admin or id_device_admin | Some APIs require a specific use role to be assigned to the account created for the integration. For most read APIs, id_readonly_admin should be sufficient. Check the API docs for the specific APIs to see what roles, if any, are required. |
xapiAccess | Required | Array | A list of device xAPI status, commands and events that the integration is requesting access to. See https://roomos.cisco.com/xapi for more details of what is supported by the Webex Devices |
xapiAccess.status.path | Required | String | The status path requested. Wildcards indicate all statuses recursively under the specific path, say RoomAnalytics.* means all statuses under the RoomAnalytics node in the status tree. |
xapiAccess.status.access | Required | required or optional | Is the status required or optional? Optional statuses can be opted out by the Control Hub admin. |
xapiAccess.commands.path | Required | String | The command(s) requested. Wildcards indicate all commands recursively under the specific path, say Bookings.* means all commands under the Bookings node in the command tree. |
xapiAccess.commands.access | Required | required or optional | Is the command required or optional. Optional commands can be opted out by the Control Hub admin. |
xapiAccess.events.path | Required | String | The event requested. |
xapiAccess.events.access | Required | required or optional | Is the event required or optional? Optional events can be opted out by the Control Hub admin. |
provisioning | Required | Provisioning | Describes how the integration is provisioned when enabled from Control Hub |
provisioning.type | Required | manual or https | manual provisioning means the Control Hub admin will be presented with an activation code that is copied and provided to the integration. https provisioning removes the need for the activation code but requires a global activation URL for the 3rd party service. More details on this later. |
provisioning.url | Required (when https ) | URL | If the type is manual , the URL is optional. If set, it provides a link to the page where the admin needs to provide the activation code. If the type is https , the URL is required and is where the activation code is posted to start the activation flow. Note that the URL must be an HTTPS URL. |
provisioning.activationGuideUrl | Optional | URL | Cannot be used together with provisioning.url . If specified, it provides a link to a documentation page by the integrator explaining how to continue the activation process. |
anchorDeployment
anchorDeploying an integration will make it available for activation in Control Hub. Private integrations will only be accessible to the integration developer organization, and can be uploaded in Control Hub by the administrator:
Global integrations are approved and deployed by Cisco. They are available for all customers to activate through Control Hub. In both cases, the output of the process is an OAuth clientId and secret that the integration needs to get an access token to use in the Webex APIs.
anchorActivation
anchorActivating an integration ultimately means to provide the integration with the details needed to integrate with the Webex APIs. Administrators do this in Control Hub via the "Activate" button.
These details are in both modes of provisioning encoded in a Cisco signed JSON Web Token (JWT), in Control Hub named an activation code:
- Manual: The JWT is copied from Control Hub UI and must be manually provided to the integration.
- HTTPS: The JWT is transported in an HTTPS POST request to the URL specified in the provisioning part of the manifest.

The JWT uses the ES256 algorithm and an ECDSA signature. To verify the signature, the JSON Web Key Set (JWKS) can be found at the following regional URLs:
Region | Description | URL |
---|---|---|
us-west-2_r | US West (Oregon) | https://xapi-r.wbx2.com/jwks |
us-east-2_a | US East (Ohio) | https://xapi-a.wbx2.com/jwks |
eu-central-1_k | Europe (Frankfurt) | https://xapi-k.wbx2.com/jwks |
The region
key is embedded in the JWT and can be used to lookup the correct JWKS URL. If a region is found in the JWT that does not match one of the three in this list, default to the us-east-2_a
URL: https://xapi-a.wbx2.com/jwks.
Example JSON Web Key Set (JWKS)
{
"keys": [
{
"kty": "EC",
"use": "sig",
"crv": "P-256",
"kid": "VkOd36xy3JeGFka5uW5gW525",
"key_ops": [
"verify"
],
"x": "ilEKY13J464rnzK4CvdIh1_ow3q4e1eoqnjXES_PnC8",
"y": "T-Kp8qcf4FBvvtNIMSkQmAWnbd3uiz2U_NqfTavc1x8",
"alg": "ES256"
},
{
"kty": "EC",
"use": "sig",
"crv": "P-256",
"kid": "GzaEArXcMo24RDst1kP5r1q7",
"key_ops": [
"verify"
],
"x": "vflTChGJ6bjxt2e4M3nIVb90IZb9Ms7fg0wcQ9FrFV0",
"y": "07HeNRvKQW4b-JiAuvNoXc957flcH6PD538jhWTFvHI",
"alg": "ES256"
},
{
"kty": "EC",
"use": "sig",
"crv": "P-256",
"kid": "m9vlvDThppHKT0LlQbeURKwd",
"key_ops": [
"verify"
],
"x": "h0rF6Q0EZXQsAGmNG-S7Dnw29LEpDE3lGj_FNOYoRJc",
"y": "td1qLYMGXK9m4PqKF4cdrHMPoeU-g9pmPQGsGTnhSFU",
"alg": "ES256"
}
]
}
Example Activation JWT
Let's have a look at an example JWT activation code and break down the fields. You can find more details on the JSON Web Token standard in RFC7519. The JWT is a string with three dot separated base64url encoded strings:
header.payload/claims.signature
Encoded:
eyJraWQiOiJnQjFzbnd6bGJwb3RuMFdZTm1EMFNIZVUiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9
.
eyJzdWIiOiJZMmx6WTI5emNHRnlhem92TDNWeWJqcFVSVUZOT25WekxXVmhjM1F0TVY5cGJuUXhNeTlQVWtkQlRrbGFRVlJKVDA0dk0yRTJabVl6TnpNdE5qaGhOeTAwTkdVMExUa3haRFl0WVRJM05EWXdaVEJoWXpWaiIsIm9hdXRoVXJsIjoiaHR0cHM6Ly9pbnRlZ3JhdGlvbi53ZWJleGFwaXMuY29tL3YxL2FjY2Vzc190b2tlbiIsIm9yZ05hbWUiOiJBdGxhcyBEZXZpY2UgTWFuYWdlbWVudCB0ZXN0IG9yZyIsImFwcFVybCI6Imh0dHBzOi8veGFwaS1pbnRiLndieDIuY29tL3hhcGkvYXBpL29yZ2FuaXphdGlvbnMvM2E2ZmYzNzMtNjhhNy00NGU0LTkxZDYtYTI3NDYwZTBhYzVjL2FwcHMvYWM2YjY5NzItNTM4ZS0xMWVjLWJmNjMtMDI0MmFjMTMwMDAyIiwiYXBwSWQiOiJhYzZiNjk3Mi01MzhlLTExZWMtYmY2My0wMjQyYWMxMzAwMDIiLCJleHBpcnlUaW1lIjoiMjAyMS0xMi0wNFQwOTozMzowNS44NjcyMTFaIiwiYWN0aW9uIjoicHJvdmlzaW9uIiwid2ViZXhhcGlzQmFzZVVybCI6Imh0dHBzOi8vaW50ZWdyYXRpb24ud2ViZXhhcGlzLmNvbS92MSIsInNjb3BlcyI6InNwYXJrLWFkbWluOmRldmljZXNfcmVhZCxzcGFyay1hZG1pbjp3b3Jrc3BhY2VzX3JlYWQsc3Bhcms6eGFwaV9zdGF0dXNlcyxzcGFyazp4YXBpX2NvbW1hbmRzIiwiaWF0IjoxNjM4NTIzOTg1LCJqdGkiOiJueTBnMFBfZ1RBZV9PbFF2cGtWY1VRPT0iLCJyZWZyZXNoVG9rZW4iOiJaakJqWm1Jd016RXROakV4WWkwME1qTXdMVGszWXpBdE1EYzNPV0V4WkdZNE9EVTJZelptWTJFd1l6Z3RORE0yX0E1MkRfM2E2ZmYzNzMtNjhhNy00NGU0LTkxZDYtYTI3NDYwZTBhYzVjIiwieGFwaUFjY2VzcyI6IntcImNvbW1hbmRzXCI6W1wiTWVzc2FnZS5TZW5kXCJdLFwic3RhdHVzZXNcIjpbXCJSb29tQW5hbHl0aWNzLipcIixcIlBlcmlwaGVyYWxzLkNvbm5lY3RlZERldmljZVsqXS5Sb29tQW5hbHl0aWNzLipcIixcIlN5c3RlbVVuaXQuU3RhdGUuTnVtYmVyT2ZBY3RpdmVDYWxsc1wiLFwiU3RhbmRieS5TdGF0ZVwiXSxcImV2ZW50c1wiOltdfSJ9
.
33XZfYzMEZBvfE_APBxC4uVe6eSKajTlrEZPTeGPBOBapNQhKUzamISzONnHaCfZdt1iMh8Ev3PRE5tHztCmNA
Decoded:
Header:
{
"kid": "gB1snwzlbpotn0WYNmD0SHeU",
"typ": "JWT",
"alg": "ES256"
}
Payload:
{
"sub": "Y2lzY29zcGFyazovL3VybjpURUFNOnVzLWVhc3QtMV9pbnQxMy9PUkdBTklaQVRJT04vM2E2ZmYzNzMtNjhhNy00NGU0LTkxZDYtYTI3NDYwZTBhYzVj",
"oauthUrl": "https://webexapis.com/v1/access_token",
"orgName": "Atlas Device Management test org",
"region": "us-east-2_a",
"appUrl": "https://xapi-a.wbx2.com/xapi/api/organizations/3a6ff373-68a7-44e4-91d6-a27460e0ac5c/apps/ac6b6972-538e-11ec-bf63-0242ac130002",
"appId": "ac6b6972-538e-11ec-bf63-0242ac130002",
"expiryTime": "2021-12-04T09:33:05.867211Z",
"action": "provision",
"webexapisBaseUrl": "https://webexapis.com/v1",
"iat": 1638523985,
"jti": "ny0g0P_gTAe_OlQvpkVcUQ==",
"refreshToken": "ZjBjZmIwMzEtNjExYi00MjMwLTk3YzAtMDc3OWExZGY4ODU2YzZmY2EwYzgtNDM2_A52D_3a6ff373-68a7-44e4-91d6-a27460e0ac5c",
"scopes": "spark-admin:devices_read,spark-admin:workspaces_read,spark:xapi_statuses,spark:xapi_commands",
"xapiAccess": "{\"commands\":[\"Message.Send\"],\"statuses\":[\"RoomAnalytics.*\",\"Peripherals.ConnectedDevice[*].RoomAnalytics.*\",\"SystemUnit.State.NumberOfActiveCalls\",\"Standby.State\"],\"events\":[]}"
}
Signature:
33XZfYzMEZBvfE_APBxC4uVe6eSKajTlrEZPTeGPBOBapNQhKUzamISzONnHaCfZdt1iMh8Ev3PRE5tHztCmNA
Field | Required / Optional | Value space | Description |
---|---|---|---|
kid | Required | String | The Id of the key from the JSON Web Key Set (JWKS) used to sign this JWT |
typ | Required | String | Will be JWT for our use case here |
alg | Required | String | Specifies the signature algorithm used to sign the key: ES256 which means ECDSA using P-256 and SHA-256. |
sub | Required | String | The Webex customer / organization ID. |
oauthUrl | Required | URL | The URL to fetch an OAuth access token from the provided refresh token |
orgName | Required | String | The name of the Webex customer / organization |
region | Required | us-west-2_r , us-east-2_a or eu-central-1_k | The region for the Webex customer. Should be used to determine which JWKS URL to use. |
appUrl | Required | URL | The URL to use to patch details about the integration, like actionUrl, webhookUrl and more. |
appId | Required | UUID | The Id of the integration, same as in the manifest |
expiryTime | Required | Timestamp | ISO8601 UTC date time when this request will expire. If an integration receives a JWT after this date, it should be refused. The expiry is 24 hours. |
action | Required | String | The type of action this JWT embeds, which in the activation case is provision |
webexapisBaseUrl | Required | URL | The base URL for the Webex public APIs. Append the specific API urls (e.g. v1/workspaces , v1/xapi/status ) to this base URL. |
iat | Required | NumericDate | The issued at field identifies the time at which the JWT was issued. |
jti | Required | String | The JWT ID field provides a unique identifier for the JWT. The jti is used to prevent replays of the same message. Integrators should disallow a JWT with a jti that has been seen within the last 24 hours (which is the expiry time of the message). After 24h, a replay will be invalidated by the expiry time. |
refreshToken | Required | String | The OAuth refresh token. This token can be used to get an access token from the oauthUrl. |
scopes | Required | String | A comma separated list of scopes granted. If the manifest has optional scopes, this shows what scopes got approved. |
xapiAccess | Required | Object | The xAPI commands and statuses granted. If the manifest has optional status or commands, this shows which got approved. |
Activation Flow
1. a) Provide Activation Code JWT (Manual)
The activation code JWT is copied from Control Hub and provided to the integration.
1. b) Provide Activation Code JWT (HTTPS)
The JWT is sent in an HTTPS POST (the payload is the same as for the manual case) to the URL provided in the provisioning part of the manifest. At this point, there is no association with the tenant / customer on the integrators side, which means the activation data will have to be stored temporarily with an activation session id embedded in the redirect URL returned. The response must contain said redirect URL in the success case or a detailed (human friendly) error if something goes wrong.
The redirect URL should be a landing page in the 3rd party integration that can authenticate the admin (in case they have an existing account) or allow the creation of a new user. When the admin is authenticated in the 3rd party system, the association between the Cisco activation JWT posted earlier and the 3rd party account can be made (including any additional 3rd party setup needed). The Cisco customer details from the JWT (orgName) can be used to render what customer from Webex the activation applies to.
POST https://example.com/webexintegration
{
"jwt": "eyJhbGciOiJSUzI1NiJ9...."
}
Response 200 OK:
{
"redirectUrl": "https://example.com/webexintegration/setup/81dc908d-39a1-4a11-9dd1-43ab22d1d571"
}
Response 5XX/4XX:
{
"description": "A human friendly description of the failure",
"trackingId": "an-error-reference-to-provide-to-support"
}
2. Validate the activation JWT
When the integration has received the activation JWT, it needs to be validated:
- Read the
region
field from the JWT (prior to validation) and use it to select from the list of regional JSON Web Key Set (JWKS) URLs described earlier. If no match, default to theus-east-2_a
URL. Fetch the JWKS from the URL. - Verify the ES256 signature using the the JWKS. The
kid
in the JWT header indicates what key to use from the key set. If there is no matching key in the key set, the JWT should be considered invalid. - If the signature is valid, the integration should look at the
expiryTime
. If the current time is after this timestamp, the JWT is invalid and cannot be used for activation. - The integration must look at the
appId
and verify that it matches the id provided in the app manifest. - Store the
jti
for up to 24 hours and reject any JWT that contains the same ID at a later point in time.
3. Verify and store the activation payload
The most important part of the activation payload is the refreshToken
. The integration should verify that the token can be exchanged for a valid access token using the oauthUrl
from the JWT and the client ID and client secret from the deployment step:
POST {oauthUrl}
{
"grant_type": "refresh_token",
"client_id": "... from deployment ...",
"client_secret": "... from deployment ...",
"refresh_token": "... from the JWT ..."
}
Response 200 OK:
{
"expires_in": 7199,
"token_type": "Bearer",
"refresh_token": "ZDI3MGEyYzQtNmFlNS00NDNhLWFlNzAtZGVjNjE0MGU1OGZmZWNmZDEwN2ItYTU3",
"refresh_token_expires_in": 5090490,
"access_token": "eyJhbGciOiJSUzI1NiJ9..."
}
The access_token
is the token to use in all requests to the Webex APIs added to the Authorization HTTP header, but do note that it has an expiry and when expired, the integration must fetch a new access token using the refresh token:
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Also note that if the POST returns a new refresh_token
(not the same as the one in the JWT), update to use the one returned from the oauthUrl post.
When the refresh token has been validated, the details from the payload needs to be securely stored and associated with the 3rd party tenant account (if applicable).
4. Update the integration activation status
If the setup is completed on the 3rd party integrations side, this fact must be passed on by a PATCH to the appUrl
from the manifest (using the access token from the previous step), together with the customer specific actionsUrl
. The actionsUrl
is where Webex will send JWT encoded actions to the integration. Note that the actionsUrl
is optional, but not having one will significantly impair the management capabilities (no Webex initiated reprovisioning, updates, health checks etc.).
The integration can also pass in a webhook
definition that will allow receiving xAPI status changes and the integration customer details.
PATCH {appUrl}
Authorization: Bearer {access_token}
{
"provisioningState": "completed",
"actionsUrl": "https://eu.example.com/customer/0beaca8b-3342-4eb4-973f-de70c14f2c91/webexintegration/actions",
"webhook": {
"targetUrl": "https://eu.example.com/customer/0beaca8b-3342-4eb4-973f-de70c14f2c91/webexintegration/sendmedata",
"type": "hmac_signature",
"secret": "my-secret-key-for-signing"
},
"customer": {
"id": "tenant / customer ID",
"name": "display name of the tenant / customer"
}
}
Field | Required / Optional | Value space | Description |
---|---|---|---|
provisioningState | Required | completed or error | The state of the provisioning / activation |
actionsUrl | Optional | URL | The URL where Webex will send JWT encoded actions. Must be an HTTPS URL with a valid certificate and publicly available. |
webhook | Optional | Object | Specifies a webhook to receive xAPI change notifications |
webhook.targetUrl | Required | URL | The URL where the status change notifications will be posted. Must be an HTTPS URL with a valid certificate and publicly available. |
webhook.type | Required | hmac_signature , basic_authentication , authorization_header , none | The webhook authentication strategy to use. Passing none will delete the webhook. |
webhook.secret | Optional | String | The secret to use with the hmac_signature or authorization_header strategies. Must be 20 characters or longer. |
webhook.username | Optional | The username to use with the basic_authentication authentication strategy. | |
webhook.password | Optional | The password to use with the basic_authentication authentication strategy. | |
customer | Optional | Object | The tenant / customer details from the 3rd party integration side |
customer.id | Required | String | The Id of the customer from the integration |
customer.name | Optional | String | The display name of the customer from the integration |
At this point we should have a functioning integration and Control Hub will show the integration as active.
Webhooks
If a webhook was specified during activation, it will receive notifications for supported status and event keys that the integration has been granted access to (access is granted through the xapiAccess
part of the manifest). The following keys are supported:
Supported Events
Frequency | Event keys |
---|---|
Sent immediately when raised | BootEvent , UserInterface.Message.Prompt.Response |
Supported Status
Frequency | Status keys |
---|---|
Sent immediately when the value changes | Standby.State , SystemUnit.State.NumberOfActiveCalls , Conference.Presentation.LocalInstance[*].SendingMode ,RoomAnalytics.Engagement.CloseProximity ,RoomAnalytics.PeoplePresence ,RoomAnalytics.PeopleCount.Current |
Sent at most every minute | Bookings.Availability.Status |
Sent at most every 5 minutes | RoomAnalytics.AmbientNoise.Level.A , RoomAnalytics.AmbientTemperature , Peripherals.ConnectedDevice[*].RoomAnalytics.AmbientTemperature , RoomAnalytics.RelativeHumidity , Peripherals.ConnectedDevice[*].RoomAnalytics.RelativeHumidity , RoomAnalytics.Sound.Level.A , Peripherals.ConnectedDevice[*].RoomAnalytics.AirQuality.Index , RoomAnalytics.ReverberationTime.Middle.RT60 |
A notification is only sent if the value has changed since the last notification. If the value has been unchanged for longer than the defined max frequency, a notification is sent immediately once it changes. If the value changes multiple times during the notification period, the notification will only contain the last value.
Note that the RoomAnalytics
data is only sent if the customer has opted into Workspace Utilization data and/or Workspace Environmental data in Control Hub (these settings are configured under "Workspaces > Settings").
Example Webhook
Let's have a look at an example webhook and the fields.
POST {webhook.targetUrl}
X-Spark-Signature: [... HMAC-SHA1 of the body using the webhook.secret ...]
{
"appId": "ac6b6972-538e-11ec-bf63-0242ac130002",
"deviceId": "Y2lzY29[...]MjZiYzZm",
"workspaceId": "Y2lzY29zcGF[...]NjM=",
"orgId": "Y2lzY29[...]YzVj",
"timestamp": "1970-01-01T00:00:10Z",
"type": "status",
"changes": {
"updated": {
"Standby.State": "Halfwake",
"RoomAnalytics.PeopleCount.Current": 2
},
"removed": [
"Audio.Microphones.Mute"
]
},
"isFullSync": false
}
Fields that are present in both status and event messages
Field | Required / Optional | Value space | Description |
---|---|---|---|
appId | Required | String | The integration ID. |
deviceId | Required | String | The device ID. |
workspaceId | Required | String | The workspace ID. Not present in health check messages. |
orgId | Required | String | The customer ID. Corresponds to the sub in the activation JWT. |
timestamp | Required | Timestamp | ISO8601 UTC date time when this message was sent. |
type | Required | status , events or healthCheck | The type of webhook message. In this case a status change notification. |
Fields that are only present when type is status
Field | Required / Optional | Value space | Description |
---|---|---|---|
changes | Required | Object | Object containing the updated or removed status keys |
changes.updated | Optional | Object | Object containing the updated status keys. In the example above, the standby state and the people count values changed. |
changes.removed | Optional | Array | List of status keys that are no longer present. In the example above, the microphones are no longer muted. |
isFullSync | Required | Boolean | If true, this is an update containing the current state for the subscribed status values. This means that the update was not necessarily triggered by a status change; approximately every hour the devices will send out a "full sync" containing the relevant status keys. |
Fields that are only present when type is events
Field | Required / Optional | Value space | Description |
---|---|---|---|
events | Required | Array | List of events that have been triggered. |
events.key | Required | String | Name of the event. |
events.value | Required | Object | Object containing any additional event data. |
events.timestamp | Required | Timestamp | ISO8601 UTC date time the event occurred at. |
Webhook message authentication
Webhook messages support three forms of authentication:
- HMAC verification: an HMAC (HMAC-SHA1) of the entire webhook payload is computed using the value in the
secret
field as the key and sent in theX-Spark-Signature
header. The recipient must also compute this value and verify that the message is signed with the shared webhook secret. If thetimestamp
is older than 5 minutes, the message must be discarded to avoid replays of old, but valid messages. - HTTP basic authentication: the
username
andpassword
fields are used as credentials with HTTP basic authentication. - Custom authentication token: the value of the
secret
field is sent in theAuthorization
header.
HMAC verification is the recommended authentication strategy. Secrets should also be changed periodically, which the integration can do in a new patch to the appUrl
. The new configuration will apply approx. 5 minutes after it is updated.
Webhook Health Check
To verify that the webhook is working as expected, a payload of type healthCheck
may also be sent. This payload allows Cisco to verify that webhooks are being correctly received on the integrator side. Messages should be authenticated by verifying the contents of the X-Spark-Signature
or Authorization
header, depending on the authentication strategy. If verification succeeds, the integration should respond with a 200 OK.
POST {webhook.targetUrl}
X-Spark-Signature: [... HMAC-SHA1 of the body using the webhook.secret ...]
{
"appId": "ac6b6972-538e-11ec-bf63-0242ac130002",
"timestamp": "1970-01-01T00:00:10Z",
"type": "healthCheck" // standard payloads have type = 'status'
}
Removing a webhook
To remove a webhook, an update (PATCH) needs to be applied, with a webhook type none
:
PATCH {appUrl}
Authorization: Bearer {access_token}
{
"webhook": {
"type": "none"
}
}
anchorManagement
anchorThe integrations framework defines APIs and messages for the management of an integration beyond the initial activation and setup. This is important to manage the life cycle of the integration, from verifying status / health, updating the authorization tokens and to deactivating the integration.
The management APIs can be divided in two:
- Webex to integration: Actions posted to the provided
actionsUrl
. These are signed JWTs similar to the one user in the activation flow. - Integration to Webex: HTTP GET or PATCH of the
appUrl
.
Let's have a look at these in more detail:
Actions: Webex to Integration
The actionsUrl provided by the integration is used to send signed JWT "actions". All the JWT payloads are signed the same way as the activation request (ES256 and ECDSA):
POST {actionsUrl}
{
"jwt": "eyJhbGciOiJSUzI1NiJ9...."
}
The actions supported are as follows:
Health Check
Run a health / connectivity check for the integration. This allows Webex to check the current state and detect if the integration has been removed on the integrator side without a successfully completed removal flow (indicated by the integration returning 404 Not Found or 410 Gone). Note that the most important part of this check is for the integration to verify that it is able to talk to the Webex APIs and report a tokensState. The token state behavior should be as follows:
- valid: The integration can successfully do a GET on the appUrl with a 200 OK
- invalid: A GET on the appUrl returns a 401 / 403 and the app is unable to get a new access token from the refresh token. The fix will be to provision a new refresh token via the "update" action.
- unknown: A GET on the appUrl fails with a non 200/401/403 status or a transient network issue (timeout etc.)
The operationalState can indicate other problems with the integration and the Control Hub admin will be guided to log in to check the state.
Request (JWT)
{
"sub": "Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi8zYTZmZjM3My02OGE3LTQ0ZTQtOTFkNi1hMjc0NjBlMGFjNWM",
"iat": 1516239022,
"jti": "WTJselkyOXpjR0Z5YXpvdkw=",
"appId": "6d93fe09-7130-4507-b261-3908b63428a4",
"action": "healthCheck"
}
Response:
{
"operationalState": "operational",
"tokensState": "valid"
}
Field | Required / Optional | Value space | Description |
---|---|---|---|
operationalState | Required | operational , impaired or outage | The operational state of the integration. If the integration is not operational, it can report either impaired to indicate that some features might not work or outage to indicate a full outage for all integration features. |
tokensState | Required | valid , invalid or unknown | The state of the auth tokens. Should be verified by doing an HTTP GET of the appUrl . |
If the integration uses webhooks, a healthCheck
type payload is also sent to the webhook URL to verify that it is working correctly.
Update
- The
appUrl
and theregion
can change, for example when the Webex customer is moved to a different region. - A new refresh token can be provided, if needed.
Request (JWT)
{
"sub": "Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi8zYTZmZjM3My02OGE3LTQ0ZTQtOTFkNi1hMjc0NjBlMGFjNWM",
"iat": 1516239022,
"jti": "WTJselkyOXpjR0Z5YXpvdkw=",
"appId": "6d93fe09-7130-4507-b261-3908b63428a4",
"action": "update",
"appUrl": "https://xapi-k.wbx2.com/xapi/api/organizations/3a6ff373-68a7-44e4-91d6-a27460e0ac5c/apps/6d93fe09-7130-4507-b261-3908b63428a4",
"region": "eu-central-1_k",
"refreshToken": "eyJhbGciOiJSUzI1NiJ9..."
}
Response 204 No content
Update Approved
The updateApproved
action is sent when the admin has approved an update to a new manifest version. For convenience, the message contains the scopes and xApi access approved from the new manifest (including any optional ones).
When this message is received, the refresh token will already support the new API scopes and the integration can fetch a new access token to start using the new Webex APIs.
Note that in the case this message is lost, the integration can still check the current state by doing an HTTP GET request on the appUrl.
Request (JWT)
{
"sub": "Y2lzY29zcGFyazovL3VybjpURUFNOnVzLWVhc3QtMV9pbnQxMy9PUkdBTklaQVRJT04vM2E2ZmYzNzMtNjhhNy00NGU0LTkxZDYtYTI3NDYwZTBhYzVj",
"iat": 1629278630,
"jti": "v0wyEuKHTxygQB53-h8_qw=="
"action": "updateApproved",
"manifestVersion": "3",
"appId": "a9aecf7e-af2c-48e7-ae61-3f49773922b1",
"scopes": "spark-admin:workspaces_read,spark:xapi_statuses"
"xapiAccess": {"commands": [], "statuses": ["RoomAnalytics.*"], "events": []}
}
Response 204 No content
Deactivate
Deactivate or deprovision
the integration. When this action is received, the integration should be removed and terminated. If the delete is interactive, an optional redirectUrl
can be provided to render a landing page for the customer to perform additional cleanup or capture other information.
Request (JWT)
{
"sub": "Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi8zYTZmZjM3My02OGE3LTQ0ZTQtOTFkNi1hMjc0NjBlMGFjNWM",
"iat": 1516239022,
"jti": "WTJselkyOXpjR0Z5YXpvdkw=",
"appId": "6d93fe09-7130-4507-b261-3908b63428a4"
"action": "deprovision",
"interactive": true
}
Response 200 (with redirectUrl) / 204 No content (if not):
{
"redirectUrl": "https://eu.example.com/customer/0beaca8b-3342-4eb4-973f-de70c14f2c91/apps/6d93fe09-7130-4507-b261-3908b63428a4/removed"
}
Integration to Webex (appUrl)
Update the integration
- The
actionsUrl
can be updated in case there is a need to do so from the integrator. - Request a manifest update by adding a new
manifest.updateRequest
. An update request will trigger a flow where the admin can approve the update to a new version of the manifest including any changes to the permissions needed.
Note that an update request patched as described below will only apply to this customer. For global integrations, this can be used to roll out changes to Beta / Trial customers first, and when ready, Cisco will deploy the new manifest for all remaining customers.
PATCH {appUrl}
Authorization: Bearer {access_token}
{
"actionsUrl": "https://us.example.com/customer/0beaca8b-3342-4eb4-973f-de70c14f2c91/apps/6d93fe09-7130-4507-b261-3908b63428a4",
"updateRequest": {
"newManifest": { ... desired manifest for approval (as described in the manifest section) ... },
}
}
Read the current integration state
Reading the current state allows the integration to:
- Check the current integration manifest (including the version of the integration the customer has running) and trigger an update request if needed. An ongoing update request is indicated by the
updateRequest
object with the new manifest and the update state. - Detect if the integration has been removed on the Webex side without a successfully completed removal flow (indicated by a 404 Not Found or 410 Gone response code). This should trigger cleanup and removal of the integration setup on the integrators side.
- Check what scopes and xAPI access the integration was granted, in case the manifest had optional values.
- Verify that the
actionsUrl
is correct.
GET {appUrl}
Authorization: Bearer {access_token}
{
{
"id": "ac6b6972-538e-11ec-bf63-0242ac130002",
"manifestVersion": 2,
"scopes": [
"spark:xapi_statuses",
"spark:xapi_commands"
],
"xapiAccessKeys": {
"commands": [
"Message.Send"
],
"statuses": [
"RoomAnalytics.*",
"Standby.State"
]
},
"createdAt": "2021-10-27T08:48:26.232928Z",
"updatedAt": "2021-11-19T11:26:50.209904Z",
"provisioningState": "completed",
"actionsUrl": "https://eu.example.com/customer/0beaca8b-3342-4eb4-973f-de70c14f2c91/webexintegration/actions",
"webhook": {
"targetUrl": "https://eu.example.com/customer/0beaca8b-3342-4eb4-973f-de70c14f2c91/webexintegration/sendmedata"
},
"customer": {
"id": "tenant / customer ID",
"name": "display name of the tenant / customer"
}
}
anchorSecurity
anchorSecurity is very important to Cisco and our customers and it's crucial that integrators focus on security when writing the integration. Please have a look at the following security guidelines as you plan and review the implementation of your integration:
Item | Description |
---|---|
Validate all JSON Web Tokens (JWT) | Make sure to verify the signature of the JWT and reject any JWTs where the jti (ID) has been seen before or where the appId does not match the appId provided by the integration in the manifest. In the case of the activation JWT (action: provision), reject any JWT where the expiryTime has passed. For any other JWT action type, an iat older than 5 minutes should be rejected. |
Validate webhook authenticity | HMAC verification is the preferred authentication strategy. Always compute the HMAC-SHA1 for the webhook payload and assert that it is the same as the provided X-Spark-Signature HTTP header, or verify the contents of the Authorization header if basic authentication or a token is used. |
Protect the webhook secret | Use webhook secrets that are unique per customer. Also consider rotating / changing the secret periodically. If you do implement a secret rotation, do note that you need to accept payloads signed with both the old and the new secret for up to 5 minutes after the change. |
Secure the webhook and JWT action transport | Both the webhook and action URLs must be HTTPS with a valid certificate signed by a trusted Certificate Authority. Self-signed certificates are not supported. |
Minimize required xAPI access | Try to minimize the xAPI access required in your manifest. It's tempting to use wildcards to avoid having to specify individual statuses or commands, but this makes it a lot harder for the admin to know and understand what the integration is actually allowed to do. |
Securely store data | All data received by the integration should be securely stored and encrypted at rest. This holds especially true for the clientId , clientSecret and refreshToken . |
anchorIntegration-provided features in Control Hub
anchorIntegration-provided digital signage and Room Navigator persistent web apps are currently in beta and are subject to change. If you're interested in using these features, please go to beta.webex.com to add your account to the Beta program.
Workspace integrations can also provide new functionality in Control Hub. To achieve this, integrations can mark themselves as providing features, which are contracts integrations can specify that they support. Integrations that provide features are highlighted on the integrations page, and when activated, they enhance functionality provided in other areas of Control Hub.
Control Hub supports integration-provided digital signage and Room Navigator persistent web apps. When an integration that is marked as providing digital signage is enabled, it's available as a service in the bulk and single-device digital signage configuration modals. Read more about configuring digital signage on Webex Boards, Room, and Desk series devices here. Similarly, integrations providing Room Navigator persistent web apps appear in the persistent web app configuration modals. Learn how to configure a persistent web app on a Room Navigator device here.
Adding feature support to an integration
To register an integration as providing a feature, a new features
list must be added to the manifest. Adding a feature to this list places requirements on what information it must provide and the actions it needs to support.
Providing features may also require specific permissions. Digital signage requires the spark-admin:devices_digital_signage
scope and persistent web apps require the spark-admin:devices_pwa
scope.
Example
{
"apiAccess": [
{
"scope": "spark-admin:devices_digital_signage",
"access": "required"
},
{
"scope": "spark-admin:devices_pwa",
"access": "required"
}
],
"features": ["digital_signage", "persistent_web_app"]
// ...
}
Manifest Details
Field | Required / Optional | Value space | Description |
---|---|---|---|
features | Optional | Array | The list of features supported: digital_signage , persistent_web_app |
Attention: Adding a feature to the manifest grants the integration access to new management APIs. An integration providing digital signage or persistent web apps is not fully provisioned until it uses the corresponding feature's management API to provide a configuration object. Integrations should upload configurations for the features they provide shortly after being provisioned. If a feature is added to an existing manifest, ensure that configurations are provided to existing installations by listening for the updateApproved
action. Consider adding a periodic check to your integration verifying the feature configurations, in case of failures when storing the configurations or lost updateApproved
actions.
Digital Signage
Adding the digital_signage
feature and the spark-admin:devices_digital_signage
scope to a manifest grants the integration access to a new API. The new API lets the integration provide details regarding the digital signage service it provides.
For integrations providing signage, the link to this API is contained in the signageUrl
property of the provisioning
and updateApproved
JWT tokens. This API allows the integration to create, read, update, and delete the digital signage configuration required by the integration.
Add Signage Configuration
A PUT on the signageUrl
endpoint should contain the following payload:
PUT {signageUrl}
{
"signageUrl": "... signage URL ...",
"crossLaunch": {
"manageContent": {
"url": "... cross-launch URL to manage content ..."
},
"assignContent": {
"url": "... cross-launch URL to assign content ..."
}
}
}
This makes the integration available as a digital signage provider in Control Hub.
Configuration Details
Field | Required / Optional | Value space | Description |
---|---|---|---|
signageUrl | Required | URL | The provider signage URL. Can contain replacement values to allow the vendor to provide device-specific content. |
crossLaunch | Map | Optional | Cross-launch URLs for assigning content to devices. Shows up as actions in Control Hub. |
crossLaunch.assignContent | Map | Optional | Cross-launch URL for administering devices selected in Control Hub. |
crossLaunch.assignContent.url | Required | URL | The URL to post the assign content cross-launch action JWT to. |
crossLaunch.manageContent | Map | Optional | Cross-launch URL for launching to the integration from the integration details page in Control Hub. |
crossLaunch.manageContent.url | Required | URL | The URL to post the manage content cross-launch action JWT to. |
Signage URL Replacement Values
Webex can provide integrations information about the device displaying the digital signage screen by adding it to the URL.
Example
- URL from integration / signage provider:
https://signage.example.com/webex?deviceId=$(deviceId)&deviceName=$(deviceName)
- URL fetched by the device:
https://signage.example.com/webex?deviceId=Y2lzY29...&deviceName=Board%20Room&appId=ac6b6972-538e-11ec-bf63-0242ac130002
Replacement Value Details
Replacement Value | Description |
---|---|
$(deviceId) | The ID of the device: Y2lzY29... |
$(deviceName) | The name of the device: Board Room |
$(deviceModel) | The device model: Y2lzY29... |
$(organizationId) | The organization ID: Y2lzY29... |
The replacement values are resolved by the Webex backend before provisioning the URL to the devices.
Note that there is a _cisco_app_id
URL parameter added at the end of the URL which is used internally by the Webex backend.
Cross-launch to the integration
Control Hub also supports cross-launching to integrations. If the signage configuration specifies manageContent
, an action button appears under the Actions drop-down on the integration details page that allows the administrator to launch the provider UI to do digital signage content management. If assignContent
is specified, a button will show in the bulk and single-device digital signage modals that allows the administrator to manage signage for the selected devices. When a cross-launch is performed, a specific cross-launch JWT is sent to the URL, with the selected devices in a devices
list:
POST {crossLaunch.assignContent.url}
{
"jwt": "eyJhbGciOiJSUzI1NiJ9....",
"devices": [
{
"deviceId": "Y2lzY29..."
}
]
}
JWT
{
"sub": "Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi8zYTZmZjM3My02OGE3LTQ0ZTQtOTFkNi1hMjc0NjBlMGFjNWM",
"iat": 1516239022,
"jti": "WTJselkyOXpjR0Z5YXpvdkw=",
"appId": "6d93fe09-7130-4507-b261-3908b63428a4",
"action": "crossLaunch"
}
Response:
{
"redirectUrl": "... required redirectUrl that will be opened in a new tab by Control Hub ... ",
}
Retrieve Signage Configuration
Integrations can read the stored configuration by performing a GET on the signageUrl
.
GET {signageUrl}
{
"signageUrl": "... signage URL ...",
"crossLaunch": {
"manageContent": {
"url": "... cross-launch URL to manage content ..."
},
"assignContent": {
"url": "... cross-launch URL to assign content ..."
}
}
}
Update Signage Configuration
Integrations can update the digital signage configuration by performing a PUT on the JWT signageUrl
. This automatically provisions the updated digital signage URL configuration to devices using the workspace integration for digital signage.
PUT {signageUrl}
{
"signageUrl": "... signage URL ...",
"crossLaunch": {
"manageContent": {
"url": "... cross-launch URL to manage content ..."
},
"assignContent": {
"url": "... cross-launch URL to assign content ..."
}
}
}
Delete Signage Configuration
Integrations can delete the digital signage configuration by performing a DELETE on the JWT signageUrl
. This removes the digital signage that was configured with the workspace integration from the devices.
DELETE {signageUrl}
Persistent Web App
Managing the Persistent Web App (PWA) configuration requires the persistent_web_app
feature entry and the spark-admin:devices_pwa
scope. The link to the PWA configuration API is contained in the pwaUrl
property of the provisioning
and updateApproved
JWT tokens. This API allows the integration to create, read, update, and delete the PWA configuration.
Add Persistent Web App Configuration
To configure PWA, perform a PUT on the pwaUrl
endpoint with the following payload:
PUT {pwaUrl}
{
"pwaUrl": "... PWA URL ...",
"crossLaunch": {
"manageContent": {
"url": "... cross-launch URL to manage content ..."
},
"assignContent": {
"url": "... cross-launch URL to assign content ..."
}
}
}
This makes the integration available as a PWA provider in Control Hub.
Integrations can also require access to JSXAPI by adding the spark-admin:devices_pwa_jsxapi
scope. This will allow the integration to directly access a subset of the device's xAPI when running as a persistent web app, irrespective of the permissions granted to the integration itself. This is useful for applications where low latency is required (e.g. controlling the device's LED light). Learn more about JSXAPI.
Configuration Details
Field | Required / Optional | Value space | Description |
---|---|---|---|
pwaUrl | Required | URL | The provider PWA URL. Can contain replacement values to allow the vendor to provide device-specific content. |
crossLaunch | Map | Optional | Cross-launch URLs for assigning content to devices. Shows up as actions in Control Hub. |
crossLaunch.assignContent | Map | Optional | Cross-launch URL for administering devices selected in Control Hub. |
crossLaunch.assignContent.url | Required | URL | The URL to post the assign content cross-launch action JWT to. |
crossLaunch.manageContent | Map | Optional | Cross-launch URL for launching to the integration from the integration details page in Control Hub. |
crossLaunch.manageContent.url | Required | URL | The URL to post the manage content cross-launch action JWT to. |
Persistent Web App URL Replacement Values
Webex can provide integrations information about the Room Navigator displaying the persistent web app by adding it to the URL.
Example
- URL from integration / PWA provider:
https://pwa.example.com/webex?deviceId=$(deviceId)&deviceName=$(deviceName)
- URL fetched by the device:
https://pwa.example.com/webex?deviceId=Y2lzY29...&deviceName=Board%20Room&appId=ac6b6972-538e-11ec-bf63-0242ac130002
Replacement Value Details
Replacement Value | Description |
---|---|
$(deviceId) | The ID of the device: Y2lzY29... |
$(deviceName) | The name of the device: Board Room |
$(deviceModel) | The device model: Y2lzY29... |
$(organizationId) | The organization ID: Y2lzY29... |
The replacement values are resolved by the Webex backend before provisioning the URL to the devices.
Note that there is a _cisco_app_id
URL parameter added at the end of the URL which is used internally by the Webex backend.
Cross-launch to the integration
Control Hub also supports cross-launching to integrations. If the PWA configuration specifies manageContent
, an action button appears under the Actions drop-down on the integration details page that allows the administrator to launch the provider UI to do content management. If assignContent
is specified, a button will show in the bulk and single-device PWA modals that allows the administrator to manage PWA for the selected devices. When a cross-launch is performed, a specific cross-launch JWT is sent to the URL, with the selected devices in a devices
list:
POST {crossLaunch.assignContent.url}
{
"jwt": "eyJhbGciOiJSUzI1NiJ9....",
"devices": [
{
"deviceId": "Y2lzY29..."
}
]
}
JWT
{
"sub": "Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi8zYTZmZjM3My02OGE3LTQ0ZTQtOTFkNi1hMjc0NjBlMGFjNWM",
"iat": 1516239022,
"jti": "WTJselkyOXpjR0Z5YXpvdkw=",
"appId": "6d93fe09-7130-4507-b261-3908b63428a4",
"action": "crossLaunch"
}
Response:
{
"redirectUrl": "... required redirectUrl that will be opened in a new tab by Control Hub ... ",
}
Retrieve Persistent Web App Configuration
Integrations can read the stored configuration by performing a GET on the pwaUrl
.
GET {pwaUrl}
{
"pwaUrl": "... signage URL ...",
"crossLaunch": {
"manageContent": {
"url": "... cross-launch URL to manage content ..."
},
"assignContent": {
"url": "... cross-launch URL to assign content ..."
}
}
}
Update Persistent Web App Configuration
Integrations can update the PWA configuration by performing a PUT on the JWT pwaUrl
. This automatically provisions the updated PWA URL configuration to devices using the workspace integration for PWA.
PUT {pwaUrl}
{
"signageUrl": "... PWA URL ...",
"crossLaunch": {
"manageContent": {
"url": "... cross-launch URL to manage content ..."
},
"assignContent": {
"url": "... cross-launch URL to assign content ..."
}
}
}
Delete Persistent Web App Configuration
Integrations can delete the PWA configuration by performing a DELETE on the JWT pwaUrl
. This removes the PWA that was configured with the workspace integration from the devices.
DELETE {pwaUrl}