Wiki / Core Features / Webhooki wychodzące

Webhooki wychodzące

Zaktualizowane przez Maxime_48 · 3 godziny temu · Wyświetlenia: 73

Języki: English Français Deutsch Español Português (Brasil) Italiano Nederlands Polski Türkçe Русский 日本語 한국어 中文

Webhook wysyła aktywność twojego zespołu pod adres URL, który należy do ciebie, w chwili jej wystąpienia. Każde działanie, które trafia do twojego Dziennika audytu, może zostać wysłane jako podpisany POST z JSON na twój własny serwer, własnego bota lub wewnętrzne narzędzie - bez odpytywania, bez eksportu, bez zeskrobywania panelu.

Jedna zasada rozstrzyga wszystko na tej stronie. Webhook niesie dokładnie to, co pokazuje Dziennik aktywności twojego zespołu, i nic więcej. Nie równoległy strumień z własnym zakresem, nie bogatszy ładunek dla maszyn. Jeśli czegoś nie ma na tej stronie, nie ma tego w treści.

Aktywny webhook zespołu z kategoriami dostarczania i czasem ostatniej dostawy


📍 Gdzie go znaleźć

Ustawienia zespołu → Webhooki, na osobnej stronie twojego zespołu. Do trzech adresów docelowych na zespół, każdy z własnym URL, własnymi kategoriami i własnym sekretem podpisu.

Kto może go skonfigurować: właściciel zespołu albo członek, którego rola obejmuje Usuwanie serwera z zespołu. To ta sama zasada, która otwiera sam Dziennik aktywności - webhook przekazuje to, co ta strona wyświetla, więc zgoda na jej czytanie to zgoda na przekazywanie. Zobacz Role i uprawnienia zespołu.


🎯 Co jest wysyłane, a co nigdy

Wybierasz kategorie. Dostarczane są tylko te, które zaznaczysz:

Kategoria Co obejmuje
Funkcje Funkcja włączona, wyłączona lub zapisane jej ustawienia
Serwery Serwer dodany do zespołu lub z niego usunięty
Zespół Zmiana nazwy zespołu, edycja ról, zmiany webhooków
Członkowie Członkowie zaproszeni, dodani, usunięci lub wychodzący
Moderacja Sankcje, apelacje, zgłoszenia, tickety, wyczyszczone historie
Subskrypcja Zmiany subskrypcji zespołu
Rozliczenia Zdarzenia rozliczeniowe zespołu
Działania platformy wobec tego zespołu Co administrator platformy zrobił z jednym z twoich serwerów

🚫 Trzy rzeczy, które nigdy nie podróżują

  • Adresy IP. W Dzienniku aktywności IP jest pokazywany osobie, do której należy, i nikomu innemu. Webhook nie ma czytelnika, z którym można by to sprawdzić, więc pole nie jest wysyłane - jest usuwane z ładunku, a nie zaczerniane.
  • Działania zaplecza, które nie dotyczą ciebie. Administracja ogólnoplatformowa nie niesie żadnego zespołu, więc nic w rozsyłaniu nie może jej dosięgnąć. Kategoria Działania platformy wobec tego zespołu to tylko to, co administrator zrobił z twoimi serwerami.
  • Dane uwierzytelniające. Każde pole wyglądające jak token, klucz lub sekret przychodzi jako ••••••••, po obu stronach pary przed/po. Dzieje się to ponownie w drodze wyjściowej, niezależnie od maskowania wykonywanego przez stronę.

Logowania nie są oferowane. Ślad audytu ma kategorię auth, która celowo nie występuje na liście powyżej: wysyłanie zdarzeń logowania na serwer osoby trzeciej, bez nadzoru i bezterminowo, to inny akt niż pokazywanie ich na stronie, którą ktoś musiał otworzyć. Może zostać dodana później.


📦 Dostawa

Każda dostawa to pojedynczy POST z treścią JSON:

