Slack Huddle Bots Integration Guide

This guide walks you through integrating a Slack bot and app with Recall.ai to capture Slack Huddle data. You’ll set up a bot, create a Slack app, configure webhooks, and activate it to join a Slack workspace and listen to huddles in both public and private channels.

Pre-requisites

  • Slack Workspace: A workspace where you have permission to invite participants
  • Domain: A custom domain or subdomain which will be added to Recall for us to read incoming emails to your Slack bot’s email (e.g., [email protected])

Overview - How It Works

This flow can be broken down into 5 parts:

  1. Your customer invites your Slack bot to their workspace using a dedicated bot email
  2. Recall receives the slack email invite and sends a webhook to your endpoint notifying you of the invite
  3. You activate the bot which gets it to joins your customer's Slack workspace
  4. Users in their workspace authorize your Slack app
  5. Bot watches for huddles and joins automatically/by invite

Integration Steps: Part 1 (Slack Bot)

Step 1: Configure your webhook in your app

Add a webhook to your app to listen for events related to our new Slack integration. You can either:

  • Create a new webhook endpoint specifically for these events, or
  • Modify an existing webhook by adding the code snippet below.

Since all webhook events are sent to the endpoints configured in the Recall.ai dashboard, your webhook will automatically receive relevant Slack integration events

The Slack Team Webhook Event will have the following schema

type SlackTeamWebhookEventType =
    "slack_team.invited" | // A customer has invited your Slack bot to their workspace
    "slack_team.active" | // Your Slack bot is active and is ready to join huddles
    "slack_team.access_revoked"; // A customer has removed your Slack bot from their Slack workspace

type SlackTeamWebhookEventSubCode =
    "invited" |
    "active" |
    "access_revoked";

interface SlackTeamEvent {
    event: SlackTeamWebhookEventType;
    data: {
        data: {
            code: SlackTeamWebhookEventSubCode;
            sub_code: string | null;
            updated_at: string;
        };
        slack_team: {
            id: string;
            metadata: object;
        };
    };
}

Next is to handle each event type that could be sent:

  • slack_team.invited - A customer has invited your Slack bot to their workspace. You can activate your Slack team integration by making a PATCH request to the Update Slack Team api. Note that you should set the bot name, otherwise the bot will join the slack workspace as None
  • slack_team.active - Your Slack bot is active and is ready to join huddles. No action required
  • slack_team.access_revoked - A customer has removed your Slack bot from their Slack workspace. You can delete their Slack team integration by calling the Delete Slack Team api

Below is a sample of how your webhook should handle slack-related webhook events:

/**
 * Handles Slack webhook events.
 * @param slackEvent - The Slack event payload.
 */
const handleRecallSlackWebhookEvent = async (slackEvent: SlackTeamEvent) => {
    const { event, data } = slackEvent;
    const slackTeamId = data.slack_team.id;

    const recallApiKey = process.env.RECALL_API_KEY;
    const recallDomain = process.env.RECALL_DOMAIN;

    switch (event) {
        case "slack_team.invited": {
            const response = await fetch(`${recallDomain}/api/v2/slack-teams/${slackTeamId}`, {
                method: 'PATCH',
                headers: {
                    'Authorization': `${recallApiKey}`,
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    bot_name: 'Slack Bot',
                }),
            });
            if (!response.ok) {
                throw new Error(`Failed to activate Slack team ${slackTeamId}: ${await response.text()}`);
            }
            console.info(`Slack team ${slackTeamId} activated`);
            break;
        }
        case "slack_team.active": {
            console.info(`Slack team ${slackTeamId} is active`);
            break;
        }
        case "slack_team.access_revoked": {
            console.info(`Slack team ${slackTeamId} has had their access revoked`);
            break;
        }
        default: {
            console.info(`Received ${event} event for team ${slackTeamId}`);
            break;
        }
    }
};

Step 2: Register Your Webhook in the Dashboard

Regardless of whether you’re using an existing webhook or a new one, make sure the webhook endpoint URL is registered in the Recall dashboard. This ensures that Slack-related events are properly routed to your webhook

