Исходящие вебхуки

Исходящие вебхуки позволяют вам получать информацию о событиях в вашем пространстве в реальном времени. Это могут быть новые сообщения, реакции, чаты, участники и тд.

Вебхук представляет собой JSON объект, отправляемый в момент наступления события на указанный в настройках бота URL, содержащий небольшое количество информации, которой достаточно для написания автоматизаций. Если требуется получить больше информации об объектах, указанных в JSON - вы можете воспользоваться API и получить полную информацию.

Каждый исходящий вебхук защищён с помощью подписи, основанной на хешировании содержимого. Подробнее об этом, а также о других методах проверки подлинности исходящего вебхука, вы можете прочитать в блоке Безопасность.

Как это работает

Создайте бота

Перейдите в Автоматизации > Интеграции > Чат-боты и Вебхуки и создайте бота. Подробнее — в разделе Боты.

Укажите Webhook URL

В настройках бота перейдите на вкладку Исходящий Webhook и укажите URL вашего сервера, на который Пачка будет отправлять события.

Выберите типы событий

Отметьте события, которые вас интересуют: новые сообщения, реакции, нажатия кнопок и другие.

Добавьте бота в чаты

Большинство событий приходят только из чатов, в которых состоит бот. Глобальные события (например, изменение состава участников пространства) не требуют добавления в чат.

Настройки

Настройки исходящего вебхука находятся на вкладке Исходящий Webhook в настройках бота.

Вкладка Исходящий Webhook в настройках бота

Сохранять историю событий

Включите эту настройку, чтобы бот сохранял все исходящие вебхуки в очередь. Сохранённые события можно получить через API — подробнее в разделе Поллинг.

Игнорировать свои сообщения

Включите эту настройку, чтобы не получать вебхуки о сообщениях, отправленных самим ботом. Это предотвращает зацикливание, когда бот реагирует на собственные сообщения. Нажатия кнопок обрабатываются всегда, вне зависимости от этой настройки.

Типы событий

Сообщения

В настройках доступны два отдельных пункта:

  • Новые сообщения — создание сообщений в чатах, где состоит бот, и в тредах этих чатов
  • Редактирование и удаление сообщений — изменение текста, файлов или удаление сообщений в чатах, где состоит бот, и в тредах этих чатов

Для новых сообщений доступны два режима отслеживания:

  • О любых сообщениях — все новые сообщения, включая треды
  • Сообщения, начинающиеся с команд — только сообщения, начинающиеся с указанных команд (например, /help, /deploy). Команды начинаются с / и пишутся на английском. Полезно, если вы не хотите получать поток событий, а вам нужно вызвать сценарий только по требованию пользователя
MessageWebhookPayloadobject

Реакции

Вебхук отправляется при добавлении или удалении реакции на сообщение в чате, где состоит бот.

ReactionWebhookPayloadobject

Нажатие кнопок

Вебхук отправляется при нажатии Data-кнопки в сообщении от бота. После получения вы можете PUTотредактировать сообщение — например, изменить или удалить кнопки — или POSTотправить новое как ответ пользователю. Подробнее — в разделе Кнопки в сообщениях.

ButtonWebhookPayloadobject

Заполнение формы

Вебхук отправляется при отправке пользователем заполненной формы (представления). Подробнее об обработке результатов — в разделе Обработка форм.

ViewSubmitWebhookPayloadobject

Изменение участников чатов

Вебхук отправляется при изменении состава участников чатов, где состоит бот, и в тредах этих чатов.

ChatMemberWebhookPayloadobject

Изменение участников пространства

Вебхук отправляется при добавлении, удалении или приостановке участников пространства. Не требует добавления бота в чат.

CompanyMemberWebhookPayloadobject

Отправка ссылок

Вебхук отправляется при появлении ссылки на один из доменов, указанных в настройках Unfurl-бота. Для получения таких событий необходимо создать специального Unfurl-бота. После получения вы можете воспользоваться методом POSTUnfurl (разворачивание ссылок) и создать предпросмотр ссылки в сообщении. Подробнее — в разделе Разворачивание ссылок.

LinkSharedWebhookPayloadobject

Безопасность

Каждый исходящий вебхук защищён с помощью подписи, основанной на хешировании содержимого. Подпись HMAC с использованием алгоритма SHA256 вычисляется из тела запроса и передаётся в заголовке Pachca-Signature.

В теле вебхука также содержится поле webhook_timestamp — метка времени в формате UNIX, указывающая момент отправки вебхука. Рекомендуется проверять, что это значение находится в пределах одной минуты от времени получения запроса, чтобы предотвратить атаки повторной отправки (replay attacks).

Пример исходящего вебхука (новое сообщение)
POST https://yourweb.site/read HTTP/1.1host: yourweb.sitecontent-Type: application/jsonpachca-signature: a805d3470c263f4628cafc4ed66235d8fe2229891d1fcf4e400331adff5d8e5auser-agent: Faraday v2.12.2content-length: 358 {    "event": "new",    "type": "message",    "webhook_timestamp": 1744618734,    "chat_id": 918264,    "content": "Клиент просит поправить шапку, подробности в документе",    "user_id": 134412,    "id": 56431,    "created_at": "2025-04-14T08:18:54.000Z",    "parent_message_id": null,    "entity_type": "discussion",    "entity_id": 918264,    "thread": null,    "url": "https://app.pachca.com/chats/124511?message=56431"}

Проверьте подпись

