Skip to main content

Webhooks (Asynchroniczne webhooki)

DLA AGENTÓW AIChatGPTClaude

Wzorzec architektoniczny asynchronicznej interakcji międzyserwisowej (Event-driven Push), w którym dostawca zdarzeń wysyła żądanie HTTP POST z danymi na zarejestrowany URL konsumenta po wystąpieniu zdarzenia systemowego.

1. Przegląd koncepcji i problem systemowy

Tradycyjna integracja systemów poprzez ciągłe odpytywanie (Polling / Short Polling) cierpi na dwa wzajemnie wykluczające się problemy:

  1. Wysokie opóźnienie reakcji (Latency): Jeśli klient sprawdza zmiany co 60 sekund, dane są dostarczane ze średnim opóźnieniem wynoszącym 30 sekund.
  2. Ogromne marnotrawstwo zasobów obliczeniowych: 99% zapytań HTTP zwraca pustą odpowiedź 304 Not Modified lub [], przeciążając bazę danych oraz stos sieciowy.

Webhooks (Callbacki przez HTTP) zmieniają paradygmat z Pull na Push. Zamiast pytać: "Czy są nowe dane?", konsument rejestruje swój adres URL (Webhook Listener). Gdy tylko w systemie providera wystąpi zdarzenie (sukces transakcji Stripe, nowy commit w GitHub, przychodząca wiadomość Telegram), provider wysyła zapytanie HTTP POST z ładunkiem JSON bezpośrednio na serwer subskrybenta.

Tradycyjne odpytywanie (marnowanie zasobów):
Klient  ---> GET /events?since=... ---> Serwer (Pusto: [])
Klient  ---> GET /events?since=... ---> Serwer (Pusto: [])
Klient  ---> GET /events?since=... ---> Serwer (Nowe zdarzenie!)

Webhooki sterowane zdarzeniami (natychmiastowe push):
Dostawca zdarzeń (Stripe / GitHub / Telegram)
       |
       |  Zdarzenie: charge.succeeded (Payload zdarzenia + podpis HMAC)
       |  HTTP POST https://api.mysite.com/webhooks/stripe
       v
Odbiorca (Walidacja podpisu -> 200 OK -> Kolejka w tle RabbitMQ/Redis)

2. Taksonomia architektoniczna i model mentalny

Cykl życia webhooka opiera się na trzech filarach architektonicznych:

  1. Kryptograficzna autentykacja (HMAC Signature):
    • Provider generuje kryptograficzny hash z ciała zapytania (raw body) przy użyciu wspólnego klucza tajnego (Webhook Secret), dodając go do nagłówka (np. Stripe-Signature lub X-Hub-Signature-256).
    • Konsument oblicza ten sam hash i waliduje go, eliminując ryzyko sfałszowania zapytania przez napastnika.
  2. Idempotentność i deduplikacja (Idempotency Engine):
    • Każde zdarzenie posiada globalnie unikalny identyfikator (evt_xxxxxxxxxx).
    • Konsument atomowo sprawdza obecność ID w cache/bazie danych (np. poprzez INSERT ... ON CONFLICT DO NOTHING lub Redis SET key NX EX 86400).
  3. Polityka gwarancji dostawy i ponownych prób (Retry & Dead-Letter Queue):
    • W przypadku niedostępności serwera konsumenta provider powtarza zapytania według skali wykładniczej (Exponential Backoff z jitterem): po 5 min, 30 min, 2 h, 12 h.
    • Zdarzenia, których nie udało się przetworzyć po maksymalnej liczbie prób, trafiają do kolejki martwych list (Dead-Letter Queue, DLQ) do ręcznej analizy przez inżynierów.

3. Potok techniczny i mechanika wewnętrzna

Produkcyjny procesor z weryfikacją HMAC (Node.js / Express / TypeScript)

[!IMPORTANT] Do sprawdzenia podpisu konieczne jest uzyskanie surowego ciała zapytania (raw buffer), a nie sparsowanego obiektu req.body. W przeciwnym razie hash się nie zgodzi z powodu różnic w białych znakach i sortowaniu kluczy JSON.

import express, { Request, Response } from "express";
import crypto from "crypto";

const app = express();