{
  "version": 1,
  "delivery_id": "0a4b2f0e-8c6e-4a5f-9b3d-2f6c1e0d7a11",
  "event": "server.feature.enabled",
  "category": "feature",
  "team": { "id": 12, "name": "Night Shift" },
  "data": {
    "id": 918273,
    "action": "server.feature.enabled",
    "category": "feature",
    "description": "Enabled Starboard",
    "actor_name": "Ava",
    "actor_role": "moderator",
    "is_admin_action": false,
    "team_id": 12,
    "team_name": "Night Shift",
    "server_id": 4,
    "server_name": "Night Shift HQ",
    "subject_label": "Starboard",
    "changes": { "threshold": { "before": 3, "after": 5 } },
    "metadata": null,
    "created_at": "2026-09-06T09:41:12+00:00"
  }
}

Te piętnaście pól to całość data - te same piętnaście, które renderuje Dziennik aktywności, minus adres IP. Każde z nich może być null: działanie bez serwera niesie "server_id": null, działanie bez czego porównywać niesie "changes": null. Czytaj je defensywnie, zamiast zakładać kształt dla każdego zdarzenia.

  • version to wersja koperty. Istnieje po to, aby twój odbiorca mógł się według niej rozgałęziać, zamiast zgadywać, gdy kształt się rozszerzy.
  • event to audytowane działanie. Jest ich ponad sto trzydzieści, a lista rośnie wraz z platformą - dopasowuj po prefiksie lub po category, nigdy po wyczerpującej liście nazw.
  • delivery_id jest stały dla wszystkich ponowień tego samego zdarzenia. To on umożliwia deduplikację; patrz niżej.
  • data to sam wpis audytu, ten sam obiekt, który renderuje Dziennik aktywności.

📨 Nagłówki

Nagłówek Wartość
User-Agent YAWBDB-Webhooks/1.0
X-YAWBDB-Event Nazwa działania, taka sama jak event w treści
X-YAWBDB-Delivery ID dostawy, takie samo jak delivery_id
X-YAWBDB-Timestamp Czas uniksowy w sekundach, w chwili zbudowania żądania
X-YAWBDB-Signature v1= a po nim skrót szesnastkowy

🔐 Weryfikacja podpisu

Każdy, kto pozna URL twojego adresu docelowego, może wysłać na niego dowolny POST. Podpis pozwala odróżnić nasze dostawy od ich.

Skrót to HMAC-SHA256 z znacznika czasu, kropki, a następnie surowej treści żądania, z kluczem w postaci twojego sekretu podpisu:

signed_string = X-YAWBDB-Timestamp + "." + raw_body
signature     = "v1=" + hex( hmac_sha256(signed_string, secret) )

Trzy szczegóły są kluczowe, a pominięcie któregokolwiek zostawia lukę:

  • Podpisuj surowe bajty, przed jakimkolwiek parsowaniem JSON. Ponowna serializacja treści ją zmienia - kolejność kluczy, uciekane ukośniki, unicode - i skrót się nie zgodzi.
  • Znacznik czasu jest wewnątrz podpisywanego ciągu, a nie tylko obok niego. To pozwala odrzucać powtórzenia: odrzuć dostawę, której znacznik czasu jest starszy niż kilka minut (pięć minut to rozsądne okno), a atakujący nie może ponownie wysłać żądania przechwyconego w zeszłym tygodniu. Ponieważ znacznik czasu jest podpisany, nie może go też edytować.
  • Porównuj w stałym czasie. Zwykłe == zwraca wynik na pierwszym różniącym się bajcie, a ta różnica czasu wystarcza, by odzyskać podpis bajt po bajcie. Użyj hash_equals, crypto.timingSafeEqual, hmac.compare_digest - jak tylko nazywa to twój język.

PHP

$raw       = file_get_contents('php://input');
$timestamp = (int) ($_SERVER['HTTP_X_YAWBDB_TIMESTAMP'] ?? 0);
$given     = $_SERVER['HTTP_X_YAWBDB_SIGNATURE'] ?? '';

if (abs(time() - $timestamp) > 300) {
    http_response_code(400);   // too old, or clock is wrong
    exit;
}

$expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $raw, $secret);

if (!hash_equals($expected, $given)) {
    http_response_code(401);
    exit;
}

http_response_code(200);       // accepted

Node.js (Express)

import crypto from 'node:crypto';

