Webhooks Explained

Webhooks allow your app to be notified when a specific event occurs on Spark. For example, your app can register a webhook to be notified when a new message is posted into a specific room. Webhooks are sent as a JSON document as an HTTP POST to a URL of your choosing each time an event occurs.

Events trigger as they occur in Spark, allowing your app and backend IT systems to stay in sync with new content and room activity.

A Webhooks API allows your app to register a new webhook, update an existing webhook and of course delete a webhook once it's no longer needed.

Resources & Events

Webhooks can be created that only apply to a certain type of event, and be filtered to only send you content that matches a particular item. A webhook that sends you all messages that posted to a room can be filtered to only apply to a specific room, for example. Or you can get a webhook whenever someone is made a moderator of a room.

In order to create a webhook on a resource, the auth token must have read scope for that resource. For example, to create a webhook that receives messages, the application must have been granted the spark:rooms_read scope, and to create a webhook on membership changes to a room, the application must have spark:memberships_read scope. No special scopes are needed to view, update, or delete webhooks - an application can perform these actions on any webhook it created.

Below are a list of each resource and event type that you can watch, and for each type, the available filters you can set.

Resource Event Trigger Filters (optional)
memberships created Someone joined a room that you are in roomId, personId, personEmail, isModerator
memberships updated Someone's membership was updated; primarily used to detect moderator changes roomId, personId, personEmail, isModerator
memberships deleted Someone left or was kicked out of a room that you are in roomId, personId, personEmail, isModerator
messages created New message posted into a room roomId, roomType, personId, personEmail, mentionedPeople, hasFiles
messages deleted A message was deleted roomId, roomType, personId, personEmail, mentionedPeople, hasFiles
rooms created A new room was created by you or one of your integrations type, isLocked
rooms updated A room that you are in was updated; primarily used to detect when a room becomes Locked or Unlocked type, isLocked

The Firehose

Spark Webhooks support what is commonly known as a "firehose" or "wildcard" webhook, allowing your app to subscribe to all events with a single webhook.

A single resource firehose will trigger on any event for a given resource type. For example, to subscribe to all events for the teams resource your app can register a webhook like:

{
   "name": "Teams Firehose",
   "targetUrl": "http://example.com/team-events",
   "resource": "teams",
   "event": "all"
}

The other type of firehose is an all resource firehose. Registering a webhook with resource set to all and event set to all will trigger for any event on all supported resources:

{
   "name": "Awesome Bot Webhook Firehose",
   "targetUrl": "http://example.com/spark-events",
   "resource": "all",
   "event": "all"
}

Creating a webhook on the all resource requires that you have read scope for all resources.

Registering a Webhook

To begin receiving platform events, you first need to register a webhook with Spark. Registering a webhook is simple; simply POST to /webhooks with some basic information and a URL to your backend.

{
   "name": "New message in 'Project Unicorn' room",
   "targetUrl": "http://example.com/spark-hook",
   "resource": "messages",
   "event": "created",
   "filter": "roomId=Y2lzY29zcGFyazovL3VzL1JPT00vYmJjZWIxYWQtNDNmMS0zYjU4LTkxNDctZjE0YmIwYzRkMTU0"
}

The name property is just a label to remember why it was created. Use it for whatever purpose you'd like.

The targetUrl property tells Spark where on your backend you'd like to receive platform events. See the following 'Handling Requests from Spark' section for detailed information about the webhook request format and best practices for scaling your infrastruture.

The resource, event and filter properties work together to narrow the types of platform events being requested.

The resource property indicates the noun being observed and will generally match the plural form of one of the Spark APIs (e.g. messages).

The event property further narrows the events by specifying the specific action that would trigger a notification to your backend.

Lastly, the filter property is a URL-encoded set of filtering critera. Generally speaking, webhook filters will be a subset of the query parameters available when GETing a list of the target resource.

Handling Requests from Spark

When one of your webhooks is triggered, Spark will send an HTTP POST to your backend at the specified targetUrl. The body of the POST will look something like this:

