Reference: Incoming Webhook HTTP Endpoint
Полный справочник endpoint'а, который принимает сообщения от внешних сервисов.
Endpoint
POST /api/v1/webhooks/incoming/{token}{token} — уникальная часть URL, выдаётся при создании webhook'а. Хранить как secret (даже если require_signature: false — это единственный фактор защиты).
Request
Headers
| Header | Обязателен | Значение |
|---|---|---|
Content-Type |
Да | application/json или application/x-www-form-urlencoded |
X-Slack-Signature |
Если require_signature: true и выбрана Slack-схема |
v0=<hex(HMAC-SHA256)> |
X-Slack-Request-Timestamp |
То же | Unix seconds |
X-PartyFlow-Signature |
Если require_signature: true и выбрана PartyFlow-native |
sha256=<hex(HMAC-SHA256)> |
X-PartyFlow-Timestamp |
То же | Unix seconds |
Если Content-Type: application/x-www-form-urlencoded, endpoint извлекает поле payload и парсит его как JSON (Slack interactive pattern).
Body
Максимум 256 KB. Endpoint определяет формат по структуре payload:
| Признак | Формат |
|---|---|
Есть хотя бы одно из text, attachments, blocks |
Slack |
Есть message.content или message.entity_type |
Pachca |
| Ни то ни другое | HTTP 400 |
Slack-формат
| Поле | Тип | Описание |
|---|---|---|
text |
string | Текст (Slack mrkdwn → конвертируется в стандартный Markdown) |
attachments |
array | Legacy attachments (color bar, title, fields, footer) |
blocks |
array | Block Kit: header, section, divider, context, image |
username |
string | Переопределяет display name webhook'а для этого сообщения |
icon_url |
string | Переопределяет аватар для этого сообщения (https-only) |
silent |
bool | См. "Push policy" |
severity |
string | critical / warning / info — см. "Push policy" |
mentions |
string[] | user_id получателей push'а |
Полное описание attachments и blocks — reference/message-format.md.
Конвертация mrkdwn → Markdown (применяется только к text, не к тексту внутри attachments/blocks):
| Slack mrkdwn | Markdown |
|---|---|
*bold* |
**bold** |
_italic_ |
*italic* |
~strike~ |
~~strike~~ |
<url|text> |
[text](url) |
<url> |
url |
& < > |
& < > |
Pachca-формат
| Поле | Тип | Описание |
|---|---|---|
message.content |
string | Markdown (передаётся as-is, без mrkdwn-конвертации) |
message.buttons |
array | URL-кнопки |
message.files |
array | Вложения |
silent, severity, mentions |
см. Slack-формат | Работают идентично; допустимы и на верхнем уровне, и внутри message.* |
Response
Success
HTTP 200 OK
Content-Type: application/json
{"ok": true, "message_id": "<uuid>"}Сообщение появляется в канале в течение ~100–500ms после 200.
Errors
| Status | Причина | Ретраить? |
|---|---|---|
| 400 | Невалидный JSON; неизвестный формат (нет text/attachments/blocks/message.content); поле некорректного типа. |
Нет — фиксить payload. |
| 401 | Неверный/отозванный token; невалидная подпись; timestamp вне окна 5 минут. | Нет — проверить token/secret/часы. |
| 405 | Метод != POST. |
Нет. |
| 410 | Webhook auto-disabled после 100 подряд ошибок. | Нет — admin должен включить в UI. |
| 413 | Body > 256 KB. | Нет — урезать payload. |
| 429 | Rate limit превышен. Заголовок Retry-After: 1. |
Да, через Retry-After секунд. |
| 502 | Внутренний сервис чата недоступен. | Да, с exponential backoff. |
Тело ответа при ошибках — generic error, без деталей (сделано осознанно: не хотим облегчать перебор подписей/токенов).
Push policy
По умолчанию webhook-сообщения не триггерят push. Push срабатывает только при @mention: передан хотя бы один элемент в mentions[], либо в тексте есть @channel, @here.
Матрица поведения (webhook с default_push: true)
| Payload | Что происходит |
|---|---|
Нет mention в тексте и mentions: [] |
Только in-app уведомление, push — нет. |
silent: true |
Только in-app уведомление. severity и mentions игнорируются для push-пути. |
severity: "info" + есть mention |
Только in-app уведомление, push — нет. |
severity: "warning" (default) + есть mention |
Push с учётом DND/mute/quiet hours получателя. |
severity: "critical" + есть mention |
Push пробивает DND и quiet hours. muted_conversations всё равно уважаются — если юзер заглушил канал, не придёт. |
Если у webhook'а default_push: false, все комбинации эквивалентны "только in-app уведомление". Исключение — payload с silent: false: это разовый override, push разрешён.
Спецтокены в тексте
| Токен | Эффект |
|---|---|
@channel |
push всем участникам канала (с учётом DND/mute/quiet-hours) |
@here |
push только онлайн-сессиям без focus (Slack semantics) |
Синонимы severity
Приложения из экосистемы PagerDuty/Sentry/Opsgenie присылают свои уровни. Endpoint приводит их к трём внутренним:
| Входит как | Становится |
|---|---|
error, fatal, emergency, alert, critical |
critical |
warn, warning |
warning |
debug, notice, info |
info |
| Что-либо другое | warning (fallback) |
Rate limits
| Тариф | msg/sec per webhook |
|---|---|
| Free | 5 |
| Pro | 10 |
| Enterprise | 30 |
При превышении — HTTP 429 + Retry-After: 1.
Auto-disable
После 100 подряд ошибочных доставок (4xx или 5xx от endpoint'а) webhook автоматически отключается. Дальнейшие запросы возвращают HTTP 410 Gone. Admin включает webhook вручную в UI.
Счётчик сбрасывается на первом успешном 2xx.
Примеры
curl — token-only
curl -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"text":"Hello from CI"}'curl — с подписью (PartyFlow-native)
SECRET='whsec_...'
BODY='{"text":"Hello signed"}'
TS=$(date +%s)
SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')"
curl -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-H "X-PartyFlow-Timestamp: $TS" \
-H "X-PartyFlow-Signature: $SIG" \
-d "$BODY"Python
import hmac, hashlib, time, json, requests
WEBHOOK_URL = "https://api.partyflow.ru/api/v1/webhooks/incoming/whk_..."
SECRET = b"whsec_..."
body_dict = {"text": "Hello from Python"}
body = json.dumps(body_dict, separators=(",", ":")).encode() # stable serialization
ts = str(int(time.time()))
signing_string = ts.encode() + b"." + body
signature = "sha256=" + hmac.new(SECRET, signing_string, hashlib.sha256).hexdigest()
resp = requests.post(
WEBHOOK_URL,
data=body,
headers={
"Content-Type": "application/json",
"X-PartyFlow-Timestamp": ts,
"X-PartyFlow-Signature": signature,
},
)
resp.raise_for_status()
print(resp.json())Node.js
const crypto = require("crypto");
const WEBHOOK_URL = "https://api.partyflow.ru/api/v1/webhooks/incoming/whk_...";
const SECRET = "whsec_...";
const body = JSON.stringify({ text: "Hello from Node" });
const ts = Math.floor(Date.now() / 1000).toString();
const signature = "sha256=" + crypto
.createHmac("sha256", SECRET)
.update(`${ts}.${body}`)
.digest("hex");
const resp = await fetch(WEBHOOK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-PartyFlow-Timestamp": ts,
"X-PartyFlow-Signature": signature,
},
body,
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());Связанное
- concepts/security.md — HMAC, две схемы, anti-replay, ротация.
- concepts/rate-limits.md — retry-стратегии, auto-disable.
- reference/message-format.md — полное описание
attachments,blocks,buttons,files. - reference/error-codes.md — все HTTP статусы с диагностикой.