Обработка вебхуков и 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», и команда выглядит упавшей, даже если в итоге выполнилась.
Как помогает Inquir
Быстро проверить, сразу ответить, работать асинхронно
Обработчик 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
Проверить HMAC, разобрать команду
Проверьте x-slack-signature через HMAC-SHA256. Извлеките command, text, user_id, response_url из URL-encoded тела.
Запустить асинхронную оркестрацию, вернуть 200
Вызовите global.durable.startNew с параметрами команды, включая response_url. Сразу верните пустой ответ 200.
Отправить результат через response_url
Шаг пайплайна делает настоящую работу, затем отправляет POST с ответом Block Kit в response_url.
Пример кода
Обработчик slash-команды Slack
Проверить, подтвердить, запустить асинхронный пайплайн — всё в 3-секундном окне. Шаг пайплайна делает работу и отвечает через response_url.
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: '' }; }
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 без второго запуска пайплайна.