Skip to Content

Вебхуки (Приём)

При возникновении событий в вашем проекте SupDesk исходящие вебхуки отправляют HTTP POST-запросы на ваши настроенные эндпоинты. Эта страница документирует формат полезной нагрузки, механизм подписи и поведение повторных попыток, чтобы вы могли создавать надёжные приёмники вебхуков.

Формат полезной нагрузки

Каждая доставка вебхука использует стандартную оболочку:

{ "event": "post.created", "timestamp": "2026-07-15T12:00:00.000Z", "project_id": "550e8400-e29b-41d4-a716-446655440000", "data": { ... } }
ПолеТипОписание
eventstringСобытие, вызвавшее доставку
timestampstringВременная метка ISO 8601 момента возникновения события
project_idstringUUID проекта, в котором произошло событие
dataobjectПолезная нагрузка для конкретного события (см. ниже)

События

post.created

Отправлен новый пост обратной связи.

{ "event": "post.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode", "type": "feature", "status": "open", "author_type": "end_user", "body": "It would be great to have a dark mode option.", "created_at": "2026-07-15T12:00:00Z" } }

post.updated

Пост обратной связи был отредактирован.

{ "event": "post.updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode", "type": "feature", "status": "open", "author_type": "end_user", "body": "It would be great to have a dark mode option.", "created_at": "2026-07-15T12:00:00Z" } }

post.deleted

Пост обратной связи был удалён.

{ "event": "post.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode", "type": "feature", "status": "open", "author_type": "end_user", "body": "It would be great to have a dark mode option.", "created_at": "2026-07-15T12:00:00Z" } }

post.status_changed

Статус поста был изменён.

