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

Authentication

Authentication

We use a standard OAuth2 flow to grant access tokens. We require a user to be present to authorize your apps' access to their resources via APIs, even if it is done only once during onboarding.

anchorPrerequisites

anchor
  • Webex account backed by Cisco Webex Common Identity (CI) with a Contact Center Administrator role assigned on Control Hub. For more information on how to assign an administrator, refer the Administration Guide (https://help.webex.com/en-us/article/n5jdj19/Webex-Contact-Center-Administrator-Roles-and-Privileges)
  • a client ID and client secret pair (request a pair)
  • a server running your application

anchorStep 1: Requesting Permission

anchor

To kick off the authorization flow, direct your users to visit the following URL along with a set of query parameters:

https://webexapis.com/v1/authorize

This endpoint can handle requests regardless of which datacenter the user belongs

The required query parameters are:

Query ParameterValue
response_typeMust be set to "code"
client_idClient ID issued to you
redirect_uriThis must match one of the URIs supplied during app registration
scopeUse a space-separated list of scopes that must match the scopes provided during app registration
stateA unique string that will be passed back to you upon completion

Please note that the URL parameters mentioned above, should be URL-encoded. An example to follow:

https://webexapis.com/v1/authorize?client_id=C0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef&response_type=code&redirect_uri=https://your-server.com/auth&scope=cjp:config_write cjp:config_read&state=set_state_here

When the user logs in successfully, they will be redirected back to the redirect_uri your app provided above

When redirected back to the redirect_uri, a code query parameter will be present like so:

http://your-server.com/auth?code=YjAzYzgyNDYtZTE3YS00OWZkLTg2YTgtNDc3Zjg4YzFiZDlkNTRlN2FhMjMtYzUz

anchorStep 2: Exchanging the Authorization Code for an Access Token

anchor

Your app should take the one-time short-lived code obtained in the previous step and exchange it for an access token. To get an access token, please make a request to https://webexapis.com/v1/access_token with the parameters as shown below:

curl --location --request POST 'https://webexapis.com/v1/access_token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=<client_id>' \
  --data-urlencode 'client_secret=<client_secret>' \
  --data-urlencode 'redirect_uri=<redirect_uri>' \
  --data-urlencode 'code=<code>'

The required parameters are:

ParameterValue
grant_typeThis should be set to "authorization_code"
client_idClient ID issued to you
client_secretClient Secret issued to you
codeThe authorization code from previous step
redirect_uriMust match the one used in previous step

The response will contain an access_token:

{
  "access_token": "OGY2MTZjNDUtOWNmNC00YTJhLTk0MzMtYzZhOGZiNzFmNjE3MTA4MDhiNmYtNDU5_P0A1_658ae496-d708-461f-b4bf-5f7ad38100ce5759",
  "expires_in": 43199,
  "refresh_token": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTEyMzQ1Njc4",
  "refresh_token_expires_in": 7776000,
  "token_type": "Bearer",
  "scope": "spark:kms cjp:user cjp:config_write cjp:config cjp:config_read spark:people_read"
}

The expires_in times are measured in seconds. Access tokens tend to expire after 8-12 hours, while refresh tokens tend to expire in days

Note: The access token expiry time is 14 days for integrations created in this developer portal, and the refresh token expiry is 90 days.

The access token string is of variable length and structure. Take care not to truncate or extract information from the string directly

The token values in the payload also contain helpful "hidden" data - the orgID that correlates to the organization the authorized user belongs to. The orgID value is a required parameter for many of the API endpoints, including the Users API. The only other method for obtaining an orgID is through the /people API with the spark: people _read scope and returns it in a Base64 encrypted format. However, the token string from the JSON payload already reveals the unencrypted orgID value. This makes it an ideal place to retrieve it from without having to perform another API call or further decryption.

The orgId is always found after the final "_" in a token string. To see how the encoded orgID can easily be separated from the token, check out the app-auth-sample in Webex CC Github repo, starting on line 147:

  let [accessToken, ciCluster, orgld] = loginDetails.access_token.split('_');

The final part of the token JSON payload provides the scopes associated with the token, that the granting user has permission to authorize. These scope values can be leveraged by an integration to conditionally present features, access data, or other actions dependent on permission levels.

anchorStep 3: Using the Access Token

anchor

You can use the access_token to make API calls by passing it as an HTTP header like so:

  # ...
  -H "Authorization: Bearer $token" \
  # ...

Note: if you receive an HTTP 429 (Too Many Requests) in response to an API request, it means you have exceeded the rate limit quota for your client ID. See Rate Limiting for more information.

anchorStep 4: Using the Refresh Token

anchor

After the access token expires, APIs will no longer accept requests with the token If your users stay logged in for a long time or your app needs to make background requests on behalf of a user, your app must use a refresh_token to get a new access_token before the current access_token expires

To get a new access_token, make a POST request to https://webexapis.com/v1/access_token, by setting grant_type to "refresh_token" as follows:

curl --location --request POST 'https://webexapis.com/v1/access_token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'client_id=<client_id>' \
  --data-urlencode 'client_secret=<client_secret>' \
  --data-urlencode 'refresh_token=<refresh_token>'

The required parameters are:

FieldValue
grant_typeThis should be set to "refresh_token"
client_idClient ID issued to you
client_secretClient Secret issued to you
refresh_tokenThe refresh token you received from the previous step

The response format will match the earlier format. The refresh_token in the response may also be renewed, make sure to use that in future requests.

anchorExample Node.js app

anchor

This app implements an OAuth2 flow using server-side session logic. First, a user is redirected to the login server upon clicking "login" (your usecase may warrant redirecting immediately instead of presenting a button). Then, when a code query param is provided, the app exchanges the code for an access token.

It has a route at /home which requires the user to login to view. Unauthenticated user sessions are redirected back to the root page for login.

Since /v1/access_token can return 429 with a Retry-After header, this app also implements client-side retries with exponential back-off. The same logic should be reused for any other API call, see our Ratelimiting Guide.

This app requires the following environment variables set in the config/.env file:

Environment VariableDescription
CLIENT_IDRequired. Should have http://localhost:4242 in its redirect_uri list and as well as the cjp:config_read and cjp:config_write scopes.
CLIENT_SECRETRequired. Client secret paired with CLIENT_ID.
require("dotenv").config({ path: "config/.env" });

const express = require("express");
const session = require("express-session");

const axios = require("axios");
const axiosRetry = require("axios-retry");

// https://github.com/softonic/axios-retry/issues/72#issuecomment-699785860
// Honor Retry-After header
axiosRetry(axios, {
  retries: 3,
  retryCondition(e) {
    return (
      axiosRetry.isNetworkOrIdempotentRequestError(e) ||
      e.response.status === 429
    );
  },
  retryDelay: (retryCount, error) => {
    if (error.response) {
      return error.response.headers["retry-after"];
    }
    return axiosRetry.exponentialDelay(retryCount, error);
  },
});

const AUTH_AUTHORIZE_URL =
  process.env.AUTH_AUTHORIZE_URL || `https://webexapis.com/v1/authorize`;
const AUTH_ACCESS_TOKEN_URL =
  process.env.AUTH_ACCESS_TOKEN_URL || `https://webexapis.com/v1/access_token`;

// client id/secret pair generated in Developer Portal
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;

// URL users will visit after successfully authenticating
const REDIRECT_URI = process.env.REDIRECT_URI || "http://localhost:4242";

// permissions users must grant your app, separated by spaces
const SCOPE = process.env.SCOPE || "cjp:config_read cjp:config_write";

const app = express();

// TODO(you): replace in-memory session store
app.use(
  session({
    secret: process.env.SESSION_SECRET || "development",
    resave: false,
    saveUninitialized: false,
  })
);

//
// OAuth2 landing/login page
//

// pre-login landing/redirect_uri page
app.get("/", async (req, res) => {
  // case 1: authentic session (session has token) => redirect to /home
  // case 2: new session (no token, no `code`) => serve landing page
  // case 3: authenticating new session (no token, has `code`) => exchange code for token, then follow case 1

  const sessionToken = await getSessionToken(req);

  // case 2
  if (!sessionToken && !("code" in req.query)) {
    res.send(buildLoginPage());
    return;
  }

  // case 3
  if (!sessionToken) {
    try {
      const token = await exchangeCodeForToken(req.query.code);
      await setSessionToken(req, token);
    } catch (e) {
      console.error(e);
      res
        .status(503)
        .send(
          "Unable to contact authentication server, please try again later."
        );
    }
  }

  // case 1
  res.redirect("/home");
});

function buildLoginPage() {
  return `<html>
        <head>
            <title>My Integration</title>
        </head>
        <body style="padding: 20%">
            <h1>Welcome!</h1>
            <a href="${buildLoginUrl("/")}">Login</a>
        </body>
        </html>
    `;
}

// creates /v1/authorize URL to redirect to with optional `state=` param.
function buildLoginUrl(state) {
  const baseUrlRedirectEncoded = encodeURI(AUTH_AUTHORIZE_URL);
  const clientIdEncoded = encodeURIComponent(CLIENT_ID);
  const redirectUriEncoded = encodeURIComponent(REDIRECT_URI);
  const scopeEncoded = encodeURIComponent(SCOPE);

  // optional: include something useful in state, like page to redirect to
  const stateEncoded = encodeURIComponent(state);

  return `${baseUrlRedirectEncoded}?client_id=${clientIdEncoded}&response_type=code&redirect_uri=${redirectUriEncoded}&scope=${scopeEncoded}&state=${stateEncoded}`;
}

async function exchangeCodeForToken(code) {
  const params = new URLSearchParams();
  params.append("grant_type", "authorization_code");
  params.append("client_id", CLIENT_ID);
  params.append("client_secret", CLIENT_SECRET);
  params.append("code", code);
  params.append("redirect_uri", REDIRECT_URI);

  const response = await axios.post(AUTH_ACCESS_TOKEN_URL, params, {
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
  });
  const token = response.data;

  // Translate `expires_in` duration to approximate timestamps ASAP
  const now = Date.now();
  token.expiresAt = now + token.expires_in * 1000; // usually a few hours
  token.refreshExpiresAt = now + token.refresh_token_expires_in * 1000; // usually a few days

  return token;
}

async function getSessionToken(req) {
  // `async` just in case your code writes to a shared cache like redis
  return req.session.token;
}

async function setSessionToken(req, token) {
  // `async` just in case your code writes to a shared cache like redis
  req.session.token = token;
}

// middleware to guarantee route will have session
async function requireAuthenticationMiddleware(req, res, next) {
  const sessionToken = await getSessionToken(req);
  if (!sessionToken) {
    res.redirect("/");
    return;
  }
  return next();
}

//
// Example protected route
//

app.use("/home", requireAuthenticationMiddleware, async (req, res) => {
  const { expiresAt } = await getSessionToken(req);

  res.send(`<html>
        <head>
            <title>My Integration</title>
        </head>
        <body style="padding: 20%">
            <h1>Welcome!</h1>
            Your token will expire at ${new Date(expiresAt).toISOString()}.
        </body>
        </html>
    `);
});

const port = 4242;
app.listen(port, "0.0.0.0", () => {
  console.log(`Running on port ${port}`);
});

For reference, this script is tested with the following dependencies in node 12.18.3:

{
  "axios": "0.24.0",
  "axios-retry": "3.2.4",
  "dotenv": "10.0.0",
  "express": "4.17.1",
  "express-session": "1.17.2"
}
In This Article
  • Prerequisites
  • Step 1: Requesting Permission
  • Step 2: Exchanging the Authorization Code for an Access Token
  • Step 3: Using the Access Token
  • Step 4: Using the Refresh Token
  • 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.