Усі статті
Discordwebhook debugginglocal testingbots

Вебхуки Discord проти interactions, тест локально

Discord називає «вебхуками» дві речі, які поводяться абсолютно по-різному. Одна — це URL за принципом «надіслав і забув» для публікації повідомлень. Інша — endpoint interactions, який отримує підписані POST щоразу, коли користувач виконує slash-команду. Перша працює на localhost. Друга — ніколи. Цей посібник про другу, бо саме їй потрібен тунель.

Два різновиди вебхуків Discord

Вихідні вебхуки — це URL, на які ви робите POST. Ви надсилаєте JSON-повідомлення, воно зʼявляється в каналі. Тунель не потрібен, бо трафік іде з вашої машини назовні.

Endpoint-и interactions — у зворотному напрямку. Ви реєструєте URL у Discord; Discord робить POST на нього, коли користувач виконує /вашу-команду. Endpoint має бути HTTPS, відповідати менш ніж за три секунди і перевіряти підпис Ed25519 на кожному запиті. Localhost сам цього не вміє.

Чому endpoint-и interactions суворіші за більшість

Discord хоче не лише HTTPS — він вимагає довести, що ви контролюєте endpoint, перш ніж прийняти URL. Перевірка відбувається при реєстрації: Discord шле interaction PING, ваш сервер має відповісти підписаним PONG. Якщо перевірка Ed25519 провалиться хоч раз, Discord відмовиться зберігати URL.

Частину з Ed25519 з першого разу ви не налаштуєте. Майже ні в кого не виходить.

Перевірка Ed25519 — частина, що кусає

Discord використовує Ed25519 (не HMAC SHA-256) і надає два заголовки на кожній interaction:

  • X-Signature-Ed25519 — підпис у hex
  • X-Signature-Timestamp — мітка часу в секундах

Ви підписуєте timestamp + rawBody публічним ключем, який Discord показує в порталі розробника. У більшості мов для цього є бібліотека. У Node:

import nacl from 'tweetnacl';

const valid = nacl.sign.detached.verify(
  Buffer.from(timestamp + rawBody),
  Buffer.from(signature, 'hex'),
  Buffer.from(PUBLIC_KEY, 'hex'),
);

Причина номер один провалу перевірки — знову — middleware, який парсить тіло до того, як виконається цей код. Вам потрібен сирий рядок, який надіслав Discord, а не перезібрана версія розпарсеного обʼєкта.

Крок за кроком: endpoint interactions на localhost

  1. Запустіть HTTP-сервер вашого бота локально (будь-який порт).
  2. Виконайте npx portpreview 3000, щоб отримати публічний HTTPS-адрес.
  3. У порталі розробника Discord відкрийте застосунок і вставте URL тунелю разом зі шляхом interactions у Interactions Endpoint URL.
  4. Натисніть Save. Discord одразу надішле PING. Якщо перевірка Ed25519 працює, URL збережеться. Якщо ні, поле покаже помилку.
  5. Після збереження виконайте slash-команду на будь-якому сервері, де є ваш бот. Запит надійде у ваш обробник.

Якщо збереження раз за разом не вдається, дослідіть захоплений запит — заголовки, тіло і відповідь вашого сервера. У девʼяти випадках із десяти тіло було тихо змінене JSON-middleware до перевірки.

Дедлайн у три секунди

Discord вимагає відповідь менш ніж за три секунди, інакше зараховує interaction як провалену. Для всього, що довше, надішліть першу відповідь типу 5 (відкладену), а потім PATCH-ом оновіть вихідну interaction справжньою відповіддю пізніше. Локальне тестування одразу показує, які обробники надто повільні — захоплення запитів показує точну затримку.

Вихідні вебхуки досі корисні для тестів

Якщо потрібно лише надсилати повідомлення в канал із локального коду, URL вихідного вебхука працює без тунелю. POST JSON — повідомлення зʼявилося. Ми використовуємо це для алертів із логів локальних прогонів.

Коли ця схема перестає вистачати

Для одного бота з кількома командами локального тестування з тунелем цілком досить. Якщо ви ведете публічного бота на мільйони користувачів, з часом захочеться staging-розгортання зі стабільним URL, щоб Discord не перевіряв заново щоразу при ротації сесії тунелю.

Тайм-аут Discord у три секунди — та сама родина проблем, що й десятисекундне вікно GitHub. Прочитайте наш погляд на повтори вебхуків та ідемпотентність для докладної версії. Приєднайтеся до списку очікування PortPreview заради тунелю + захоплення в одному CLI.

Поширені запитання

Чи можна тестувати slash-команди Discord на localhost?
Напряму — ні. Discord вимагає HTTPS, перевірку підпису Ed25519 і вікно відповіді три секунди. Використайте тунель, щоб відкрити локальний сервер по HTTPS, і налаштуйте його як endpoint interactions у порталі розробника.
Чому Discord відхиляє URL мого endpoint interactions?
Коли ви зберігаєте URL, Discord одразу шле interaction PING. Якщо перевірка Ed25519 провалюється або обробник не відповідає коректним PONG, збереження відхиляється. Найчастіша причина — middleware, що парсить тіло запиту до перевірки.
Чи потрібен вихідним вебхукам Discord тунель?
Ні. Вихідні вебхуки — це URL, на які ви робите POST із коду, тож трафік іде лише назовні. Тунель потрібен лише endpoint-ам interactions, які отримують POST від Discord.