CreateBlogSupport
Log inSign up
Home
Webex Contact Center
  • Overview
  • Guides
  • API REFERENCE
  • AI
  • Configuration
  • Data
  • Desktop
  • Media And Routing
  • Customer Journey Data Service
  • Webhooks
  • Changelog
  • Contact Center Sandbox
  • Using Webhooks
  • Beta Program
  • Webex Status API
  • XML API Deprecation

Webex Contact Center

Using Webhooks

Webhooks enable you to receive notifications and react to events occurring within Webex Contact Center.

anchorUse Cases

anchor

Webhooks form a great system for working with near-real-time data, and for reacting to Contact Center events using custom logic. If you want to stay on top of a busy contact center, or have an application which can’t wait for slow batch reports or expensive API polling-and-diffing solutions, then you're in the right place.

anchorDesign Considerations

anchor

As with all webhook systems, http webhook delivery is not always guaranteed, due to the nature of HTTP. For this reason, integrations should not rely solely on webhooks to synchronize with Webex Contact Center. For this reason, we aim to quickly deliver webhooks over delivering all webhooks. The best practice is to implement a reconciliation job using our APIs and run it occasionally (say, nightly) to synchronize all activities that occurred since the last reconciliation.

Furthermore, since webhooks are delivered via HTTP, we cannot guarantee that webhooks would arrive in order. This often complicates stateful calculations. Therefore, build your system/logic accordingly.

Lastly, webhooks may be delivered multiple times. Build your system/logic accordingly.

anchorCustomer Onboarding Page

anchor

Before your app can receive any webhooks, a user with an administrator role must opt in to send events to your service by creating a webhook Subscription. Your app is responsible for building this onboarding page.

A Subscription is how your app states which events it should be notified of, and where the notifications should be sent. When notifying your app of an event, we send POST requests to the destinationUrl field of the registered Subscription. This field has some restrictions; check the Subscription API docs for restrictions on this field. Note that it must use https and cannot have query parameters.

Authentication and Authorization

Consult our Authentication guide on how to build a UI app with Cisco Common Identity. This is necessary because a Cisco Common Identity token is required to call the Subscription APIs.

To use the Subscription APIs, the user may be an organization admin, or a partner/delegated admin configuring on behalf of an organization. Additionally, the token must have the cjp:config_write scope.

Subscription Lifecycle and Onboarding Page Functionality

A Subscription resource is first registered, and may be updated, and then deleted. All of these functions must be supported by your page. If your Subscriptions were disabled (by the system or by API) or deleted, reenabling or recreating the Subscriptions would require a token.

An organization has an upper limit on the total number of Subscriptions. Ensure that you clean up Subscriptions and keep your total count low.

When you clean up Subscriptions, ensure that you remove your app's Subscriptions only. Use the name, description, or destinationUrl fields to find out which Subscriptions are managed by your app.

anchorReceiving Webhooks

anchor

We draw inspiration from the CloudEvents.io JSON format, HTTP Structured Content Mode, and Webhook specifications. The main areas in which we differ are Webhook authorization, and managing request rates and 429s.

If you are not familiar with CloudEvents spec, do not worry. Here's what you need to know:

Anatomy of a Webhook

We send POST requests to the destinationUrl of the Subscription. Here is a cURL request similar to what a webhook would look like:

Version 1.0 subscription

curl \
    -X POST \
    http://localhost:1337/v1/notifications \
    -H "content-type: application/json" \
    -H "user-agent: " \
    -H "X-WebexCC-Signature: 227c6b5aeb154b9d034aadd65fbc50f1dfa6dd5f91feb7f7c91f9449e3730285" \
    -H "connection: keep-alive" \
    --data-raw '{"comciscoorgid":"3eb3c4b5-6c60-4893-8bd8-8454537324e4","data":{"...":"..."},"datacontenttype":"application/json","id":"e9619c7b-8f8a-45e6-9f32-f44ecef98472","source":"/com/cisco/wxcc/fdcac25f-c5e3-43ba-8455-67c44cf4936c","specversion":"1.0","type":"agent:login"}'

Version 2.0 subscription