{ "event": "post.status_changed", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Add dark mode", "type": "feature", "old_status": "open", "new_status": "planned", "author_type": "member", "body": "We're planning this for Q3.", "created_at": "2026-07-15T12:00:00Z" } }

comment.created

Добавлен новый комментарий к посту.

{ "event": "comment.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "post_id": "550e8400-e29b-41d4-a716-446655440001", "post_title": "Add dark mode", "author_type": "end_user", "body": "Any update on this?", "created_at": "2026-07-15T12:00:00Z" } }

comment.updated

Комментарий был отредактирован.

{ "event": "comment.updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "post_id": "550e8400-e29b-41d4-a716-446655440001", "post_title": "Add dark mode", "author_type": "end_user", "body": "Any update on this?", "created_at": "2026-07-15T12:00:00Z" } }

comment.deleted

Комментарий был удалён.

{ "event": "comment.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "post_id": "550e8400-e29b-41d4-a716-446655440001", "post_title": "Add dark mode", "author_type": "end_user", "body": "Any update on this?", "created_at": "2026-07-15T12:00:00Z" } }

message.created

Отправлено новое приватное сообщение.

{ "event": "message.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "thread_id": "550e8400-e29b-41d4-a716-446655440001", "thread_subject": "Help needed", "sender": "end_user", "body": "I can't find the settings page.", "via": "web", "created_at": "2026-07-15T12:00:00Z" } }

message.updated

Приватное сообщение было отредактировано.

{ "event": "message.updated", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "thread_id": "550e8400-e29b-41d4-a716-446655440001", "thread_subject": "Help needed", "sender": "end_user", "body": "I can't find the settings page.", "via": "web", "created_at": "2026-07-15T12:00:00Z" } }

message.deleted

Приватное сообщение было удалено.

{ "event": "message.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "thread_id": "550e8400-e29b-41d4-a716-446655440001", "thread_subject": "Help needed", "sender": "end_user", "body": "I can't find the settings page.", "via": "web", "created_at": "2026-07-15T12:00:00Z" } }

beta_feedback.created

Бета-тестировщик отправил отзыв о бета-программе.

{ "event": "beta_feedback.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "program": "Acme 2.0 Beta", "category": "bug", "severity": "high", "title": "Crash on export", "body": "The app closes when I export a large file.", "created_at": "2026-07-15T12:00:00Z" } }

severity может быть null.

beta_feedback.deleted

Отзыв о бете был удалён.

{ "event": "beta_feedback.deleted", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "program": "Acme 2.0 Beta", "category": "bug", "severity": "high", "title": "Crash on export", "body": "The app closes when I export a large file.", "created_at": "2026-07-15T12:00:00Z" } }

waitlist_signup.created

Посетитель присоединился к списку ожидания.

{ "event": "waitlist_signup.created", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "jamie@example.com", "status": "waiting", "position": 42, "referral_count": 0, "referral_code": "a1b2c3", "source": "portal", "created_at": "2026-07-15T12:00:00Z", "invited_at": null, "joined_at": null } }

position равно null для записей invited и joined.

waitlist_signup.invited

Запись была приглашена из списка ожидания.

{ "event": "waitlist_signup.invited", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "jamie@example.com", "status": "invited", "position": null, "referral_count": 0, "referral_code": "a1b2c3", "source": "portal", "created_at": "2026-07-15T12:00:00Z", "invited_at": "2026-07-15T12:00:00Z", "joined_at": null } }

waitlist_signup.joined

Приглашённая запись приняла приглашение и присоединилась.

{ "event": "waitlist_signup.joined", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "jamie@example.com", "status": "joined", "position": null, "referral_count": 0, "referral_code": "a1b2c3", "source": "portal", "created_at": "2026-07-15T12:00:00Z", "invited_at": "2026-07-15T12:00:00Z", "joined_at": "2026-07-15T12:05:00Z" } }

csat_survey.completed

Опрос удовлетворённости CSAT завершён с оценкой.

{ "event": "csat_survey.completed", "data": { "thread_id": "550e8400-e29b-41d4-a716-446655440000", "rating": 5, "comment": "Support was fast and friendly.", "created_at": "2026-07-15T12:00:00Z" } }

rating — целое число от 1 до 5; comment может быть null.

Проверка подписей

Каждая доставка включает заголовок X-SupDesk-Signature:

X-SupDesk-Signature: sha256=a1b2c3d4e5f6...

Значение после sha256= — это шестнадцатеричный HMAC-SHA256 от тела запроса, вычисленный с использованием секрета подписи вашего вебхука.

Шаги проверки

  1. Прочитайте тело запроса (не парсите JSON сначала)
  2. Вычислите HMAC-SHA256(секрет_подписи, тело_запроса)
  3. Сравните шестнадцатеричный дайджест со значением в заголовке подписи
  4. Используйте сравнение постоянного времени для предотвращения атак по времени

Пример на Node.js

import { createHmac, timingSafeEqual } from "crypto"; function verifySupDeskWebhook(secret, rawBody, signatureHeader) { const expected = createHmac("sha256", secret) .update(rawBody) .digest("hex"); const received = signatureHeader.replace("sha256=", ""); return timingSafeEqual(Buffer.from(expected), Buffer.from(received)); } // Express example app.post("/webhooks/supdesk", express.raw({ type: "application/json" }), (req, res) => { const sig = req.headers["x-supdesk-signature"]; if (!verifySupDeskWebhook(SECRET, req.body, sig)) { return res.status(401).json({ error: "Invalid signature" }); } const event = JSON.parse(req.body); // Process event... res.status(200).json({ ok: true }); });

Пример на Python

import hmac import hashlib from flask import Flask, request, jsonify app = Flask(__name__) WEBHOOK_SECRET = "your-signing-secret" def verify_signature(secret: str, body: bytes, header: str) -> bool: expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() received = header.removeprefix("sha256=") return hmac.compare_digest(expected, received) @app.route("/webhooks/supdesk", methods=["POST"]) def handle_webhook(): sig = request.headers.get("X-SupDesk-Signature", "") if not verify_signature(WEBHOOK_SECRET, request.data, sig): return jsonify(error="Invalid signature"), 401 event = request.get_json() # Process event... return jsonify(ok=True), 200

Пример на Ruby

require "openssl" require "sinatra" require "json" WEBHOOK_SECRET = "your-signing-secret" def verify_signature(secret, body, header) expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body) received = header.sub("sha256=", "") Rack::Utils.secure_compare(expected, received) end post "/webhooks/supdesk" do body = request.body.read sig = request.env["HTTP_X_SUPDESK_SIGNATURE"] || "" unless verify_signature(WEBHOOK_SECRET, body, sig) halt 401, { error: "Invalid signature" }.to_json end event = JSON.parse(body) # Process event... status 200 { ok: true }.to_json end

Повторные попытки

При неудачной доставке вебхука SupDesk повторяет попытки с экспоненциальной задержкой:

ПопыткаЗадержка
1Немедленно
21 секунда
32 секунды

Условия повторных попыток: ответы 5xx, сетевые ошибки, тайм-ауты (10 секунд).

Не повторяется: ответы 4xx (ошибки клиента указывают на проблему конфигурации).

Ответ на вебхуки

  • Верните код статуса 2xx для подтверждения получения
  • Отвечайте в течение 10 секунд, чтобы избежать повторных попыток из-за тайм-аута
  • Обрабатывайте событие асинхронно, если ваша логика занимает больше времени
  • Возвращайте тот же ответ для дублирующих доставок (идемпотентность)
Last updated on