Webex Teams recently released support for Adaptive Cards, which allow the user to interact with a teams user (typically a bot) without leaving the page. This is great for taking quick polls, and also piping data from other applications like help desk, travel and expenses, crm, etc.

I love that Cisco adopted the Microsoft standard for Adaptive Cards instead of trying to create their own unnecessarily. The website, adaptivecards.io has great documentation, and a designer to help get started. There is an author sdk as well, but I find that using a templating system such as Jinja2 does the job without learning anything new.


I teach a class at our companywide conference every year called UC Automation, and I thought what a better way to teach about Buttons and Cards (aka Adaptive Cards) than by using it in the class itself. A month prior to the conference, I sent a poll to everyone that registered so they could pick the topics they want to hear about most.

UC Automation: Drinking From the Firehose

To use Adaptive Cards in Webex Teams, I created an example in 🐍Python

First install prerequisites:

pip install requests
pip install webexteamssdk
pip install flask

brew install ngrok
ngrok http 3000


import json
import os
import requests
from webexteamssdk import WebexTeamAPI, ApiError
from flask import Flask, request
app = Flask(__name__)

For prototyping, we use ngrok.
We'll request the tunnel and parse the url to use for a webhook
tunnel = json.loads(
  requests.request('GET', url = 'http://localhost:4040/api/tunnels'
public_url = tunnel['tunnels'][0]['public_url']

Specify the webex token and roomId to use
token = os.environ['token']
roomId = os.environ['roomId']

Using webexteamssdk but also need requests
for attachment action endpoint which is not in sdk yet!
wbx = WebexTeamsAPI(access_token = token)
headers = {
  'Authorization': 'Bearer ' + token

Register webhook to ngrok for attachmentActions
for webhook in wbx.webhooks.list():

  name = 'Development - ngrok',
  targetUrl = public_url,
  resource = 'attachmentActions',
  event = 'created'

Paste Card from adaptivecards.io/designer to a file named card.json 
attachments = []
attachment = {}
attachment['contentType'] = "application/vnd.microsoft.card.adaptive"
attachment['content'] = json.loads(open('card.json').read())

Send Message
  roomId = roomId, 
  markdown = '.', 
  attachments = attachments

Receive Data in Webhook and Request Action Payload
@app.route('/', methods = ['POST'])
def index():
  action = request.json['data']['id']
  results = requests.request('GET',
  headers = headers,
  url = f '{wbx.base_url}attachment/actions/{action}'
  return ('', 200, None)

if __name__ == '__main__':
  app.run(port = 3000, use_reloader = True)

Just like with the messages webhook resource, the payload does not include the data, but rather an action id that is used along with the token to retrieve the data, shown below:

{'created': '2019-11-08T21:47:14.436Z',
 'id': 'asdfasdfasdfasdf',
 'inputs': {
  'comments': 'Here’s a great idea.  Get a real job!'
 'messageId': 'qwerqwerqwerqwerqwerqwer',
 'personId': 'fghjfghjfghjfghjfghjfghj',
 'roomId': 'vbnmbnmvbnmvbnmvbnmvbnm',
 'type': 'submit'}

For my UC Automation class, I stored the results in AWS. Below is a fun script to gather the results and display them with emojis.

from boto3 import *
table = resource('dynamodb').Table('count')

results = table.get_item(Key={'use': 'uc250'})
results = rd.replace_decimals(results['Item'])
del results['use']
results = list(results.items())

for topic in results:
    chart = 😎
    for i in range(0, int(topic[1])):
        chart = chart+'😎 '
    print(topic[0]+': '+chart)

$ python collect.py

expressway: 😎 😎 😎 😎 
guest: 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 
cucm: 😎 😎 😎 😎 😎 😎 😎 
buttons: 😎 😎 😎 😎 😎 
admin: 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 😎 
xapi: 😎 😎 😎 
browser: 😎 😎 
curri: 😎 😎  
cms: 😎 
meetings: 😎 😎 😎 

Anyone who has deployed Jabber knows of the cisco-uds SRV record that Jabber uses (or Expressway-C in the case of MRA) to discover it’s services. It’s also used for directory searches, and home cluster lookup with ILS.

UDS or User Data Services is a simple REST based API for CUCM. While the UDS API is not as extensive as what you can do with AXL through SOAP and WSDL, it makes a great use case for a front end to allow end users to manage their own devices: Change their single number reach destination, password, speed dials, and conference pins.

For security reasons, most browsers will block an XMLHttpRequest served on one page and originating on another (in the case of a front end making UDS calls to CUCM). To get around this, you would use CORS or Cross Origin Resource Sharing, defining the front end URL inside CUCM.

UDS supports REST queries POST/PUT/GET/DELETE in XML format. Some calls do not require authentication and some do. Ones that do will use Basic authentication built into the browser which encodes the username:password in Base64 format.

UDS resources that do not require authentication
clusterUser, installedLocales, models, options, phoneService(s), servers, timezones, users, version

GET https://{host}:8443/cucm-uds/users?last=Smit
GET https://{host}:8443/cucm-uds/clusterUser?username={userId}

UDS resources that require authentication
credentials, device(s), extension(s), remoteDestination(s), speedDial(s), subscribedService(s), user, userPolicy

POST https:Β­//{host}:8443/cucm-uds/user/{userId}/speedDials
<!–add speedDials example request body–>

CUCM UDS API Reference
CUCM UDS Developer Guide
CUCM UDS Authentication Guide
Base64 Encoder/Decoder

You’ve probably heard a lot of buzzwords lately: API, JSON, and REST among them, however truth be told, REST or Representational State Transfer has always been around as the “language of the internet” (a REST HTTP GET brought you this page!), so it goes without saying that in the age of Internet of Things, REST would become infinitely more important.

REST is made up of 4 things

URI: https://api.ciscospark.com/v1/rooms
Request Type: POST, GET, PUT, etc
Header: Authorization (API key), Content-Type (html, application/xml, application/json)
Body: { “function” : “sendMessage”, “message” : “this is a message”}

JSON is the preferred format over XML because its more efficient. With PHP methods json_encode() and json_decode() for instance, it’s very easy to parse arrays into JSON format and vis a visa.

One of the great things about REST is that when a call is made, it sends a response, which can then in turn be a variable in a second response and so on. It’s not client-server, it’s a conversation.


Check out my demo’s for Spark API and Tropo API for a full writeup of how to write some simple REST calls using an html form and some PHP.