# API and webhook tokens

<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

To migrate from Slack to Zoom, create your app, get your developer credentials, and request your chatbot token.

## Get credentials

Before you call Zoom chatbot endpoints and use webhooks, you need to [get your Client ID and Client Secret, Bot JID, and webhook secret token](/docs/chat/create/).

## Add app

After getting your credentials, you need to [add your chatbot app](/docs/chat/installation-and-authentication/#add-app).

## Request chatbot token

Now that we have our developer credentials and have added our app, we can request a Chatbot token. Unlike Slack, which gives you the chatbot token in the developer portal, Zoom requires you to request a chatbot token via the `client_credentials` OAuth grant type.

Request URL:

```plaintext
POST: https://zoom.us/oauth/token?grant_type=client_credentials
```

Request Headers:

```json
{ "Authorization": "Basic base64Encoded(ZOOM_CLIENT_ID:ZOOM_CLIENT_SECRET)" }
```

If successful, the response body is a JSON representation of the Chatbot token:

```json
{
    "access_token": "<JWT_TOKEN>",
    "token_type": "bearer",
    "expires_in": 3600,
    "scope": "imchat:bot",
    "api_url": "https://api.zoom.us"
}
```

Chatbot tokens expire after one hour. After your chatbot token expires, you can simply [request a new one](#request-chatbot-token), there is no separate refresh flow required.

## OAuth token

To call APIs endpoints other than the Chatbot ones, you can request an [account managed](/docs/internal-apps/s2s-oauth/) or [admin/user managed](/docs/integrations/oauth/) OAuth `access_token` with your Chatbot's same client ID and client secret.

## Webhook verification

[Verifying the webhook request comes from Zoom](/docs/api/webhooks/#verify-webhook-events) is more simple than Slack since Zoom uses `content-type: application/json` for webhook requests where Slack uses both `content-type: application/x-www-form-urlencoded` and `content-type: application/json`. Notice for Slack you have to use the rawBody request body. Below is an example in Node.js.

```javascript
import crypto from "crypto";

const message = `v0:${req.headers["x-slack-request-timestamp"]}:${req.rawBody}`;
const hashForVerify = crypto
    .createHmac("sha256", process.env.SLACK_WEBHOOK_SECRET)
    .update(message)
    .digest("hex");
const signature = `v0=${hashForVerify}`;

// you verifying the request came from Slack
if (req.headers["x-slack-signature"] === signature) {
    // Authorized request, continue to challenge or handling all webhook events
} else {
    // Unauthorized request
}
```

```javascript
import crypto from "crypto";

const message = `v0:${req.headers["x-zm-request-timestamp"]}:${JSON.stringify(req.body)}`;
const hashForVerify = crypto
    .createHmac("sha256", process.env.ZOOM_WEBHOOK_SECRET_TOKEN)
    .update(message)
    .digest("hex");
const signature = `v0=${hashForVerify}`;

// you verifying the request came from Zoom
if (req.headers["x-zm-signature"] === signature) {
    // Authorized request, continue to challenge or handling all webhook events
} else {
    // Unauthorized request
}
```

## Webhook validation

[Zoom validating you control the webhook URL](/docs/api/webhooks/#validate-your-webhook-endpoint) is also more simple than Slack since Zoom uses `content-type: application/json` for webhook requests where Slack uses both `content-type: application/x-www-form-urlencoded` and `content-type: application/json`. Notice for Slack you now use the JSON request body. Below is an example in Node.js.

Slack validation header:

```json
{
    "host": "hardy-moved-mako.ngrok-free.app",
    "user-agent": "Slackbot 1.0 (+https://api.slack.com/robots)",
    "content-length": "129",
    "accept": "*/*",
    "accept-encoding": "gzip,deflate",
    "content-type": "application/json",
    "x-forwarded-for": "54.209.23.241",
    "x-forwarded-host": "hardy-moved-mako.ngrok-free.app",
    "x-forwarded-proto": "https",
    "x-slack-request-timestamp": "1739921314",
    "x-slack-signature": "v0=WF9TTEFDS19TSUdOQVRVUkU"
}
```

Slack validation body:

```json
{
    "token": "U0xBQ0tfV0VCSE9PS19UT0tFTg",
    "challenge": "U0xBQ0tfV0VCSE9PS19DSEFMTEVOR0U",
    "type": "url_verification"
}
```

```javascript
if (req.body.type === "url_verification") {
    // Challenge webhook event type

    response = {
        challenge: req.body.challenge,
        status: 200,
    };

    res.status(response.status);
    res.json(response.challenge);
} else {
    response = { message: "Authorized request.", status: 200 };

    res.status(response.status);
    res.json(response);

    // All other webhook event types
}
```

Zoom validation header:

```json
{
    "host": "hardy-moved-mako.ngrok-free.app",
    "user-agent": "Zoom Marketplace/1.0a",
    "content-length": "110",
    "authorization": "Wk9PTV9BVVRIT1JJWkFUSU9O",
    "content-type": "application/json; charset=utf-8",
    "traceparent": "Wk9PTV9UUkFDRVBBUkVOVA",
    "x-forwarded-for": "134.224.12.54",
    "x-forwarded-host": "hardy-moved-mako.ngrok-free.app",
    "x-forwarded-proto": "https",
    "x-zm-request-timestamp": "1739923528",
    "x-zm-signature": "v0=WF9aT09NX1NJR05BVFVSRQ",
    "accept-encoding": "gzip"
}
```

Zoom validation body:

```json
{
    "payload": {
        "plainToken": "Wk9PTV9QTEFJTl9UT0tFTg"
    },
    "event_ts": 1654503849680,
    "event": "endpoint.url_validation"
}
```

```javascript
if (req.body.event === "endpoint.url_validation") {
    // Challenge webhook event type

    const hashForValidate = crypto
        .createHmac("sha256", process.env.ZOOM_WEBHOOK_SECRET_TOKEN)
        .update(req.body.payload.plainToken)
        .digest("hex");

    response = {
        message: {
            plainToken: req.body.payload.plainToken,
            encryptedToken: hashForValidate,
        },
        status: 200,
    };

    res.status(response.status);
    res.json(response.message);
} else {
    response = { message: "Authorized request.", status: 200 };

    res.status(response.status);
    res.json(response);

    // All other webhook event types
}
```
