Сценарий · Slack

Обработка вебхуков и slash-команд Slack на serverless

Slack требует ответа на slash-команду за 3 секунды, иначе помечает приложение как медленное. Соберите serverless-обработчик, который проверяет HMAC, отвечает сразу и отправляет настоящий ответ через response_url из асинхронного пайплайна — медленная работа никогда не соревнуется с окном ответа.

Обновлено: 2026-06-28

Суть ответа

Обработка вебхуков и slash-команд Slack на serverless. Обработчик Slack проверяет HMAC-подпись, извлекает параметры slash-команды, запускает асинхронный пайплайн и возвращает HTTP 200 с пустым телом за миллисекунды.

Когда подходит

  • Slash-команды, которые запускают инференс LLM, запросы к базе или вызовы внешних API
  • Подписки на события, которые передают данные в пайплайны или запускают уведомления

На что обратить внимание

  • Любой вызов внешнего API внутри синхронного обработчика Slack рискует выйти за 3-секундное окно. Инференс LLM, запросы к базе с join и вызовы сторонних API регулярно занимают 2–10 секунд.
  • Без асинхронной передачи пользователи видят «This app took too long to respond», и команда выглядит упавшей, даже если в итоге выполнилась.

Трёхсекундное окно Slack

  • Slash-команды: Slack требует HTTP 200 за 3 секунды, иначе помечает приложение как не отвечающее
  • Подписки на события: подтверждение за 3 секунды, обработка может продолжаться
  • Интерактивные компоненты (кнопки, модальные окна): то же 3-секундное окно

Большинство операций Slack — вызовы LLM, запросы к базе, обращения к внешним API — занимают дольше 3 секунд. Стандартный паттерн: подтвердить сразу, выполнить работу асинхронно и отправить результат через response_url или Slack Web API.

Обработка внутри запроса почти всегда слишком медленная

Любой вызов внешнего API внутри синхронного обработчика Slack рискует выйти за 3-секундное окно. Инференс LLM, запросы к базе с join и вызовы сторонних API регулярно занимают 2–10 секунд.

Без асинхронной передачи пользователи видят «This app took too long to respond», и команда выглядит упавшей, даже если в итоге выполнилась.

Быстро проверить, сразу ответить, работать асинхронно

Обработчик Slack проверяет HMAC-подпись, извлекает параметры slash-команды, запускает асинхронный пайплайн и возвращает HTTP 200 с пустым телом за миллисекунды.

Шаг пайплайна делает настоящую работу — вызывает LLM, запрашивает базу, забирает внешние данные — и отправляет результат в Slack через response_url или chat.postMessage.

Что нужно для обработки вебхуков Slack

Проверка запроса HMAC-SHA256

Slack подписывает запросы HMAC-SHA256 по timestamp + сырое тело. Проверяйте подпись до обработки любой команды.

Быстрый ACK за 3 секунды

Возвращайте 200 сразу после проверки. Никогда не ждите медленную работу до ответа.

Асинхронный ответ через response_url

Отправляйте настоящий результат в response_url из асинхронного шага пайплайна. Поддерживаются ответы ephemeral и in_channel.

Маршрутизация slash-команд

Направляйте разные slash-команды в разные задачи пайплайна по полю command.

Дедупликация повторных доставок

Slack может повторить slash-команду или доставку Events API. Используйте trigger_id или event_id как ключ идемпотентности до запуска пайплайна.

Поток обработки slash-команды Slack

1

Проверить HMAC, разобрать команду

Проверьте x-slack-signature через HMAC-SHA256. Извлеките command, text, user_id, response_url из URL-encoded тела.

2

Запустить асинхронную оркестрацию, вернуть 200

Вызовите global.durable.startNew с параметрами команды, включая response_url. Сразу верните пустой ответ 200.

3

Отправить результат через response_url

Шаг пайплайна делает настоящую работу, затем отправляет POST с ответом Block Kit в response_url.

Обработчик slash-команды Slack

Проверить, подтвердить, запустить асинхронный пайплайн — всё в 3-секундном окне. Шаг пайплайна делает работу и отвечает через response_url.

webhooks/slack.mjs (обработчик быстрого ACK)
import { createHmac, timingSafeEqual } from 'node:crypto';

export async function handler(event) {
  const body = event.body ?? '';
  const timestamp = event.headers['x-slack-request-timestamp'] ?? '';
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return { statusCode: 400, body: 'stale request' };
  }
  const sigBase = `v0:${timestamp}:${body}`;
  const expected = 'v0=' + createHmac('sha256', process.env.SLACK_SIGNING_SECRET).update(sigBase).digest('hex');
  const received = event.headers['x-slack-signature'] ?? '';
  if (expected.length !== received.length ||
      !timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
    return { statusCode: 401, body: 'invalid signature' };
  }
  const params = Object.fromEntries(new URLSearchParams(body));
  await global.durable.startNew('slack-command', undefined, {
    command: params.command,
    text: params.text,
    userId: params.user_id,
    channelId: params.channel_id,
    responseUrl: params.response_url,
  });
  return { statusCode: 200, body: '' };
}
jobs/slack-command.mjs (асинхронная работа + ответ)
export async function handler(event) {
  const { command, text, userId, responseUrl } = event.payload ?? {};
  const result = await processCommand(command, text, userId);
  await fetch(responseUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      response_type: 'in_channel',
      blocks: [{ type: 'section', text: { type: 'mrkdwn', text: result } }],
    }),
  });
  return { command, userId, result };
}

Используйте для автоматизации Slack

Когда это уместно

  • Slash-команды, которые запускают инференс LLM, запросы к базе или вызовы внешних API
  • Подписки на события, которые передают данные в пайплайны или запускают уведомления

Когда лучше выбрать другое

  • Простые входящие вебхуки Slack, которые только отправляют сообщения, — им проверка HMAC не нужна

Частые вопросы

А подписки на события Slack?

При проверке URL ответьте значением challenge за 3 секунды. Продакшен-события подтверждайте и передавайте в асинхронные пайплайны точно так же, как slash-команды.

Как обрабатывать отправку модальных окон Slack?

Отправка модальных окон (view_submission) работает так же: проверить HMAC, сразу вернуть 200, обработать асинхронно в пайплайне.

Что если Slack повторит ту же slash-команду?

Используйте trigger_id (slash-команды) или event_id (Events API) как ключ идемпотентности — делайте upsert до global.durable.startNew, чтобы повторная доставка вернула 200 без второго запуска пайплайна.