To do this, you can:

  • Go to the Recall.ai Dashboard -> Webhooks
  • Add your webhook URL endpoint (ex. https://example.com/api/webhooks/recallai)

📘

Tip for local development

When testing locally, use a local development tunnel like Ngrok to expose your local server for testing. Then add the ngrok link to the Recall webhooks dashboard

Step 3: Give Recall access to manage emails sent to the slackbot's email domain

Recall.ai needs permission to read emails sent to your bot's domain so that we can receive the Slack workspace invite emails. This requires setting up DNS records for your domain.

❗️

Do not configure DNS records on your main domain

Adding the DNS records we provide to you will interfere with your ability to receive emails if you use your main domain. You should either use a different domain, or a dedicated subdomain, e.g. bot.yourdomain.com.

Set Up a Custom Email for Your Slack Bot

  1. Add Your Bot's Domain in the Recall.ai Dashboard

    • Navigate to the Slack bot setup in the Recall dashboard and enter your domain (e.g., yourdomain.com)
    • This allows Recall to generate the necessary DNS records for your domain (~10 minutes to generate the records)

  2. Retrieve the Generated DNS Records

    • Once the domain is added, Recall.ai will generate several DNS records (MX, TXT, etc.)
  3. Add the DNS Records to Your DNS Provider

    • Copy the generated records and add them to your DNS provider (e.g., Cloudflare, GoDaddy).
  4. Wait for DNS Verification

    • Once the records are added, Recall.ai will attempt to verify them (~1 hour for the records to propagate, set a low TTL)

Once verification is complete, Recall will be able to receive emails sent to your Slack bot's domain

Step 4: Invite the Email to Your Slack Workspace

Now that your email inbox is set up, you need to invite it to your Slack workspace:

  1. Send an Invite to the Bot Email
    • In your Slack workspace, invite [email protected] as a new member
    • Note that because we're managing your email server for the added domain, you can choose any email username for your bot (i.e. [email protected])
  2. For SSO-Enabled Workspaces
    • If your Slack workspace uses Single Sign-On (SSO), invite the bot as a guest user with limited channel access to every channel you want it to join
    • This ensures the bot can join without requiring full SSO authentication. Once invited and the bot has joined, the bot will be able to participate in Slack huddles

What Happens Next?

Now that you’ve invited the bot to your Slack workspace, here’s what to expect:

  • Bot Activation (~15 Minutes)
    • The bot will take up to 15 minutes to join the workspace and come online
    • You’ll know it’s ready when you see the green online status on its profile picture
  • Inviting the Bot to Huddles
    • Once the bot is online, you can invite it to join Slack Huddles just like any other member
    • At this point, your Slack bot is fully integrated and ready to handle events within your workspace! 🚀

How to Get Slack Huddle Bot Data

Once the bot is online and joins Slack Huddles, Recall.ai will automatically send webhook events containing meeting data just like it does for other meeting platforms.

When you receive a bot's status change event, query the bot using the Retrieve Bot api

Once you have the bot, you can check the meeting_metadata field on the bot which should look something like this:

data = {
  // ... other bot-related fields
  meeting_metadata: {
    slack_channel_id: 'slack_team_id',
    slack_huddle_id: 'slack_huddle_id'
  },
  slack_team: {
    id: 'slack_team_id'
  }
}

At this stage, your integration is fully functional, and your bot can collect meeting data from Slack Huddles seamlessly! 🎯

Re-inviting the bot

This flow is triggered when:

  • A bot ([email protected]) has been invited and joined a Slack workspace
  • The SlackTeam associated with the above email and customer's Slack workspace has been deleted. Whether the bot is still in the Slack workspace or not is unrelated to this step
  • You try to add the slack bot with the same email back to the same Slack workspace

To re-invite the bot to a slack workspace:

  1. Ensure that the slack bot in the workspace has been completely removed from the Slack workspace. This will reset the bot's status in Slack
    1. Note that deleting the Slack Team doesn't make the bot leave the Slack workspace automatically. This step needs to be done manually by an admin in the Slack workspace
  2. Invite the bot via email through the normal flow

To remove a user (the bot) from your slack workspace:

NOTE: Only the user with the "Primary Workspace Owner" role can perform this action

  1. First head to your Slack workspace's admin dashboard
  2. Head to "Manage Members"
  3. Deactivate account for that user

  1. Delete the profile for that user

Integration Steps: Part 2 (Slack App)

Part 2 covers integrating a Slack app with Recall.ai. While this step is optional, we highly recommend completing it to unlock the full range of features (like asking to join private huddles) and ensure your integration can access all available Slack huddle data. Setting up a Slack app allows for deeper workspace integration and maximizes the value you get from Recall.ai.

📘

The slack bot will work without completing this step but you'll miss out on important capabilities (e.g. full access to private channel huddles). To ensure your users get the best possible coverage and data, we strongly encourage completing the Slack app integration

If you already have a Slack app set up for your product, there’s no need to start from scratch—you can simply integrate your existing Slack app with Recall.ai to take advantage of all these features (jump to step 2).

Step 1: Create your Slack App

  1. Navigate to https://api.slack.com/apps?new_app=1 and select “From scratch”
  2. Enter your Slack application’s name. This should reflect your brand as it will be visible to all end-users.
  3. Select a workspace to develop it in. This will just be for testing–you’ll be able to add it to all your end-users’ workspaces.
  4. Click "Create App"

📘

Choose an appropriate workspace to develop your app in

Slack Apps are permanently tied to a home workspace. You should choose your company's Slack workspace rather than create a new workspace just for this app.

Step 2: Update Slack App's Redirect URL

Next up: get the user to authorize the Slack app via Slack's OAuth

When a user tries to connect your Slack app, they’ll be sent to Slack’s authorization page, where they’ll review and approve the permissions your app is requesting. After the user grants access, Slack will redirect them back to the redirect URL you specified in your app settings. This redirect includes a special authorization code in the URL

Your application should be set up to receive this code and immediately exchange it with Slack for an access token. This access token is what Recall will store and use to interact with Slack on behalf of the user to discover channels and automatically listen for huddles happening within them.

  1. Navigate to “OAuth & Permissions”
  2. In “Redirect URLs”, add your product’s redirect URL to process the Slack authorization grant (this should not include recall's domain)
  3. Click "Save URLs"
  1. Add appropriate logic to your product to handle the Slack OAuth flow
const SLACK_CLIENT_ID = process.env.SLACK_CLIENT_ID!;
const SLACK_CLIENT_SECRET = process.env.SLACK_CLIENT_SECRET!;
const SLACK_REDIRECT_URI = process.env.SLACK_REDIRECT_URI!;
const RECALL_API_KEY = process.env.RECALL_API_KEY!;

// This endpoint is the redirect url you set in your Slack App's settings
app.get('/slack/oauth/callback', async (req, res) => {
  const code = req.query.code as string;
	const state = req.query.state as string;
  const error = req.query.error as string | undefined;

  if (error) {
    return res.status(400).send(`Slack OAuth error: ${error}`);
  }

  if (!code) {
    return res.status(400).send('Missing code parameter from Slack');
  }
  
  if (!checkState(state)) {
    return res.status(400).send('Invalid state parameter');
	}
  
  let accessToken;

  try {
    // Exchange the code for an access token
    const response = await fetch('https://slack.com/api/oauth.v2.access', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        code,
        client_id: SLACK_CLIENT_ID,
        client_secret: SLACK_CLIENT_SECRET,
        redirect_uri: SLACK_REDIRECT_URI,
      }),
    });

    const data = await response.json();

    if (!data.ok) {
      return res.status(400).send(`Slack OAuth failed: ${data.error}`);
    }

    // you now have access_token, team info, authed user, etc.
    // you can store these securely in your database for future use
    // example: data.access_token, data.team, data.authed_user 
		accessToken = data.access_token;
  } catch (err) {
    res.status(500).send('Internal server error');
  }
  
  try {
    const response = await fetch(`https://api.recall.ai/api/v2/slack-teams/{slack_team_integration_id}/oauth-tokens/`, {
      method: 'POST',
      headers: {
        'Authorization': RECALL_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        token: accessToken,
      }),
    });

    const data = await response.json();

    return res.status(200);
  } catch (err) {
    res.status(500).send('Internal server error');
  }
});

