Skooh.io Logo
Back to blog
IntegrationDeveloper TutorialSeptember 24, 20257 min

Slack Events API to a Private Endpoint Using Skooh

Handle Slack's URL verification challenge and respond to events quickly using Skooh.

The Slack Challenge

Slack's Events API has two quirks that trip up many developers:

  1. URL Verification Challenge: When you add a webhook URL, Slack sends a challenge you must echo back
  2. 3-Second Timeout: Slack expects a response within 3 seconds or it retries

Skooh handles both gracefully.

Step 1: Set Up Skooh

  1. Create an endpoint in Skooh
  2. Copy your webhook URL and hook token

Step 2: Run the Skooh Proxy

Using the Desktop App:

  1. Download the Skooh Desktop App
  2. Enter your API key and hook token
  3. Set forwarding URL to your Slack bot (e.g., `http://localhost:3000/events\`)
  4. Click Connect

Using Docker:

docker run -d \\
  --name skooh-slack \\
  -e SKOOH_TOKEN=your_token \\
  -e SKOOH_API_KEY=your_api_key \\
  -e FORWARD_URL=http://slack-bot:3000/events \\
  --restart unless-stopped \\
  skooh/proxy

Step 3: Handle the URL Verification Challenge

Note: Slack's API evolves frequently. See Slack's Events API documentation for current setup instructions.

When you add your Skooh URL to a Slack app, Slack sends:

{
  "type": "url_verification",
  "token": "...",
  "challenge": "abc123xyz"
}

Your handler must respond with the challenge value:

app.post('/events', express.json(), (req, res) => {
  // Handle URL verification
  if (req.body.type === 'url_verification') {
    return res.json({ challenge: req.body.challenge });
  }
  
  // Handle other events...
  res.status(200).send('OK');
});

Step 3: Respond Fast, Process Async

Slack's 3-second timeout is strict. Use this pattern:

const { Queue } = require('bull');
const eventQueue = new Queue('slack-events');

app.post('/events', express.json(), async (req, res) => {
  // URL verification (must be immediate)
  if (req.body.type === 'url_verification') {
    return res.json({ challenge: req.body.challenge });
  }
  
  // Verify the request is from Slack
  if (!verifySlackRequest(req)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Queue for async processing
  await eventQueue.add({
    type: req.body.event.type,
    event: req.body.event,
    team_id: req.body.team_id
  });
  
  // Respond immediately (within 3 seconds!)
  res.status(200).send('OK');
});

// Process events asynchronously
eventQueue.process(async (job) => {
  const { type, event } = job.data;
  
  switch (type) {
    case 'message':
      await handleMessage(event);
      break;
    case 'app_mention':
      await handleMention(event);
      break;
    case 'reaction_added':
      await handleReaction(event);
      break;
  }
});

Verifying Slack Requests

Slack signs requests with a signature. Verify it:

const crypto = require('crypto');

function verifySlackRequest(req) {
  const timestamp = req.headers['x-slack-request-timestamp'];
  const signature = req.headers['x-slack-signature'];
  
  // Reject old timestamps (replay protection)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > 300) {
    return false;
  }
  
  const sigBaseString = \`v0:\${timestamp}:\${req.rawBody}\`;
  const mySignature = 'v0=' + crypto
    .createHmac('sha256', process.env.SLACK_SIGNING_SECRET)
    .update(sigBaseString)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(mySignature),
    Buffer.from(signature)
  );
}

Event Types to Handle

Common Slack events:

switch (event.type) {
  case 'message':
    // New message in channel
    break;
  case 'app_mention':
    // Bot was @mentioned
    break;
  case 'reaction_added':
    // Emoji reaction added
    break;
  case 'member_joined_channel':
    // User joined a channel
    break;
  case 'channel_created':
    // New channel created
    break;
}

Testing with Replay

  1. Trigger an event in Slack (send a message, add a reaction)
  2. Find the webhook in Skooh dashboard
  3. Click Replay to test your handler again
  4. Debug without spamming your Slack workspace

Troubleshooting

"URL verification failed"

  • Ensure your handler returns `{ challenge: req.body.challenge }`
  • Check the Skooh proxy is forwarding correctly
  • View the request in Skooh dashboard

"dispatch_failed" errors in Slack

  • Your handler is taking >3 seconds
  • Process events async and respond immediately
  • Check Skooh dashboard for timing info

Duplicate events

  • Slack retries if response is slow
  • Use `event_id` for deduplication:
const processedEvents = new Set();

if (processedEvents.has(req.body.event_id)) {
  return res.status(200).send('OK'); // Already processed
}
processedEvents.add(req.body.event_id);

Set up Slack events →

Written by Skooh Team

Share:

Ready to try Skooh?

Start receiving webhooks in your local environment in under 5 minutes.

Skooh | Slack Events API to a Private Endpoint Using Skooh