Вебхуки
Pano может сообщать другой системе, когда что-то происходит: игрок регистрируется, открывается тикет, публикуется запись, оплачивается заказ. Вы даёте Pano адрес; Pano отправляет на него подписанный POST.
Создайте вебхук в панели: Настройки -> Вебхуки: название, адрес, события, секрет (показывается один раз). Тест сразу отправляет ping.
События
Имена имеют вид источник.тема.глагол. Источник это core или короткое имя плагина.
| Событие | Когда |
|---|---|
core.user.registered | Игрок регистрируется |
core.user.deleted | Игрок удалён в панели |
core.ticket.created | Открыт тикет |
core.ticket.replied | В тикет пришло сообщение (staff показывает, кто написал) |
core.post.published | Запись опубликована |
core.test.ping | Вы нажали Тест |
Плагины добавляют свои, например market.order.paid, market.order.refunded, market.subscription.renewed, market.shipment.shipped. Панель перечисляет все события с примерами.
Подписывайтесь на точные имена, на core.* или market.*, либо на *. Подстановочные знаки никогда не включают core.test.ping.
Полезная нагрузка не содержит адресов электронной почты, IP-адресов и текстов сообщений.
Что вы получаете
{
"id": "6c1f1b0e-0d4b-3a5e-9b0e-2f5c7e1a9d11",
"event": "core.post.published",
"source": "core",
"createdAt": 1790000000000,
"apiVersion": 1,
"site": { "name": "My server", "url": "https://example.com" },
"data": { "id": 7, "title": "Hello", "url": "/post/hello", "categoryId": 1, "publishedAt": 1790000000000 }
}Позже могут появиться новые ключи; не падайте на неизвестном. id стабилен для одного события и одной конечной точки: используйте его, чтобы игнорировать повторы.
Заголовки: X-Pano-Event, X-Pano-Event-Id, X-Pano-Delivery, X-Pano-Attempt, X-Pano-Signature.
Проверьте подпись
X-Pano-Signature имеет вид t=<unix-секунды>,v1=<hex>, где v1 это HMAC-SHA256 от <t>.<сырое тело> с вашим секретом. Используйте сырое тело, а не разобранный JSON. Отклоняйте t, старше 300 секунд.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}Подпись можно отключить для конечных точек, которым вы доверяете другим способом. Формат Discord отправляет неподписанный embed.
Повторы
Отвечайте любым 2xx в течение 10 секунд. Иначе Pano повторяет, по умолчанию до 8 попыток (от 1 до 20):
- Задержка растёт с 30 секунд, удваиваясь, до 6 часов, с небольшим разбросом.
Retry-Afterпри429или503учитывается (не более 1 часа).410 Goneостанавливает сразу. Перенаправления не выполняются.- После 50 неудач подряд вебхук отключается.
Pano отклоняет адреса, указывающие внутрь его собственной сети. Для локального тестового получателя задайте webhooks.allow-private-targets = true в config.conf (на хостинговом Pano игнорируется).
Журнал
Каждая доставка хранится со статусом (PENDING, SENDING, SUCCEEDED, FAILED, DEAD), числом попыток, ответом и временем. Откройте её в панели, чтобы увидеть последний ответ или отправить повторно. Завершённые записи удаляются через 30 дней. До 50 вебхуков на сайт.