📘

Your integration can only monitor private huddles in channels where at least one member has authorized the app.

xThis means it may not knock on every private huddle across the workspace. It will only knock on the private channels where it has visibility via an authorized user.

Adoption is still beneficial because the more users who install the app, the more private channels it can cover. Full workspace adoption isn’t required for private channel support. Huddles in public channels can always be discovered and detected.

Step 3: Update Slack App's permissions

  1. Navigate to “OAuth & Permissions”
  2. Click "Save URLs"
  3. Under “User Token Scopes” in “Scopes”, click “Add an OAuth Scope” and add the following: channels:read, groups:read, im:read, mpim:read, team:read and users:read

🚧

You may need to go through Slack app review if listed on Slack Marketplace

If your Slack app is already listed on the Slack Marketplace, adding new scopes requires an app review. This process can take up to 8 weeks and typically involves regular communication with Slack’s review team. Please note that Slack’s approval process is thorough and has strict requirements, so approval isn’t guaranteed even with provided justifications. However, if your app already uses some or all of the required scopes listed above, no further action or additional justifications are needed, provided your existing setup meets Slack’s standards.

If your app already has these scopes, you’re good to go and can fully utilize our integration. If not, you can still benefit from partial integration functionality as long as you have at least one relevant permission. For example, having channels:read will enable the integration to detect huddles in private channels.