With Version 2.0 based subscription, we add the X-WebExCC-Timestamp and X-WebexCC-Webhook-Version headers:

curl \
    -X POST \
    http://localhost:1337/v1/notifications \
    -H "content-type: application/json" \
    -H "user-agent: " \
    -H "X-WebexCC-Signature: a68fac8fcf67a36071139c2808d90965aed73109b1e86386c03802e53fad51bb" \
    -H "X-WebExCC-Timestamp: 1697211697694" \
    -H "X-WebexCC-Webhook-Version: agent:1.0.0" \
    -H "connection: keep-alive" \
    --data-raw '{"comciscoorgid":"3eb3c4b5-6c60-4893-8bd8-8454537324e4","comciscotimestamp": "1697211697694","data":{"...":"..."},"datacontenttype":"application/json","id":"e9619c7b-8f8a-45e6-9f32-f44ecef98472","source":"/com/cisco/wxcc/fdcac25f-c5e3-43ba-8455-67c44cf4936c","specversion":"1.0","type":"agent:login"}'

Note: When testing the sample code below with a 2.0 payload, the above curl command includes an expired timestamp. Consequently, upon running the code, you will encounter a validation error message. To resolve this, replace the outdated X-WebExCC-Timestamp and comciscotimestamp with the current Unix timestamp. After that, generate a new hash for the X-WebexCC-Signature and update it in the curl request to successfully pass the replay attack validation step.

The request body follows this structure:

For Version 1.0 based subscription

{
    "comciscoorgid": "3eb3c4b5-6c60-4893-8bd8-8454537324e4",
    "data": {
        "...": "..."
    },
    "datacontenttype": "application/json",
    "id": "e9619c7b-8f8a-45e6-9f32-f44ecef98472",
    "source": "/com/cisco/wxcc/fdcac25f-c5e3-43ba-8455-67c44cf4936c",
    "specversion": "1.0",
    "type": "agent:login"
}

For Version 2.0 based subscription

{
    "comciscoorgid": "3eb3c4b5-6c60-4893-8bd8-8454537324e4",
    "comciscotimestamp": "1697211697694",
    "data": {
        "...": "..."
    },
    "datacontenttype": "application/json",
    "id": "e9619c7b-8f8a-45e6-9f32-f44ecef98472",
    "source": "/com/cisco/wxcc/fdcac25f-c5e3-43ba-8455-67c44cf4936c",
    "specversion": "1.0",
    "type": "agent:login"
}

The JSON body shown above is an envelope around the data field, which varies by the event type. The envelope follows the CloudEvents.io JSON format. Here's what they mean:

  • id: Unique per source event (but not per notification; one source event may trigger multiple notifications). You may use this for event deduplication.
  • source: Contains the Subscription ID at the end.
  • type: The type of event (indicated as eventType in the Subscription API).
  • campaignId: (only provided for campaign events) The ID of the campaign.
  • comciscoorgid: Org ID of the organization where the event occurred.
  • comciscotimestamp: (Version 2.0+) The UTC time in epoch ms that the request body is constructed. Should match X-WebExCC-Timestamp header.

The request will be made only over HTTPS with a valid certificate using TLS. Self-signed certificates will not work. When you first start development, a tool such as ngrok can help you get past this requirement.

Not Receiving Events?

Double-check:

  • Events in Webex Contact Center are occurring.
  • The subscription exists, is enabled, and has the correct destinationUrl.
  • Your company/datacenter firewall could be blocking incoming HTTPS requests. Please note that we cannot provide IT with static IPs, as our cloud infrastructure is dynamic in nature.
Request Verification

To verify requests are originating from Webex Contact Center, are authentic, and are unmodified, you may verify webhooks using a cryptographic signature. Receivers are strongly encouraged to implement this feature for their own security.

When a Subscription is registered, a secret may be provided. Providing a secret causes our system to include the X-WebexCC-Signature header in webhooks. To verify a webhook, you must supply the unmodified request body and Subscription's secret to the HMAC-SHA256 algorithm, then compare the hex-encoded output with the header's value. If they match, then the webhook is valid. Note that this system relies on the provided secret field being confidential.