{
  "id":"Y2lzY29zcGFyazovL3VzL1dFQkhPT0svZjRlNjA1NjAtNjYwMi00ZmIwLWEyNWEtOTQ5ODgxNjA5NDk3",
  "name":"Guild Chat to http://requestb.in/1jw0w3x1",
  "resource":"messages",
  "event":"created",
  "filter":"roomId=Y2lzY29zcGFyazovL3VzL1JPT00vY2RlMWRkNDAtMmYwZC0xMWU1LWJhOWMtN2I2NTU2ZDIyMDdi",
  "orgId": "Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi8xZWI2NWZkZi05NjQzLTQxN2YtOTk3NC1hZDcyY2FlMGUxMGY",
  "createdBy": "Y2lzY29zcGFyazovL3VzL1BFT1BMRS8xZjdkZTVjYi04NTYxLTQ2NzEtYmMwMy1iYzk3NDMxNDQ0MmQ",
  "appId": "Y2lzY29zcGFyazovL3VzL0FQUExJQ0FUSU9OL0MyNzljYjMwYzAyOTE4MGJiNGJkYWViYjA2MWI3OTY1Y2RhMzliNjAyOTdjODUwM2YyNjZhYmY2NmM5OTllYzFm",
  "ownedBy": "creator",
  "status": "active",
  "actorId": "Y2lzY29zcGFyazovL3VzL1BFT1BMRS8xZjdkZTVjYi04NTYxLTQ2NzEtYmMwMy1iYzk3NDMxNDQ0MmQ",
  "data":{
    "id":"Y2lzY29zcGFyazovL3VzL01FU1NBR0UvMzIzZWUyZjAtOWFhZC0xMWU1LTg1YmYtMWRhZjhkNDJlZjlj",
    "roomId":"Y2lzY29zcGFyazovL3VzL1JPT00vY2RlMWRkNDAtMmYwZC0xMWU1LWJhOWMtN2I2NTU2ZDIyMDdi",
    "personId":"Y2lzY29zcGFyazovL3VzL1BFT1BMRS9lM2EyNjA4OC1hNmRiLTQxZjgtOTliMC1hNTEyMzkyYzAwOTg",
    "personEmail":"person@example.com",
    "created":"2015-12-04T17:33:56.767Z"
  }
}

The first few properties are called the "envelope" and they identify all webhooks that are sent. This envelope contains:

  • id is the webhook ID. This is the same ID returned when you created the webhook and is what you would use to view the webhook configuration or delete the webhook.
  • name is the name you gave your webhook when you created it.
  • resource is the resource the webhook data is about.
  • event is the type of event this webhook is for.
  • filter lists any filters that you applied that triggered this webhook
  • orgId is the Organization that owns the webhook. This usually is the organization the user that added the webhook belongs to. This ID can be used to view the Organization details with the API.
  • createdBy is the personId of the user that added the webhook.
  • appId identifies the application that added the webhook.
  • ownedBy indicates if the webhook is owned by the org or the creator. Webhooks owned by the creator can only receive events that are accessible to the creator of the webhook. Those owned by the organization will receive events that are visible to anyone in the organization.
  • status indicates if the webhook is active. A webhook that cannot reach your URL is disabled. Thus, for all webhooks you actually see, the status will always read 'active'.
  • actorId is the personId of the user that caused the webhook to be sent. For example, on a messsage created webhook, the author of the message will be the actorId. On a membership deleted webhook, the actor is the person who removed a user from a room.

The data property contains the JSON representation of the resource that triggered the webhook. For example, if you registered a webhook that triggers when messages are created (i.e. posted into a room) then the data property will contain the JSON representation for a message resource, as specified in the Messages API documentation.

Sensitive Data

Spark encrypts sensitive room content such as titles and message text using keys only available to the members of that room. This means that not even the Spark operations team can read your messages, making it one of the most secure communications platforms on the planet. This level of security has a subtle impact on webhooks.

You'll notice in the above message webhook example that the text property is missing. That's because room messages are considered sensitive information and since Spark initiated the request to your backend, it did not have your Access Token with which to decrypt the message. In order to get the sensitive information, your app needs to use the resource id to fetch the full resource. Using the above messages example, your app could fetch the complete message object along with the text by doing an authenticated (via your Bearer Token) GET request to /messages/{id} like so:

GET /messages/Y2lzY29zcGFyazovL3VzL01FU1NBR0UvMzIzZWUyZjAtOWFhZC0xMWU1LTg1YmYtMWRhZjhkNDJlZjlj
{
  "id":"Y2lzY29zcGFyazovL3VzL01FU1NBR0UvMzIzZWUyZjAtOWFhZC0xMWU1LTg1YmYtMWRhZjhkNDJlZjlj",
  "roomId":"Y2lzY29zcGFyazovL3VzL1JPT00vY2RlMWRkNDAtMmYwZC0xMWU1LWJhOWMtN2I2NTU2ZDIyMDdi",
  "personId":"Y2lzY29zcGFyazovL3VzL1BFT1BMRS9lM2EyNjA4OC1hNmRiLTQxZjgtOTliMC1hNTEyMzkyYzAwOTg",
  "personEmail":"person@example.com",
  "text":"Something interesting and potentially sensitive",
  "created":"2015-12-04T17:33:56.767Z"
}

Authenticating Requests

Webhooks require that the Cisco Spark Cloud be able to reach your backend over HTTP. One common question from enterprise IT is how to authenticate that a request is coming from Cisco. We are continuously adding infrastructure to the Spark Cloud so IP whitelisting is not practical. To solve this, we introduced a technique for cryptographically verifying the JSON payload based on a shared secret of your choosing.

When creating a webhook you can now supply and secret parameter. If specified, every message sent to your backend will contain a new HTTP header called X-Spark-Signature containing an HMAC-SHA1 signature of the JSON payload.

See the docs for creating a webhook for more details on the secret parameter.