Przejdź do głównej zawartości
getnextpdf.com

Enterprise edycja

Webhook — szczegółowa referencja

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.

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.

SymbolParametryZachowanie domyślneZwracaZgłasza lub kończy się niepowodzeniemUwagi
WebhookManager::__constructWebhookDelivery $delivery, ?LoggerInterface $logger = nullTworzy menedżera z pustym indeksem rejestracji w pamięciNowy WebhookManagerNie zgłaszaRejestracje są indeksowane dla danego najemcy
WebhookManager::registerTenantContext $tenant, WebhookRegistration $registrationDołącza rejestrację do indeksu wywołującego najemcyvoidInvalidArgumentException, gdy najemca rejestracji nie odpowiada najemcy z kontekstuRejestracja międzynajemcza jest odrzucana przed zapisem
WebhookManager::unregisterTenantContext $tenant, string $registrationIdZastępuje pasującą rejestrację dezaktywowaną kopiąboolNie zgłasza; zwraca false, gdy identyfikator nie zostanie znalezionyMiękka dezaktywacja; historia jest zachowana
WebhookManager::activeRegistrationsTenantContext $tenantFiltruje rejestracje najemcy do aktywnychlist<WebhookRegistration>Nie zgłaszaWidoczne są wyłącznie rejestracje wywołującego najemcy
WebhookManager::dispatchTenantContext $tenant, JobEvent $eventDostarcza zdarzenie do każdej aktywnej rejestracji subskrybującej dany typ zdarzeniaint (udane dostarczenia)Propaguje JsonException, gdy dane zdarzenia nie dają się zakodować do JSON; niepowodzenia dostarczania nie zgłaszają wyjątkuDla każdego dostarczenia do rejestracji generowany jest nowy 32-znakowy szesnastkowy identyfikator dostarczenia
WebhookRegistration::__constructstring $id, string $tenantId, string $url, array $events, string $secret, bool $active = true, ?string $description = nullPrzechowuje przekazane wartości dosłownieNowy WebhookRegistrationBrak zadeklarowanego @throws; PHP zgłasza TypeError przy niezgodnych typach argumentów w trybie strict_typesfinal readonly; puste $events oznacza subskrypcję wszystkiego
WebhookRegistration::subscribesToJobEventType $eventTypetrue, gdy $events jest puste lub zawiera dany typboolNie zgłaszaŚcisłe porównanie tożsamości
WebhookRegistration::deactivateZwraca nieaktywną kopięselfNie zgłaszaOryginalna instancja pozostaje niezmieniona
WebhookPayload::fromJobEventJobEvent $event, string $tenantId, string $deliveryIdKopiuje ze zdarzenia identyfikator zadania, typ zdarzenia, dane oraz znacznik czasuselfNie zgłaszaFabryka statyczna używana przez dispatch
WebhookPayload::toJsonSerializuje sześciopolową treść z nieucieczkowanymi ukośnikaminon-empty-stringJsonException, gdy dane zdarzenia nie dają się zakodować do JSONJSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES
WebhookPayload::toArrayZwraca treść jako tablicę asocjacyjnąarray<string, mixed>Nie zgłaszaZnacznik czasu w rozszerzonym formacie RFC 3339
WebhookPayload::signedTimestampCzas zdarzenia w sekundach uniksowych, ograniczony do zera lub więcejint<0, max>Nie zgłaszaEmitowany jako X-NextPDF-Timestamp i związany z MAC
WebhookPayload::signstring $secretHMAC-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::__constructClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, WebhookRetryPolicy $retryPolicy = new WebhookRetryPolicy(), ?LoggerInterface $logger = nullSilnik dostarczania PSR-18/PSR-17 z pustą kolejką dead-letterNowy WebhookDeliveryNie zgłaszaPolityka domyślna: 5 prób, baza 1 s, ograniczenie 300 s
WebhookDelivery::deliverWebhookRegistration $registration, WebhookPayload $payloadWysyła metodą POST podpisany ładunek z walidacją ruchu wychodzącego SSRF przy każdej próbie oraz wykładniczym wycofaniemboolJsonException przed pierwszą próbą, gdy treść nie daje się zakodować; poza tym nie zgłasza — false oznacza, że ładunek trafił do kolejki dead-lettertrue wyłącznie przy odpowiedzi 2xx
WebhookDelivery::deadLettersZwraca wszystkie zapisane wpisylist<DeadLetterEntry>Nie zgłaszaW pamięci, w zakresie procesu
WebhookDelivery::clearDeadLettersOpróżnia kolejkę dead-lettervoidNie zgłaszaNieodwracalne; najpierw wyeksportuj wpisy, jeśli wymagane jest odtwarzanie
WebhookRetryPolicy::__constructint $maxRetries = 5, int $baseDelaySeconds = 1, int $maxDelaySeconds = 300Przechowuje wartości politykiNowy WebhookRetryPolicyBrak zadeklarowanego @throws; parametry udokumentowane jako positive-int$maxRetries liczy łączną liczbę prób
WebhookRetryPolicy::delayForAttemptint $attemptbaseDelaySeconds × 2^(attempt − 1), ograniczone do maxDelaySecondspositive-intNie zgłaszaNumery prób liczone od 1
WebhookRetryPolicy::shouldRetryint $currentAttempttrue, dopóki bieżąca próba jest poniżej maksimumboolNie zgłaszaOczekiwanie jest pomijane po ostatniej próbie
WebhookRetryPolicy::default5 prób, baza 1 s, ograniczenie 300 sselfNie zgłaszaFabryka statyczna; domyślne dla produkcji
WebhookRetryPolicy::aggressive10 prób, baza 2 s, ograniczenie 600 sselfNie zgłaszaFabryka statyczna dla krytycznych punktów końcowych
DeadLetterEntry::__constructstring $id, string $registrationId, WebhookPayload $payload, int $attempts, string $lastError, ?int $lastHttpStatus, DateTimeImmutable $failedAt, bool $replayed = falsePrzechowuje rekord niepowodzenia dosłownieNowy DeadLetterEntryBrak zadeklarowanego @throws; TypeError w trybie strict_typesfinal readonly; null $lastHttpStatus oznacza niepowodzenie transportu
DeadLetterEntry::markReplayedZwraca kopię z replayed = trueselfNie zgłaszaTen 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): int
public 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(): self
public 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): string
public 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(): void
public 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(): self
public 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(): self
  • 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-Id oraz X-NextPDF-Event.
  • Pola treści JSON to delivery_id, job_id, event_type, data, timestamp (rozszerzony RFC 3339) oraz tenant_id, serializowane z nieucieczkowanymi ukośnikami. Wartości typu zdarzenia pochodzą z JobEventType w nextpdf/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-Timestamp jest 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-Timestamp T; odrzuć, gdy T wykracza poza akceptowalne okno świeżości (na przykład 300 s); przelicz ponownie hash_hmac('sha256', T . '.' . rawBody, secret) nad surowymi otrzymanymi bajtami; porównaj w czasie stałym z wartością nagłówka po usunięciu prefiksu sha256=.
  • 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łędem Blocked 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 do maxDelaySeconds. Oczekiwanie jest pomijane po ostatniej próbie.
  • Gdy żadna próba się nie powiedzie, DeadLetterEntry zapisuje 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.
  • 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 wykorzystuj X-NextPDF-Timestamp.
  • Niekodowalne dane zdarzenia. toJson() oraz sign() zgłaszają JsonException, który propaguje poza deliver() i dispatch() 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łaniem clearDeadLetters(), 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ć.

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.

  • 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.
  • Wszystkie klasy deklarują strict_types=1 i są final; WebhookRegistration, WebhookPayload, WebhookRetryPolicy oraz DeadLetterEntryfinal readonly z promowanymi właściwościami publicznymi.
  • Moduł nosi adnotację @since o wartości 2.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 na X-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.

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.