// Отримання raw buffer для ендпоінтів вебхуків
app.post(
  "/api/v1/stripe-webhook",
  express.raw({ type: "application/json" }),
  async (req: Request, res: Response) => {
    const signature = req.headers["stripe-signature"] as string;
    const secret = process.env.STRIPE_WEBHOOK_SECRET!;

    if (!signature) {
      return res.status(400).send("Missing signature header");
    }

    // 1. Криптографічна валідація підпису через HMAC SHA-256
    const computedHmac = crypto
      .createHmac("sha256", secret)
      .update(req.body)
      .digest("hex");

    // Безпечне порівняння за фіксований час
    const isValid = crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(computedHmac)
    );

    if (!isValid) {
      return res.status(401).send("Invalid signature");
    }

    const payload = JSON.parse(req.body.toString());
    const eventId = payload.id;

    // 2. Ідемпотентна перевірка дедуплікації
    const isNew = await redis.set(`webhook:processed:${eventId}`, "1", "NX", "EX", 86400);
    if (!isNew) {
      // Подію вже оброблено — повертаємо 200 OK без повторного виконання
      return res.status(200).json({ status: "already_processed" });
    }

    // 3. Відправка важкої роботи в чергу BullMQ
    await eventQueue.add("process_payment", payload);

    // 4. Миттєве підтвердження отримання
    return res.status(200).json({ received: true });
  }
);

4. Praktyczne scenariusze inżynierskie w środowisku produkcyjnym

01. Bramka płatności dla AI SaaS (Stripe / Paddle / LiqPay)

Użytkownik opłaca subskrypcję planu Pro. System płatności generuje zdarzenie invoice.payment_succeeded. Webhook natychmiast aktualizuje status użytkownika w bazie danych, resetuje limity tokenów w Redis oraz wysyła e-mail powitalny, pozwalając użytkownikowi kontynuować pracę bez przeładowania strony.

02. Wyzwalacz autonomicznego potoku CI/CD (GitHub Webhooks)

Inżynier wykonuje git push do gałęzi main. GitHub wysyła ładunek z hashem commita na własny serwer Coolify lub ArgoCD. Serwer sprawdza sekret, klonuje zmodyfikowany kod, uruchamia testy jednostkowe w izolowanym kontenerze Docker i aktualizuje serwis produkcyjny zgodnie ze strategią Blue/Green.

03. Asynchroniczne zbieranie wyników inferencji ciężkich modeli AI

Przy wywoływaniu modeli generowania wideo (Runway, Kling) lub trenowania LoRA zapytanie trwa kilka minut. Zamiast utrzymywać otwarte połączenie HTTP, klient przekazuje webhook_url. Gdy tylko klastr GPU zakończy generowanie, wysyła on webhook z finalnym linkiem S3 do wygenerowanych mediów.


5. Pułapki, typowe błędy i bezpieczeństwo

  1. Parsowanie JSON przed weryfikacją podpisu: Jeśli przed sprawdzeniem podpisu zadziała standardowy middleware express.json(), serializator może zmienić kolejność pól, escapowanie znaków Unicode lub formatowanie liczb, co doprowadzi do niezgodności sumy kontrolnej HMAC i odrzucenia zapytania jako Invalid Signature.
  2. Synchroniczne blokowanie wykonania (Webhook Deadlock): Wykonywanie ciężkich transakcji lub długotrwałych zapytań HTTP do zewnętrznych API wewnątrz handlera webhooka gwarantuje timeout po stronie providera. Zwracaj 200 OK natychmiast po walidacji podpisu i zapisaniu do kolejki.
  3. Brak walidacji znaczników czasu (Replay Attacks): Napastnik, który przechwycił legalny podpisany request webhook, może wysłać go ponownie po godzinie. Providerzy dodają do podpisu znacznik czasu (timestamp). Upewnij się, że różnica między czasem zapytania a zegarem systemowym serwera nie przekracza dopuszczalnego okna czasowego (np. 5 minut).
/ Najczęściej zadawane pytaniaSchema.org FAQPage

FAQ: Webhooks (Asynchroniczne webhooki)

Klasyczne porównywanie ciągów znaków przy użyciu operatorów == lub === przerywa działanie przy pierwszym niezgodnym znaku. Atakujący może mierzyć opóźnienie odpowiedzi serwera z nanosekundową precyzją (Timing Attack) i metodą prób i błędów odgadnąć poprawny hash podpisu. Funkcja timingSafeEqual wykonuje porównanie w stałym czasie, co gwarantuje odporność kryptograficzną.
/ Linki wewnętrzne
Wszystkie hasła