If your app is not listed on the Slack Marketplace, you are not required to go through app review and can still distribute your app.

📘

Note about changing scopes in distributed Slack applications

Modifying Slack application scopes requires your end-users to re-authorize your application to grant updated permissions. This process may not be simple for all use cases, so we encourage you to think about what is most appropriate for your product's user base

You may choose to only work with private channels for existing end-users, but grant extended functionality to private group and direct message channels for new users who authorize your Slack application

Step 4: Onboard Users

  1. In your Slack application's dashboard, navigate to "Manage Distribution"
  2. Under "Share Your App with Other Workspaces", expand all collapsible sections and follow the steps to prepare your application for public unlisted distribution
  3. Click "Activate Public Distribution"
  1. Navigate to "Basic Information" and take note of the following values: Client ID and Client Secret
  2. Navigate to "OAuth & Permissions" and take note of all the scopes listed
  3. Programmatically generate your OAuth URL to allow users to authorize your application into their workspace, separating your scopes with a , character and properly URL escaping them
    https://slack.com/oauth/v2/authorize?scope={{YOUR APPLICATION BOT SCOPES}}&user_scope={{YOUR APPLICATION USER SCOPES}}&redirect_uri={{YOUR REDIRECT URI}}&client_id={{YOUR CLIENT ID}}&state={{CRYPTOGRAPHICALLY SECURE RANDOM IDENTIFIER}}
    
  4. Give this URL to your end-users, such as through a button in your product

📘

Ensure state is unique and securely verifiable

The state query param should be encrypted (e.g. JWT) for you to verify in the callback. This guarantees that your app was the one that generated the request and it can't be replayed

import crypto from 'crypto';

const SLACK_CLIENT_ID = process.env.SLACK_CLIENT_ID!;
const SLACK_REDIRECT_URI = process.env.SLACK_REDIRECT_URI!;
const SLACK_SCOPES = [
  'channels:read',
  'groups:read',
  'im:read',
  'mpim:read',
  'team:read',
  'users:read'
].join(',');

// generates a cryptographically secure random string for the state parameter
function generateSecureState(length = 32): string {
  return crypto.randomBytes(length).toString('hex');
}

// generates the Slack OAuth URL and saves the state
export async function generateSlackOAuthUrl(userId: string): Promise<string> {
  const state = generateSecureState();
  await saveOAuthStateToDB(state, userId);

  const params = new URLSearchParams({
    scope: SLACK_SCOPES,
    redirect_uri: SLACK_REDIRECT_URI,
    client_id: SLACK_CLIENT_ID,
    state
  });

  return `https://slack.com/oauth/v2/authorize?${params.toString()}`;
}

app.get('/auth/slack', async (req, res) => {
  const userId = req.query.user_id as string || 'user-123';

  try {
    const oauthUrl = await generateSlackOAuthUrl(userId);
    return res.redirect(oauthUrl);
  } catch (err) {
    console.error('Failed to generate Slack OAuth URL:', err);
    return res.status(500).send('Internal server error');
  }
});

What Happens Next?

After completing the Slack App integration steps, your application will be ready to interact with Slack workspaces and monitor private huddles. Here’s what you can expect:

  • User Authorization: As users authorize your Slack app using the OAuth flow, your backend will securely receive, store, and forward access tokens. These tokens enable Recall.ai to access necessary Slack resources on behalf of each user or workspace.

  • Channel and Huddle Discovery: With the required permissions in place, Recall.ai will be able to discover channels (including private channels where the app has been authorized) and automatically detect active huddles.

  • Webhook Event Delivery: When a huddle starts or relevant events occur, Recall.ai will send webhook notifications to your registered endpoint. These events will contain meeting metadata and other important information, allowing your application to process or store the data as needed.

  • Private Channel Coverage: Your integration will monitor private huddles only in channels where at least one user has authorized the app. The more users who install your app, the broader your coverage across private channels.

At this point, your Slack app is fully integrated with Recall.ai and ready to capture and process huddle data from across your users’ Slack workspaces!