Log inSign up

Building a More Secure Bot

June 10, 2017
Taylor Hanson
Taylor HansonTechnical Solutions Specialist
Building a More Secure Bot

Updated September 2026: This post has been refreshed with current Webex bot and webhook security guidance.

This tutorial shows how to protect an existing Webex bot at the webhook boundary and in your application code. You will configure a narrow webhook subscription, allow only approved senders, and reject requests that do not match your webhook secret.

This post was originally published in June 2017 and has been updated for the current Webex bots and webhooks documentation.

If you are building your first bot, start with the Webex Bots guide. Return here after your bot can receive and send messages.

1. Limit the events your bot receives

Webex bots can receive messages in 1-to-1 spaces. In group spaces, a bot can access messages only when someone mentions it. You can also add webhook filters so Webex sends only the events your bot needs.

For example, this webhook receives new messages in one space and includes a shared secret for request verification:

{
  "name": "Secure bot messages",
  "targetUrl": "https://example.com/webhooks/webex",
  "resource": "messages",
  "event": "created",
  "filter": "roomId=ROOM_ID&mentionedPeople=me",
  "secret": "replace-with-a-strong-random-secret"
}

Replace ROOM_ID and the example secret value with values from your environment. Store the secret in a secret manager or environment variable; do not commit it to source control.

The mentionedPeople=me filter is useful for group spaces because it limits message events to messages that mention the bot. You can combine filters with &; see the Webhooks Guide for the available message filters and required scopes.

2. Authorize the sender in your bot

Webhook filters reduce unwanted traffic, but they do not replace authorization in your application. Check the event's actorId against an allowlist, or resolve the actor with GET /people/{actorId} and confirm that the returned organization is allowed to use your bot.

Do not use the webhook envelope's orgId as the sender's organization. That field identifies the organization that owns the webhook. The webhook's data object contains the message resource, and its id can be used to retrieve the full message with the bot's access token when needed.

Ignore events generated by the bot itself so a response does not trigger an unwanted loop. Return a successful response for events that you intentionally ignore; Webex expects a 2xx response for successful delivery.

3. Verify the webhook signature

When you create a webhook with a secret, Webex signs the raw JSON payload with HMAC-SHA1 and sends the result in the X-Spark-Signature header. This example uses SHA-1 only because it is part of that Webex webhook contract, not as a general cryptographic recommendation. Compute the signature over the unmodified request bytes and compare it with hmac.compare_digest before parsing or acting on the payload.

Header names are case-insensitive. The example reads the lowercase spelling so it works with HTTP/2 delivery, where header names are transmitted in lowercase.

Some webhook types also provide HMAC-SHA256 and HMAC-SHA512 signatures in the X-Webex-Signature header. Prefer that header when it is available; this example uses the HMAC-SHA1 X-Spark-Signature path. See the API changelog for the signature options.

import hashlib
import hmac
import json
import os
from typing import Optional

from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["WEBEX_WEBHOOK_SECRET"].encode("utf-8")
BOT_PERSON_ID = os.environ["WEBEX_BOT_PERSON_ID"]
ALLOWED_PERSON_IDS = {"APPROVED_PERSON_ID"}
ALLOWED_ROOM_IDS = {"APPROVED_ROOM_ID"}


def valid_signature(raw_body: bytes, signature: Optional[str]) -> bool:
    if not signature:
        return False

    expected = hmac.new(
        WEBHOOK_SECRET,
        raw_body,
        hashlib.sha1,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


@app.post("/webhooks/webex")
def receive_webhook():
    raw_body = request.get_data()
    signature = request.headers.get("x-spark-signature")

    if not valid_signature(raw_body, signature):
        return "", 401

    try:
        event = json.loads(raw_body)
    except json.JSONDecodeError:
        return "", 400

    if not isinstance(event, dict):
        return "", 400

    if event.get("resource") != "messages" or event.get("event") != "created":
        return "", 204

    actor_id = event.get("actorId")
    data = event.get("data") or {}
    if (
        not actor_id
        or actor_id == BOT_PERSON_ID
        or actor_id not in ALLOWED_PERSON_IDS
        or data.get("roomId") not in ALLOWED_ROOM_IDS
    ):
        return "", 204

    # Fetch data["id"] from /messages/{id} with the bot access token,
    # then run your application logic.
    return "", 204

The request.get_data() call must happen before the framework normalizes the body. If you parse and re-serialize the JSON first, formatting changes can produce a different HMAC and make a valid request fail verification. In a production receiver, catch the JSON parser's specific decode exception and return a 4xx response for malformed payloads.

Keep the bot's credentials and endpoint safe

Use HTTPS for the webhook target and keep the bot access token and webhook secret outside your repository. Rotate the secret through your deployment configuration, and accept only the signature generated from the raw body. Do not log access tokens, webhook secrets, or complete message payloads that may contain sensitive content.

For the full webhook payload format, delivery requirements, and available filters, see the Webhooks Guide. For bot registration, access tokens, and message behavior, see Bots. You can also review the focused Using a Webhook Secret tutorial.

Blog Categories
  • Product Announcements
  • How To
  • Events
  • Developer Stories
Share This Article

Connect

Support

Developer Community

Developer Events

Contact Sales

Handy Links

Webex Ambassadors

Webex App Hub

Resources

Open Source Bot Starter Kits

Download Webex

DevNet Learning Labs

Terms of Service

Privacy Policy

Cookie Policy

Trademarks

© 2026 Cisco and/or its affiliates. All rights reserved.