Для проверки подписи необходимо вычислить её самостоятельно, используя секрет вебхука Signing secret, который доступен в настройках бота во вкладке «Исходящий webhook». Рекомендуется использовать сырой (raw) контент тела запроса для вычисления хеша, так как при JSON-парсинге содержимое может быть изменено.

// WEBHOOK_SECRET - значение поля Signing secret во вкладке «Исходящий webhook» в настройках бота const signature = crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex");if (signature !== request.headers['pachca-signature']) {    throw "Invalid signature"}

Валидируйте IP-адрес отправителя

Кроме проверки подписи, также рекомендуется валидировать IP-адреса отправителя.

IP-адрес Пачки: 37.200.70.177

Реализация webhook handler

Полный пример обработки вебхуков на TypeScript (Express.js) и Python (Flask) с проверкой подписи, защитой от replay-атак и обработкой всех типов событий.

TypeScript (Express.js)

import express from "express"import crypto from "crypto" const SIGNING_SECRET = "your_signing_secret" // Из настроек бота → Исходящий Webhook → Signing Secretconst app = express() // Важно: используем express.raw для получения сырого тела запроса (для корректной проверки HMAC)app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {  // 1. Проверка подписи HMAC-SHA256  const signature = crypto.createHmac("sha256", SIGNING_SECRET)    .update(req.body).digest("hex")  if (signature !== req.headers["pachca-signature"]) {    return res.status(401).send("Invalid signature")  }   // 2. Защита от replay-атак (±60 секунд)  const event = JSON.parse(req.body.toString())  if (Math.abs(Date.now() / 1000 - event.webhook_timestamp) > 60) {    return res.status(401).send("Expired event")  }   // 3. Обработка события по типу  switch (event.type) {    case "message":      if (event.event === "new") {        console.log(`Новое сообщение от ${event.user_id}: ${event.content}`)      } else if (event.event === "update") {        console.log(`Сообщение ${event.id} отредактировано`)      } else if (event.event === "delete") {        console.log(`Сообщение ${event.id} удалено`)      }      break    case "reaction":      console.log(`${event.event === "new" ? "Добавлена" : "Удалена"} реакция ${event.emoji}`)      break    case "button":      console.log(`Нажата кнопка: ${event.data}`)      // trigger_id доступен 3 секунды — используйте его для открытия формы      break    case "view_submit":      console.log(`Форма заполнена:`, event.payload)      break  }   res.status(200).send("OK")})app.listen(3000)

Python (Flask)

import hmac, hashlib, json, timefrom flask import Flask, request, abort SIGNING_SECRET = "your_signing_secret"  # Из настроек бота → Исходящий Webhook → Signing Secretapp = Flask(__name__) @app.route("/webhook", methods=["POST"])def webhook():    raw_body = request.get_data()     # 1. Проверка подписи HMAC-SHA256    expected = hmac.new(SIGNING_SECRET.encode(), raw_body, hashlib.sha256).hexdigest()    if expected != request.headers.get("Pachca-Signature"):        abort(401)     # 2. Защита от replay-атак (±60 секунд)    event = json.loads(raw_body)    if abs(time.time() - event["webhook_timestamp"]) > 60:        abort(401)     # 3. Обработка события    if event["type"] == "message" and event["event"] == "new":        print(f"Новое сообщение от {event['user_id']}: {event['content']}")    elif event["type"] == "button":        print(f"Нажата кнопка: {event['data']}")    elif event["type"] == "view_submit":        print(f"Форма заполнена: {event['payload']}")     return "OK", 200

Идемпотентная обработка

Пачка использует at-least-once delivery — один и тот же вебхук может прийти повторно. Обработчик должен быть идемпотентным:

// Дедупликация по уникальным полям событияconst processed = new Set<string>() function getEventKey(event: any): string {  // Уникальный ключ: тип + событие + id объекта  return `${event.type}:${event.event}:${event.id || event.message_id || ""}`} app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {  // ... проверка подписи ...  const event = JSON.parse(req.body.toString())  const key = getEventKey(event)  if (processed.has(key)) {    return res.status(200).send("Already processed")  }  processed.add(key)  processEvent(event) // Ваша логика обработки  res.status(200).send("OK")})

Обработка ошибок доставки

Если ваш сервер не ответил 2xx в течение таймаута, Пачка повторит попытку доставки. Рекомендации:

  • Отвечайте 200 OK как можно быстрее — выносите тяжёлую обработку в фоновую очередь
  • При временных ошибках отвечайте 503 — Пачка повторит позже
  • При постоянных ошибках (невалидные данные) — 200 OK чтобы избежать бесконечных повторов

Поллинг

Если у вас нет возможности принимать входящие HTTP-запросы (локальная разработка, жёсткие firewall-правила), используйте поллинг — получение событий через API.

Включите настройку Сохранять историю событий, чтобы получать события через API:

Периодически запрашивайте список событий, обрабатывайте каждое и удаляйте обработанные.

Пример поллинга (TypeScript)

import { PachcaClient } from "@pachca/sdk" const client = new PachcaClient("YOUR_BOT_TOKEN") async function pollEvents() {  const events = await client.bots.getWebhookEvents()  for (const event of events.data) {    console.log("Событие:", event.type, event.event)    // Обработать событие...    await client.bots.deleteWebhookEvent(event.id) // Удалить из очереди  }} // Запускать каждые 5 секундsetInterval(pollEvents, 5000)