Enterprise edycja
Webhook — szczegółowa referencja
W skrócie
Dział zatytułowany „W skrócie”Przestrzeń nazw NextPDF\Enterprise\Webhook dostarcza webhooki dla zdarzeń zadań w zakresie najemcy. Powierzchnia publiczna to sześć symboli: WebhookManager, WebhookRegistration, WebhookPayload, WebhookDelivery, WebhookRetryPolicy oraz DeadLetterEntry. Menedżer rejestruje punkty końcowe dla poszczególnych najemców i rozdziela zdarzenia zadań do subskrybujących rejestracji. Silnik dostarczania wysyła metodą POST ładunek JSON podpisany HMAC-SHA256, weryfikuje każdy cel względem bramy ruchu wychodzącego SSRF z Core, ponawia z wykładniczym wycofaniem oraz zapisuje trwałe niepowodzenia w kolejce dead-letter w pamięci. Od wersji 3.1.0 podpis wiąże nagłówek X-NextPDF-Timestamp z bazowym ciągiem MAC, dzięki czemu odbiorcy weryfikują jednocześnie świeżość i integralność. Przewodnik na poziomie przepływu pracy znajduje się na stronie Webhook.
Dostępność i licencjonowanie
Dział zatytułowany „Dostępność i licencjonowanie”Ta możliwość jest dostarczana w NextPDF Enterprise (nextpdf/enterprise) i aktywuje się wraz z kopertą licencyjną poziomu Enterprise. Wdrożenie bez tego uprawnienia nie ładuje klas tej możliwości. Porównaj edycje i uzyskaj licencję.
Powierzchnia webhooków to podstawowa możliwość Enterprise, dostępna po zainstalowaniu pakietu Enterprise; nie istnieje osobna flaga dla poszczególnych funkcji. NextPDF Core (Apache-2.0) oraz NextPDF Pro nie mają powierzchni rejestracji ani dostarczania webhooków; menedżer, rejestracja, ładunek, silnik dostarczania, polityka ponawiania oraz wpis dead-letter są dostarczane wyłącznie w pakiecie nextpdf/enterprise.
Publiczna powierzchnia API
Dział zatytułowany „Publiczna powierzchnia API”| Symbol | Parametry | Zachowanie domyślne | Zwraca | Zgłasza lub kończy się niepowodzeniem | Uwagi |
|---|---|---|---|---|---|
WebhookManager::__construct | WebhookDelivery $delivery, ?LoggerInterface $logger = null | Tworzy menedżera z pustym indeksem rejestracji w pamięci | Nowy WebhookManager | Nie zgłasza | Rejestracje są indeksowane dla danego najemcy |
WebhookManager::register | TenantContext $tenant, WebhookRegistration $registration | Dołącza rejestrację do indeksu wywołującego najemcy | void | InvalidArgumentException, gdy najemca rejestracji nie odpowiada najemcy z kontekstu | Rejestracja międzynajemcza jest odrzucana przed zapisem |
WebhookManager::unregister | TenantContext $tenant, string $registrationId | Zastępuje pasującą rejestrację dezaktywowaną kopią | bool | Nie zgłasza; zwraca false, gdy identyfikator nie zostanie znaleziony | Miękka dezaktywacja; historia jest zachowana |
WebhookManager::activeRegistrations | TenantContext $tenant | Filtruje rejestracje najemcy do aktywnych | list<WebhookRegistration> | Nie zgłasza | Widoczne są wyłącznie rejestracje wywołującego najemcy |
WebhookManager::dispatch | TenantContext $tenant, JobEvent $event | Dostarcza zdarzenie do każdej aktywnej rejestracji subskrybującej dany typ zdarzenia | int (udane dostarczenia) | Propaguje JsonException, gdy dane zdarzenia nie dają się zakodować do JSON; niepowodzenia dostarczania nie zgłaszają wyjątku | Dla każdego dostarczenia do rejestracji generowany jest nowy 32-znakowy szesnastkowy identyfikator dostarczenia |
WebhookRegistration::__construct | string $id, string $tenantId, string $url, array $events, string $secret, bool $active = true, ?string $description = null | Przechowuje przekazane wartości dosłownie | Nowy WebhookRegistration | Brak zadeklarowanego @throws; PHP zgłasza TypeError przy niezgodnych typach argumentów w trybie strict_types | final readonly; puste $events oznacza subskrypcję wszystkiego |
WebhookRegistration::subscribesTo | JobEventType $eventType | true, gdy $events jest puste lub zawiera dany typ | bool | Nie zgłasza | Ścisłe porównanie tożsamości |
WebhookRegistration::deactivate | — | Zwraca nieaktywną kopię | self | Nie zgłasza | Oryginalna instancja pozostaje niezmieniona |
WebhookPayload::fromJobEvent | JobEvent $event, string $tenantId, string $deliveryId | Kopiuje ze zdarzenia identyfikator zadania, typ zdarzenia, dane oraz znacznik czasu | self | Nie zgłasza | Fabryka statyczna używana przez dispatch |
WebhookPayload::toJson | — | Serializuje sześciopolową treść z nieucieczkowanymi ukośnikami | non-empty-string | JsonException, gdy dane zdarzenia nie dają się zakodować do JSON | JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES |
WebhookPayload::toArray | — | Zwraca treść jako tablicę asocjacyjną | array<string, mixed> | Nie zgłasza | Znacznik czasu w rozszerzonym formacie RFC 3339 |
WebhookPayload::signedTimestamp | — | Czas zdarzenia w sekundach uniksowych, ograniczony do zera lub więcej | int<0, max> | Nie zgłasza | Emitowany jako X-NextPDF-Timestamp i związany z MAC |
WebhookPayload::sign | string $secret | HMAC-SHA256 nad ciągiem bazowym {signedTimestamp}.{jsonBody} | non-empty-string (hex) | JsonException poprzez toJson(), gdy treść nie daje się zakodować | Kryptograficznie wiąże nagłówek znacznika czasu z treścią |
WebhookDelivery::__construct | ClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, WebhookRetryPolicy $retryPolicy = new WebhookRetryPolicy(), ?LoggerInterface $logger = null | Silnik dostarczania PSR-18/PSR-17 z pustą kolejką dead-letter | Nowy WebhookDelivery | Nie zgłasza | Polityka domyślna: 5 prób, baza 1 s, ograniczenie 300 s |
WebhookDelivery::deliver | WebhookRegistration $registration, WebhookPayload $payload | Wysyła metodą POST podpisany ładunek z walidacją ruchu wychodzącego SSRF przy każdej próbie oraz wykładniczym wycofaniem | bool | JsonException przed pierwszą próbą, gdy treść nie daje się zakodować; poza tym nie zgłasza — false oznacza, że ładunek trafił do kolejki dead-letter | true wyłącznie przy odpowiedzi 2xx |
WebhookDelivery::deadLetters | — | Zwraca wszystkie zapisane wpisy | list<DeadLetterEntry> | Nie zgłasza | W pamięci, w zakresie procesu |
WebhookDelivery::clearDeadLetters | — | Opróżnia kolejkę dead-letter | void | Nie zgłasza | Nieodwracalne; najpierw wyeksportuj wpisy, jeśli wymagane jest odtwarzanie |
WebhookRetryPolicy::__construct | int $maxRetries = 5, int $baseDelaySeconds = 1, int $maxDelaySeconds = 300 | Przechowuje wartości polityki | Nowy WebhookRetryPolicy | Brak zadeklarowanego @throws; parametry udokumentowane jako positive-int | $maxRetries liczy łączną liczbę prób |
WebhookRetryPolicy::delayForAttempt | int $attempt | baseDelaySeconds × 2^(attempt − 1), ograniczone do maxDelaySeconds | positive-int | Nie zgłasza | Numery prób liczone od 1 |
WebhookRetryPolicy::shouldRetry | int $currentAttempt | true, dopóki bieżąca próba jest poniżej maksimum | bool | Nie zgłasza | Oczekiwanie jest pomijane po ostatniej próbie |
WebhookRetryPolicy::default | — | 5 prób, baza 1 s, ograniczenie 300 s | self | Nie zgłasza | Fabryka statyczna; domyślne dla produkcji |
WebhookRetryPolicy::aggressive | — | 10 prób, baza 2 s, ograniczenie 600 s | self | Nie zgłasza | Fabryka statyczna dla krytycznych punktów końcowych |
DeadLetterEntry::__construct | string $id, string $registrationId, WebhookPayload $payload, int $attempts, string $lastError, ?int $lastHttpStatus, DateTimeImmutable $failedAt, bool $replayed = false | Przechowuje rekord niepowodzenia dosłownie | Nowy DeadLetterEntry | Brak zadeklarowanego @throws; TypeError w trybie strict_types | final readonly; null $lastHttpStatus oznacza niepowodzenie transportu |
DeadLetterEntry::markReplayed | — | Zwraca kopię z replayed = true | self | Nie zgłasza | Ten sam identyfikator; oryginalny wpis pozostaje niezmieniony |
public function __construct( private readonly WebhookDelivery $delivery, private readonly ?LoggerInterface $logger = null,) {}
public function register(TenantContext $tenant, WebhookRegistration $registration): void
public function unregister(TenantContext $tenant, string $registrationId): bool
public function activeRegistrations(TenantContext $tenant): array
public function dispatch(TenantContext $tenant, JobEvent $event): intpublic function __construct( public string $id, public string $tenantId, public string $url, public array $events, public string $secret, public bool $active = true, public ?string $description = null,) {}
public function subscribesTo(JobEventType $eventType): bool
public function deactivate(): selfpublic static function fromJobEvent( JobEvent $event, string $tenantId, string $deliveryId,): self
public function toJson(): string
public function toArray(): array
public function signedTimestamp(): int
public function sign(string $secret): stringpublic function __construct( private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly WebhookRetryPolicy $retryPolicy = new WebhookRetryPolicy(), private readonly ?LoggerInterface $logger = null,) {}
public function deliver(WebhookRegistration $registration, WebhookPayload $payload): bool
public function deadLetters(): array
public function clearDeadLetters(): voidpublic function __construct( public int $maxRetries = 5, public int $baseDelaySeconds = 1, public int $maxDelaySeconds = 300,) {}
public function delayForAttempt(int $attempt): int
public function shouldRetry(int $currentAttempt): bool
public static function default(): self
public static function aggressive(): selfpublic function __construct( public string $id, public string $registrationId, public WebhookPayload $payload, public int $attempts, public string $lastError, public ?int $lastHttpStatus, public DateTimeImmutable $failedAt, public bool $replayed = false,) {}
public function markReplayed(): selfKontrakt zachowania
Dział zatytułowany „Kontrakt zachowania”- Rejestracje są indeksowane dla danego najemcy.
register()odrzuca rejestrację, której identyfikator najemcy nie odpowiada wywołującemu kontekstowi.unregister()to miękka dezaktywacja: rejestracja jest zastępowana nieaktywną kopią, co zachowuje historię, wykluczając ją jednocześnie z przyszłego rozdzielania. dispatch()iteruje wyłącznie aktywne rejestracje wywołującego najemcy, które subskrybują rozdzielany typ zdarzenia. Pusta lista subskrybowanych zdarzeń oznacza subskrypcję wszystkiego. Wartość zwracana liczy udane dostarczenia.- Każde dostarczenie to żądanie HTTP POST z treścią JSON i pięcioma nagłówkami:
Content-Type: application/json,X-NextPDF-Signature(sha256=<hex>),X-NextPDF-Timestamp(sekundy uniksowe),X-NextPDF-Delivery-IdorazX-NextPDF-Event. - Pola treści JSON to
delivery_id,job_id,event_type,data,timestamp(rozszerzony RFC 3339) oraztenant_id, serializowane z nieucieczkowanymi ukośnikami. Wartości typu zdarzenia pochodzą zJobEventTypewnextpdf/core:progress,completed,failed,cancelled. - Schemat podpisu (zmieniony w 3.1.0, zmiana łamiąca). Ciąg bazowy HMAC-SHA256 to
{signedTimestamp}.{jsonBody}, kluczowany sekretem rejestracji — a nie sama treść. WartośćX-NextPDF-Timestampjest składnikiem czasu w MAC, więc zmanipulowany lub powtórzony nagłówek znacznika czasu unieważnia podpis. - Weryfikacja po stronie odbiorcy: odczytaj nagłówek
X-NextPDF-TimestampT; odrzuć, gdyTwykracza poza akceptowalne okno świeżości (na przykład 300 s); przelicz ponowniehash_hmac('sha256', T . '.' . rawBody, secret)nad surowymi otrzymanymi bajtami; porównaj w czasie stałym z wartością nagłówka po usunięciu prefiksusha256=. - Treść, podpis oraz identyfikator dostarczenia są obliczane raz na dostarczenie i pozostają stałe we wszystkich próbach ponowienia.
- Brama ruchu wychodzącego SSRF. Przed każdą próbą docelowy adres URL przechodzi przez bramę
UrlValidator::validateExternalUrl()z Core: wyłącznie schemat HTTPS; zakresy loopback, prywatne, zarezerwowane, carrier-grade-NAT, metadanych chmury oraz przejściowe IPv6 z osadzonym IPv4 są blokowane; nazwy hostów są rozwiązywane w DNS (A i AAAA), a hosty nierozwiązywalne są odrzucane fail-closed. Zablokowany adres URL nigdy nie jest wysyłany: pętla prób jest przerywana, a ładunek trafia wprost do kolejki dead-letter z ostatnim błędemBlocked SSRF destination:oraz statusem HTTP null. - Klasyfikacja wyniku dla danej próby: 2xx to sukces i wraca natychmiast; 4xx inne niż 429 jest terminalne i trafia wprost do dead-letter; każdy inny wynik — 3xx, 429, 5xx lub wyjątek transportu — podlega ponowieniu do łącznej liczby prób z polityki.
- Wycofanie jest wykładnicze: oczekiwanie przed następną próbą to
baseDelaySeconds × 2^(attempt − 1), ograniczone domaxDelaySeconds. Oczekiwanie jest pomijane po ostatniej próbie. - Gdy żadna próba się nie powiedzie,
DeadLetterEntryzapisuje unikalny identyfikator, identyfikator rejestracji, oryginalny ładunek, liczbę prób (ograniczoną do maksimum z polityki), komunikat ostatniego błędu, ostatni status HTTP (null przy niepowodzeniu transportu lub blokadzie SSRF) oraz znacznik czasu niepowodzenia. - Kolejka dead-letter jest w pamięci i ograniczona do czasu życia procesu.
markReplayed()tworzy oznaczoną kopię; nie wysyła ponownie, a kolejka zachowuje oryginalny wpis.
Przypadki brzegowe i tryby awarii
Dział zatytułowany „Przypadki brzegowe i tryby awarii”- Pusta lista zdarzeń. Rejestracja otrzymuje każdy typ zdarzenia. Zawęź listę jawnie, gdy odbiorca nie może widzieć wszystkich zdarzeń.
- Terminalne 4xx a niepowodzenie transportu. Odrzucenie 4xx zapisuje wypełniony
lastHttpStatus; niepowodzenie połączenia zapisuje null. Wykorzystaj null, aby odróżnić odrzucenie przez odbiorcę od niepowodzenia transportu. - Cel zablokowany przez SSRF. Rejestracja wskazująca na adres HTTP, prywatny, loopback lub metadanych trafia do dead-letter przy pierwszej próbie z błędem
Blocked SSRF destination:i statusem null. Nie jest wykonywane żadne żądanie wychodzące. Popraw adres URL i zarejestruj ponownie. - Starsi odbiorcy po aktualizacji. Odbiorca wciąż weryfikujący HMAC z samej treści sprzed 3.1.0 kończy fail-closed wobec dostarczeń z 3.1.0. Zmigruj odbiorcę na ciąg bazowy
{timestamp}.{body}i wykorzystujX-NextPDF-Timestamp. - Niekodowalne dane zdarzenia.
toJson()orazsign()zgłaszająJsonException, który propaguje pozadeliver()idispatch()przed wykonaniem jakiejkolwiek próby. - Synchroniczne blokowanie.
deliver()usypia w miejscu między próbami. Skumulowane wycofanie osiąga 15 s przy polityce domyślnej i około 17 minut przy polityce aggressive. Rozdzielaj z workera kolejki, gdy opóźnienie odbiorcy jest niezaufane. - Ograniczenie liczby prób. Zapisana liczba prób nigdy nie przekracza maksimum z polityki, mimo że wewnętrzny licznik pętli przekracza je przy wyczerpaniu.
- Wzrost kolejki i trwałość. Kolejka dead-letter rośnie bez ograniczeń w obrębie procesu i znika po restarcie. Wyeksportuj wpisy przez
deadLetters()i utrwal je zewnętrznie przed wywołaniemclearDeadLetters(), gdy wymagane jest trwałe odtwarzanie. - Odtwarzanie jest sterowane przez operatora. Ponowne dostarczenie oznacza ponowne wywołanie
deliver()z ładunkiem wpisu;markReplayed()jedynie zapisuje ten fakt na kopii. - Pozostałość DNS-rebinding. Adres URL jest ponownie walidowany przy każdej próbie, co zawęża, ale nie zamyka okna rebindingu: abstrakcja PSR-18 nie może przypiąć połączenia do zwalidowanego IP. Dodaj kontrolę ruchu wychodzącego na warstwie sieci tam, gdzie ta pozostałość ma znaczenie.
- Obsługa sekretu. Sekret rejestracji to poświadczenie. HMAC uwierzytelnia wyłącznie integralność i pochodzenie — nie zapewnia poufności. Nie umieszczaj w ładunku zdarzenia danych, których odbiorca nie może widzieć.
Zachowanie w trybie FIPS
Dział zatytułowany „Zachowanie w trybie FIPS”Podpisywanie ładunku to HMAC-SHA256 poprzez hash_hmac() w PHP, więc opiera się na dostawcy kryptografii hosta. W kompilacji ograniczonej do FIPS niezatwierdzony prymityw kończy niepowodzeniem w granicy kryptograficznej, zamiast obniżać poziom. Warstwa webhooka nie dodaje własnej polityki kryptograficznej.
Zgodność
Dział zatytułowany „Zgodność”- Uwierzytelnianie ładunku implementuje HMAC, kod uwierzytelniania wiadomości z kluczowanym skrótem z FIPS PUB 198-1 §1, instancjonowany z SHA-256.
- Ochrona przed powtórzeniem jest zgodna z wytycznymi OWASP Cheat Sheet Series dotyczącymi bezpieczeństwa webhooków: znacznik czasu zdarzenia podróżuje w dedykowanym nagłówku i jest wprowadzany do obliczania podpisu, więc zmanipulowany znacznik czasu nie przechodzi weryfikacji.
- Znaczniki czasu w treści używają rozszerzonego formatu daty i czasu RFC 3339. Zadeklarowane w kodzie: RFC 3339 nie zostało pobrane z korpusu RAG dla tej strony.
- Są to stwierdzenia o możliwościach oparte na źródle produktu oraz cytowanych klauzulach. NextPDF nie składa żadnego oświadczenia o zgodności ani certyfikacji dla tej powierzchni.
Uwagi deweloperskie
Dział zatytułowany „Uwagi deweloperskie”- Wszystkie klasy deklarują
strict_types=1i sąfinal;WebhookRegistration,WebhookPayload,WebhookRetryPolicyorazDeadLetterEntrysąfinal readonlyz promowanymi właściwościami publicznymi. - Moduł nosi adnotację
@sinceo wartości2.2.0; schemat podpisu związany ze znacznikiem czasu to udokumentowana zmiana łamiąca w 3.1.0. - Silnik dostarczania przyjmuje abstrakcje PSR-18/PSR-17, więc atrapa klienta HTTP przechodzi pełną ścieżkę wysyłania, ponawiania i dead-letter w trybie offline. Logger domyślnie ma wartość null; wstrzyknij logger PSR-3 w produkcji, w przeciwnym razie niepowodzenia ujawniają się wyłącznie przez wartości zwracane.
- Implementacje odbiorcy powinny używać
hash_equals()do porównania podpisu i wymuszać okno świeżości naX-NextPDF-Timestamp. - Zalecane testy brzegowe: rejestracja z niezgodnym najemcą, rozgłoszenie z pustą listą zdarzeń, terminalne 4xx, wyczerpanie ponowień, adres URL zablokowany przez SSRF, odrzucenie podpisu ze zmanipulowanym znacznikiem czasu wobec ustalonego wektora oraz ograniczenie liczby prób w dead-letter.
Granica publikacji
Dział zatytułowany „Granica publikacji”Ta strona dokumentuje wyłącznie zewnętrznie obserwowalne zachowanie oraz wspieraną publiczną powierzchnię API. Wewnętrzne ścieżki przestrzeni nazw, klasy pomocnicze, tabele mechanizmów, nazwy plików runbooków oraz prefiksy zgłoszeń są poza zakresem.
Zobacz też
Dział zatytułowany „Zobacz też”- Webhook — NextPDF Enterprise — strona możliwości: przepływ pracy, konfiguracja oraz opracowane przykłady rejestracji.
- SaaS — szczegółowa referencja — tożsamość najemcy, klucze API oraz limity; źródło
TenantContext. - Metering — szczegółowa referencja — rozgłoszenie pomiaru użycia z tą samą dyscypliną dostarczania PSR-18.