// The raw body is required - express.json() alone destroys it.
app.post('/yawbdb', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = Number(req.get('X-YAWBDB-Timestamp'));
  const given     = req.get('X-YAWBDB-Signature') ?? '';

  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return res.sendStatus(400);

  const expected = 'v1=' + crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${req.body}`)
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(given);

  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);

  res.sendStatus(200);
});

Prefiks v1= nazywa schemat. Jeśli kiedyś się zmieni, odbiorca sprawdzający prefiks może celowo odrzucić nieznany, zamiast po cichu źle go odczytać.

🔑 Sekret

64 znaki szesnastkowe, wygenerowane dla ciebie, pokazane dokładnie raz - w panelu, zaraz po utworzeniu adresu docelowego lub naciśnięciu Nowy sekret. Potem nic go nie odczytuje: ani strona, ani API, ani eksport. Jeśli go zgubisz, generujesz nowy.

Rotacja działa natychmiast dla wszystkiego wysyłanego od tej chwili. Dostawy już ustawione w kolejce nadal wychodzą podpisane starym sekretem, aż kolejka się opróżni, więc spodziewaj się krótkiego nakładania i najpierw zaktualizuj swojego odbiorcę.


🔁 Dostawa, ponowienia i deduplikacja

  • Za odebrane liczy się tylko 2xx. Wszystko inne - 3xx, 4xx, 5xx - to niepowodzenie. Odpowiadaj szybko, a pracę wykonuj potem.
  • Przekierowania nie są śledzone. 302 to niepowodzenie, a nie przeskok. Podaj nam końcowy URL.
  • Limity czasu są krótkie: trzy sekundy na połączenie, pięć sekund na odpowiedź. To powiadomienie, a nie zdalne wywołanie procedury - najpierw potwierdź, potem przetwarzaj.
  • Cztery próby, w odstępach 30 sekund, 2 minut, a potem 10 minut: łącznie około dwunastu minut. To pokrywa wdrożenie, a nie awarię.
  • Ponowienia używają tego samego delivery_id. Odbiorca, który traktuje każdy POST jako nowy, podwoi liczenie w dniu, gdy nasza pierwsza próba się u ciebie powiedzie, ale po naszej stronie skończy się limitem czasu. Zapisuj ID i ignoruj to, które już obsłużyłeś.
  • Kolejność nie jest gwarantowana. Dostawy są kolejkowane niezależnie; ponowiona przychodzi po zdarzeniach, które wystąpiły później. Użyj data.created_at, jeśli kolejność ma dla ciebie znaczenie.

⛔ Automatyczne wyłączenie

Po 20 kolejnych niepowodzeniach adres docelowy zostaje wyłączony, a panel informuje o tym na karcie. Adres, który przestał odpowiadać, zwykle przestał na dobre, a nikt nie przychodzi nam o tym powiedzieć - ponawianie w nieskończoność kosztowałoby żądanie i wiersz historii przy każdym działaniu, bez końca.

Aby go uruchomić ponownie: napraw swojego odbiorcę, potem Edytuj webhook i zaznacz ponownie Dostarczaj zdarzenia pod ten adres. To ponowne włączenie zeruje licznik, więc naprawiony adres nie jest o jedno niepowodzenie od ponownego zgaśnięcia.


🧪 Wyślij test

Wyślij test natychmiast wysyła jedno prawdziwe, podpisane żądanie na twój adres docelowy i pokazuje kod statusu oraz czas odpowiedzi. To nie symulacja: przechodzi przez ten sam kod, przez który przechodzi każda prawdziwa dostawa, z tymi samymi nagłówkami, więc adres docelowy, który przejdzie test, nie może potem odrzucić ruchu produkcyjnego z powodu nagłówka, którego test nigdy nie wysłał.

Treść testu niesie "event": "panel.webhook.test" i "category": "test", więc twój odbiorca może ją rozpoznać.

Spodziewaj się dwóch dostaw, nie jednej. Naciśnięcie przycisku jest samo w sobie audytowanym działaniem zespołu. Więc twój adres dostaje dostawę testową i - jeśli subskrybuje kategorię Zespół - chwilę później drugą, zwykłą dostawę dla team.webhook.tested. To jest poprawne i nie jest pętlą.

Test nigdy nie wlicza się do limitu 20 niepowodzeń i nigdy go nie zeruje. Debugowanie adresu docelowego nie może go wyłączyć.


📜 Ostatnie próby

Ostatnie próby pokazują 20 ostatnich prób dla tego adresu: kiedy, które zdarzenie, który numer próby, kod statusu lub błąd i jak długo to trwało. Niepowodzenia zawierają krótki fragment tego, co odpowiedział twój serwer - co zwykle jest najszybszym sposobem, by odkryć, że to reverse proxy, a nie twój kod, mówi „nie".

Próby są przechowywane przez 30 dni i czyszczone co noc. To odpad z debugowania, a nie rejestr: sam ślad audytu ma własną, znacznie dłuższą retencję.


🛡️ Gdzie odmawiamy wysyłania

Adres URL musi wskazywać na publiczny internet. URL, który rozwiązuje się do adresu pętli zwrotnej, zakresu prywatnego, adresu link-local lub adresu metadanych chmury, jest odrzucany - przy zapisie, i ponownie przed każdym pojedynczym wysłaniem.

Ta druga kontrola jest tą, która liczy się w czasie. Nazwa, która w dniu wpisania wskazywała coś publicznego, może po miesiącu wskazywać 127.0.0.1, a webhook odpala się tak długo, jak istnieje. Odmowa liczy się jako niepowodzenie adresu docelowego i nie jest ponawiana: zadanie rozwiązywaczowi tego samego pytania jeszcze trzy razy niczego by nie zmieniło.

Preferuj adres https. Podpis dowodzi, kto wysłał treść, ale jej nie ukrywa - przez zwykłe http twoja aktywność podróżuje otwartym tekstem.


💬 Prosto na kanał Discord lub Slack

Wklej URL webhooka Discord (https://discord.com/api/webhooks/…) jako adres docelowy, a panel wyśle wiadomość Discord zamiast koperty JSON: jedno osadzenie na wpis, z tytułem będącym opisem, zawierające zdarzenie, kategorię, wykonawcę, zespół, serwer, przedmiot i - jeśli są - zmiany jako krótki diff. Znacznik czasu pochodzi z wpisu, a stopka niesie ID dostawy.

Osadzenie jest zbudowane z tej samej koperty opisanej wyżej, więc dziedziczy jej dwie zasady: brak adresu IP, zamaskowane dane uwierzytelniające. Wzmianki są wyłączone po stronie Discorda, więc kanał lub rola o nazwie @everyone we wpisie publikuje się jako tekst i nikogo nie oznacza. Nagłówki X-YAWBDB-* i podpis nadal są wysyłane; Discord po prostu je ignoruje.

Discord odpowiada 204 No Content na przyjętą wiadomość, co pokazuje zielony wiersz w Ostatnich próbach.

To samo dotyczy URL przychodzącego webhooka Slack (https://hooks.slack.com/services/…): panel publikuje jedną wiadomość Slack na wpis - opis pogrubiony, potem jeden wiersz na fakt, zmiany w bloku kodu, a pod spodem znacznik czasu i ID dostawy. Jest zbudowana z tej samej koperty, więc obowiązują te same dwie zasady, a trzy znaki, które Slack rezerwuje dla własnych komend (<, >, &), są escapowane, więc nazwa wpisana przez członka nigdy nie zamieni się w @channel ani @here. Slack odpowiada 200 ok na przyjętą wiadomość. URL-e workflow i trigger Slacka nie są wiadomościami: zachowują kopertę JSON, którą workflow może rozebrać za pomocą własnych zmiennych.


💡 Co ludzie z tym budują

  • Publikuj aktywność swojego zespołu na własnym kanale Discord lub Slack - URL webhooka Discord lub Slack działa od razu albo postaw własnego odbiorcę pośrodku, aby filtrować i przeformatowywać.
  • Odbijaj ślad do własnego magazynu logów i trzymaj go poza oknem retencji panelu.
  • Powiadom kogoś, gdy działanie moderacji wystąpi poza godzinami pracy.
  • Uruchom build, synchronizację lub kopię zapasową, gdy zmieni się ustawienie funkcji.

🔗 Zobacz też