Built-ins or packages exist for most language ecosystems for taking HMAC signatures. The sample code, below, implements this check in Node.

Replay attack prevention (2.0 based subscriptions only)

For V2 based subscriptions, we provide a timestamp indicating when the event was dispatched, as a part of body comciscotimestamp and header X-WebExCC-Timestamp. This timestamp, in conjunction with HMAC signatures, gives a way to validate when the event was sent out, and mitigate replay attacks.

At the time of receiving an event, making sure the timestamp in header is same as the timestamp in body, and verifying HMAC signature, ensures integrity of data and the timestamp is not tampered. If the timestamp is within an acceptable timeframe, then it is a valid request. Otherwise the request should be treated as unauthenticated.

The sample code below contains a way to check this.

Identifying event version (2.0 based subscriptions only)

With V2 based subscriptions, the event request contains POST header X-WebexCC-Webhook-Version indicating the version of event, thus providing a way to handle the expected payload based on event version.

You would want to migrate your subscription to newer version of events (as and when it's released), to get the best and latest of contact center webhook events. Older version of events will become inactive at some point in time.

Response
Code

Any 2xx response is acceptable.

Any other response (if there is a response) is considered a failure.

Redirects (3xx or otherwise) are never followed.

Latency

Must respond within 5 seconds. Consider using a 202 Accepted pattern, where you accept the request and queue the event to be processed elsewhere.

anchorMigrate from V1 to V2 Subscriptions

anchor

In order to add versioning support for webhook events, V2 based Subscription APIs have been introduced. You will be able to specify the version of webhook event you are interested in as part of the Subscription API.

Webhook events are versioned at resource level i.e. agent, task, capture, campaign. Refer to the List Event Types /v2/list-event-types API for details on the latest available resources and versions. As the current webhook events are already in production, they inherently carry a default version of 1:0:0.

The end-of-life date for V1-based subscriptions is yet to be determined but our recommendation is migrating and leveraging V2 version to avoid any service disruption. Also, newer features will be part of newer resource versions.

Please note that subscriptions created with V2 API will exclusively be accessible through /v2 based Subscription APIs. Similarly, subscriptions created with the V1 API will remain accessible exclusively through /v1 based Subscription APIs.

Migration Guide

The following guide outlines the process for migrating from V1 based subscriptions to V2 based subscriptions.

  1. Initiate Migration:

    Create a new subscription by utilizing the /v2 API. Ensure that the information provided aligns with your previous subscriptions established through the /v1 API. As part of the request, add a new field, resourceVersion, in the request body, with a value formatted as resource:version. For example, for agent resource, put agent:1.0.0 Consider adding a secret as well, if you would like to have security features like data integrity validation and replay attack prevention.

  2. Validate Integration with new subscription:

    Webhook events will be sent to both V1 and V2 based subscriptions as long as they exist. Validate your integration is working with the new subscription and you are able to receive webhook events.

  3. Post validation:

    Once, verification is completed, delete the older subscription that is no longer needed using /v1 Delete API.

anchorExample Node.js app

anchor

This sample app receives webhooks at /v1/notifications. If the webhook is invalid or the event is not for a known customer in the app's database, the app responds to the webhook and stops processing. Otherwise, it pushes the event data into a queue, which processes about 1 second later by logging agent:login events only.

const express = require("express");
const crypto = require("crypto");

const app = express();

// parse JSON bodies, but save raw body
app.use(
    express.json({
        verify: function (req, res, buf, encoding) {
            req.rawBody = buf.toString();
        },
    })
);

app.post("/v1/notifications", (req, res) => {
    // Check if org is your paying customer (a form of Authorization)
    const body = req.body;
    const orgId = body.comciscoorgid;

    // Verifying Webhook signature
    let hash;
    try {
        const subscriptionId = req.body.source.slice(
            req.body.source.lastIndexOf("/") + 1
        );
        const secret = getSecretBySubscriptionId(subscriptionId);
        hash = crypto
            .createHmac("sha256", secret)
            .update(req.rawBody)
            .digest("hex");
    } catch (e) {
        console.warn("Could not generate verification hash for webhook.", e);
        // Webhook was received, so give 200 response.
        res.status(200).send();
        return; // stop processing
    }

    const signature = req.headers["x-webexcc-signature"];

    if (signature !== hash) {
        console.warn("The secrets did not match. Aborting.", signature, hash);
        // Webhook was received, so give 200 response.
        res.status(200).send();
        return; // stop processing
    }

    // For V2 based subscriptions only: replay attack prevention 
    // check if timestamp in body matches with header, and is within acceptable limit
    const webhookTimestamp = body.comciscotimestamp;
    const tolerance = Date.now() - (5 * 60 * 1000); // 5 min tolerance. You could have a different tolerance in your app
    if (webhookTimestamp == req.headers["x-webexcc-timestamp"] && webhookTimestamp < tolerance) {
      // The request timestamp is outside of the tolerance zone
      console.warn(`The webhook timestamp (${webhookTimestamp}) is beyond tolerance (${tolerance}). 
        Possible replay attack. Aborting. hash=${hash}`);
      // return any status or message as deemed appropriate for your app  
      res.status(403).send('Request expired');
      return;
    }

    // Signature is valid, so webhook is authentic.
    // Is this organization authorized? (example business logic)

    let orgInfo;
    try {
        orgInfo = getOrgInformation(orgId);
    } catch (e) {
        console.warn(
            `Org ID '${orgId}' is not a paying customer! Ignoring webhook.`
        );
        // Webhook was received, so give 200 response.
        res.status(200).send();
        return; // stop processing
    }

    // Passed all checks!

    // get event version info (only for V2 based subscriptions)
    const resourceVersion = req.headers["x-webexcc-webhook-version"]
    // Defer processing to later by pushing to a queue and responding ASAP.
    pushToQueue({ body, orgInfo, resourceVersion });
    // If you respond slowly, your Subscription may later be disabled!

    res.status(200).send();
});

function getOrgInformation(orgId) {
    // In your app, you might try to fetch customer information from a database
    // for this example, hardcoding a map of customers
    const orgIdDatabase = new Map([
        [
            "3eb3c4b5-6c60-4893-8bd8-8454537324e4",
            { name: "My Favorite Customer" },
        ],
    ]);

    const orgInfo = orgIdDatabase.get(orgId);
    if (!orgInfo) throw new Error("Not a paying customer");
    return orgInfo;
}

function getSecretBySubscriptionId(subscriptionId) {
    // In your app, you might try to fetch the secret from a cache or a secret store
    // for this example, using a map from subscriptionId to secret.

    const subscriptionIdSecretDatabase = new Map([
        // sample code will always have an entry; your DB will have actual subscription IDs
        [
            subscriptionId,
            "preshared-secret-string-used-for-validating-webexcc-webhooks",
        ],
    ]);

    const secret = subscriptionIdSecretDatabase.get(subscriptionId);
    if (!secret) throw new Error("No secret found for the given subscription");
    return secret;
}

function pushToQueue(event) {
    // In your app, you might push to an event bus or an in-memory queue
    // for this example, pushing to JS event loop (kinda like a queue)

    console.log("Pushing event into queue...");
    setTimeout(() => {
        processEvent(event);
    }, 1000);
}

function processEvent(event) {
    // In your app, you might do many things here.
    // for this example, pretend we're only interested in agent:login events
    switch (event.body.type) {
        case "agent:login":
            // "event.resourceVersion" will be present only for V2 based subscriptions
            // Here, we print the event's resource version. In your app, you will need to handle 
            // the versions, and its expected payload.
            console.log(
                `New agent logged in ${event.orgInfo.name}! event version ${event.resourceVersion}`,
                event.body.data
            );
            break;
        default:
            console.warn(
                `Unexpected event type '${event.body.type}', ignoring. Maybe the Subscription is misconfigured?`
            );
            break;
    }
}

app.listen(1337, "0.0.0.0", () => {
    console.log("Receiver application is started.");
});

For reference, this code was tested with node v10.16.0 and Express 4.17.1.

In This Article
  • Use Cases
  • Design Considerations
  • Customer Onboarding Page
  • Receiving Webhooks
  • Migrate from V1 to V2 Subscriptions
  • Example Node.js app

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

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