Ga naar inhoud
getnextpdf.com

Enterprise editie

Webhook — Diepe referentie

De namespace NextPDF\Enterprise\Webhook levert tenant-gescopete webhook-aflevering voor taakgebeurtenissen. Het publieke oppervlak bestaat uit zes symbolen: WebhookManager, WebhookRegistration, WebhookPayload, WebhookDelivery, WebhookRetryPolicy en DeadLetterEntry. De manager registreert endpoints per tenant en verzendt taakgebeurtenissen naar de abonnerende registraties. De delivery engine POST een met HMAC-SHA256 ondertekende JSON-payload, valideert elke bestemming tegen de Core SSRF-egress-gate, probeert opnieuw met exponentiële backoff en registreert permanente fouten in een in-memory dead-letter-wachtrij. Vanaf 3.1.0 bindt de handtekening de header X-NextPDF-Timestamp in de MAC-basisstring, zodat ontvangers versheid en integriteit samen verifiëren. Voor de gids op workflowniveau, zie Webhook.

Deze mogelijkheid wordt geleverd in NextPDF Enterprise (nextpdf/enterprise) en activeert met een licentie-envelop op Enterprise-niveau. Een deployment zonder dat recht laadt de klassen van de mogelijkheid niet. Vergelijk edities en vraag een licentie aan.

Het webhook-oppervlak is een basismogelijkheid van Enterprise, beschikbaar zodra het Enterprise-pakket is geïnstalleerd; er is geen aparte vlag per functie. NextPDF Core (Apache-2.0) en NextPDF Pro hebben geen webhook-registratie- of afleveringsoppervlak; de manager, registratie, payload, delivery engine, retry policy en dead-letter-vermelding worden alleen geleverd in nextpdf/enterprise.

SymboolParametersStandaardgedragRetourneertGooit of faalt metOpmerkingen
WebhookManager::__constructWebhookDelivery $delivery, ?LoggerInterface $logger = nullMaakt een manager met een lege in-memory registratie-indexNieuwe WebhookManagerGooit nietRegistraties worden per tenant geïndexeerd
WebhookManager::registerTenantContext $tenant, WebhookRegistration $registrationVoegt de registratie toe aan de index van de aanroepende tenantvoidInvalidArgumentException wanneer de tenant van de registratie niet overeenkomt met de tenant van de contextCross-tenant-registratie wordt afgewezen vóór opslag
WebhookManager::unregisterTenantContext $tenant, string $registrationIdVervangt de overeenkomende registratie door een gedeactiveerde kopieboolGooit niet; retourneert false wanneer de id niet wordt gevondenSoft deactivate; historie blijft behouden
WebhookManager::activeRegistrationsTenantContext $tenantFiltert de registraties van de tenant op de actievelist<WebhookRegistration>Gooit nietAlleen de registraties van de aanroepende tenant zijn zichtbaar
WebhookManager::dispatchTenantContext $tenant, JobEvent $eventLevert de gebeurtenis af aan elke actieve registratie die zich op het gebeurtenistype abonneertint (geslaagde afleveringen)Propageert JsonException wanneer gebeurtenisgegevens niet JSON-encodeerbaar zijn; afleveringsfouten gooien nietEr wordt een verse 32-hex afleverings-id gegenereerd per registratieaflevering
WebhookRegistration::__constructstring $id, string $tenantId, string $url, array $events, string $secret, bool $active = true, ?string $description = nullSlaat de aangeleverde waarden letterlijk opNieuwe WebhookRegistrationGeen gedeclareerde @throws; PHP werpt TypeError bij niet-overeenkomende argumenttypes onder strict_typesfinal readonly; lege $events betekent abonneer-op-alles
WebhookRegistration::subscribesToJobEventType $eventTypetrue wanneer $events leeg is of het type bevatboolGooit nietStrikte identiteitsvergelijking
WebhookRegistration::deactivateRetourneert een inactieve kopieselfGooit nietDe originele instantie blijft ongewijzigd
WebhookPayload::fromJobEventJobEvent $event, string $tenantId, string $deliveryIdKopieert taak-id, gebeurtenistype, gegevens en tijdstempel uit de gebeurtenisselfGooit nietStatische factory gebruikt door dispatch
WebhookPayload::toJsonSerialiseert de body met zes velden met niet-geëscapete slashesnon-empty-stringJsonException wanneer gebeurtenisgegevens niet JSON-encodeerbaar zijnJSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES
WebhookPayload::toArrayRetourneert de body als een associatieve arrayarray<string, mixed>Gooit nietTijdstempel geformatteerd als RFC 3339 extended
WebhookPayload::signedTimestampGebeurtenistijd in Unix-seconden, begrensd op nul of hogerint<0, max>Gooit nietUitgezonden als X-NextPDF-Timestamp en gebonden in de MAC
WebhookPayload::signstring $secretHMAC-SHA256 over de basisstring {signedTimestamp}.{jsonBody}non-empty-string (hex)JsonException via toJson() wanneer de body niet encodeerbaar isBindt de tijdstempel-header cryptografisch aan de body
WebhookDelivery::__constructClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, WebhookRetryPolicy $retryPolicy = new WebhookRetryPolicy(), ?LoggerInterface $logger = nullPSR-18/PSR-17 delivery engine met een lege dead-letter-wachtrijNieuwe WebhookDeliveryGooit nietStandaardbeleid: 5 pogingen, 1 s basis, 300 s cap
WebhookDelivery::deliverWebhookRegistration $registration, WebhookPayload $payloadPOST de ondertekende payload met SSRF-egress-validatie per poging en exponentiële backoffboolJsonException vóór de eerste poging wanneer de body niet encodeerbaar is; anders gooit het niet — false betekent dat de payload naar de dead-letter-wachtrij is geleidtrue alleen bij een 2xx-respons
WebhookDelivery::deadLettersRetourneert alle geregistreerde vermeldingenlist<DeadLetterEntry>Gooit nietIn-memory, proces-gescoped
WebhookDelivery::clearDeadLettersLeegt de dead-letter-wachtrijvoidGooit nietOnomkeerbaar; exporteer de vermeldingen eerst als replay vereist is
WebhookRetryPolicy::__constructint $maxRetries = 5, int $baseDelaySeconds = 1, int $maxDelaySeconds = 300Slaat de beleidswaarden opNieuwe WebhookRetryPolicyGeen gedeclareerde @throws; parameters zijn gedocumenteerd als positive-int$maxRetries telt het totale aantal pogingen
WebhookRetryPolicy::delayForAttemptint $attemptbaseDelaySeconds × 2^(attempt − 1), begrensd op maxDelaySecondspositive-intGooit nietPogingnummers zijn 1-gebaseerd
WebhookRetryPolicy::shouldRetryint $currentAttempttrue zolang de huidige poging onder het maximum ligtboolGooit nietDe wachttijd wordt na de laatste poging overgeslagen
WebhookRetryPolicy::default5 pogingen, 1 s basis, 300 s capselfGooit nietStatische factory; productiestandaard
WebhookRetryPolicy::aggressive10 pogingen, 2 s basis, 600 s capselfGooit nietStatische factory voor kritieke endpoints
DeadLetterEntry::__constructstring $id, string $registrationId, WebhookPayload $payload, int $attempts, string $lastError, ?int $lastHttpStatus, DateTimeImmutable $failedAt, bool $replayed = falseSlaat het faalrecord letterlijk opNieuwe DeadLetterEntryGeen gedeclareerde @throws; TypeError onder strict_typesfinal readonly; null $lastHttpStatus betekent transportfout
DeadLetterEntry::markReplayedRetourneert een kopie met replayed = trueselfGooit nietZelfde id; de originele vermelding blijft ongewijzigd
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
  • Registraties worden per tenant geïndexeerd. register() wijst een registratie af waarvan de tenant-identificator niet overeenkomt met de aanroepende context. unregister() is een soft deactivate: de registratie wordt vervangen door een inactieve kopie, die de historie behoudt en haar uitsluit van toekomstige dispatch.
  • dispatch() itereert alleen over de actieve registraties van de aanroepende tenant die zich op het verzonden gebeurtenistype abonneren. Een lege geabonneerde-gebeurtenissenlijst betekent abonneer-op-alles. De retourwaarde telt de geslaagde afleveringen.
  • Elke aflevering is een HTTP POST met een JSON-body en vijf headers: Content-Type: application/json, X-NextPDF-Signature (sha256=<hex>), X-NextPDF-Timestamp (unix-seconden), X-NextPDF-Delivery-Id en X-NextPDF-Event.
  • De velden van de JSON-body zijn delivery_id, job_id, event_type, data, timestamp (RFC 3339 extended) en tenant_id, geserialiseerd met niet-geëscapete slashes. Gebeurtenistype-waarden komen uit JobEventType in nextpdf/core: progress, completed, failed, cancelled.
  • Handtekeningschema (gewijzigd in 3.1.0, breaking). De HMAC-SHA256-basisstring is {signedTimestamp}.{jsonBody}, gekeyd met het registratiegeheim — niet de body alleen. De waarde X-NextPDF-Timestamp is de tijdstempelcomponent van de MAC, dus een gemanipuleerde of hergebruikte tijdstempel-header maakt de handtekening ongeldig.
  • Verificatie door de ontvanger: lees de header X-NextPDF-Timestamp T; wijs af wanneer T buiten een aanvaardbaar versheidsvenster valt (bijvoorbeeld 300 s); herbereken hash_hmac('sha256', T . '.' . rawBody, secret) over de ruwe ontvangen bytes; vergelijk in constante tijd met de header-waarde na het verwijderen van de prefix sha256=.
  • De body, handtekening en afleverings-id worden eenmaal per aflevering berekend en blijven constant over de retry-pogingen heen.
  • SSRF-egress-gate. Vóór elke poging passeert de bestemmings-URL de Core UrlValidator::validateExternalUrl()-gate: alleen HTTPS-schema; loopback-, private, gereserveerde, carrier-grade-NAT-, cloud-metadata- en in IPv4 ingebedde IPv6-transitiebereiken worden geblokkeerd; hostnamen worden via DNS geresolveerd (A en AAAA) en niet-resolveerbare hosts worden fail-closed afgewezen. Een geblokkeerde URL wordt nooit verzonden: de pogingslus breekt af en de payload gaat rechtstreeks naar de dead-letter-wachtrij met een Blocked SSRF destination: als laatste fout en een null HTTP-status.
  • Uitkomstclassificatie per poging: 2xx is succes en retourneert onmiddellijk; een 4xx anders dan 429 is terminaal en gaat rechtstreeks naar dead-letter; elke andere uitkomst — 3xx, 429, 5xx of een transportuitzondering — is retryable tot het totale aantal pogingen van het beleid.
  • Backoff is exponentieel: de wachttijd vóór de volgende poging is baseDelaySeconds × 2^(attempt − 1), begrensd op maxDelaySeconds. De wachttijd wordt na de laatste poging overgeslagen.
  • Wanneer geen enkele poging slaagt, registreert een DeadLetterEntry een unieke id, de registratie-id, de originele payload, het aantal pogingen (begrensd op het beleidsmaximum), het laatste foutbericht, de laatste HTTP-status (null bij transportfout of SSRF-blokkade) en het faaltijdstempel.
  • De dead-letter-wachtrij is in-memory en gescoped op de levensduur van het proces. markReplayed() produceert een gemarkeerde kopie; het verstuurt niet opnieuw, en de wachtrij behoudt de originele vermelding.
  • Lege gebeurtenissenlijst. De registratie ontvangt elk gebeurtenistype. Scope de lijst expliciet wanneer de ontvanger niet alle gebeurtenissen mag zien.
  • Terminale 4xx versus transportfout. Een 4xx-afwijzing registreert een gevulde lastHttpStatus; een verbindingsfout registreert null. Gebruik de null om ontvangerafwijzing van transportfout te onderscheiden.
  • SSRF-geblokkeerde bestemming. Een registratie die wijst naar een HTTP-, private, loopback- of metadata-adres komt bij de eerste poging in dead-letter terecht met een Blocked SSRF destination:-fout en null-status. Er wordt geen uitgaand verzoek gedaan. Corrigeer de URL en registreer opnieuw.
  • Legacy-ontvangers na upgrade. Een ontvanger die nog de body-only HMAC van vóór 3.1.0 verifieert, faalt closed tegen 3.1.0-afleveringen. Migreer de ontvanger naar de basisstring {timestamp}.{body} en consumeer X-NextPDF-Timestamp.
  • Niet-encodeerbare gebeurtenisgegevens. toJson() en sign() gooien JsonException, die uit deliver() en dispatch() propageert voordat er een poging wordt gedaan.
  • Synchroon blokkeren. deliver() slaapt inline tussen pogingen. Cumulatieve backoff bereikt 15 s onder het standaardbeleid en ongeveer 17 minuten onder het aggressive-beleid. Verzend vanuit een queue-worker wanneer de latentie van de ontvanger niet vertrouwd is.
  • Begrenzing aantal pogingen. Het geregistreerde aantal pogingen overschrijdt nooit het beleidsmaximum, ook al gaat de interne lusteller daar bij uitputting voorbij.
  • Wachtrijgroei en duurzaamheid. De dead-letter-wachtrij groeit onbegrensd binnen het proces en verdwijnt bij een herstart. Exporteer de vermeldingen via deadLetters() en persisteer ze extern voordat je clearDeadLetters() aanroept wanneer duurzame replay vereist is.
  • Replay is operatorgedreven. Heraflevering betekent deliver() opnieuw aanroepen met de payload van de vermelding; markReplayed() legt het feit alleen vast op een kopie.
  • DNS-rebinding-restrisico. De URL wordt bij elke poging opnieuw gevalideerd, wat het rebinding-venster verkleint maar niet sluit: de PSR-18-abstractie kan de verbinding niet vastpinnen op het gevalideerde IP. Voeg egress-controles op netwerkniveau toe waar dit restrisico van belang is.
  • Omgaan met het geheim. Het registratiegeheim is een referentie. De HMAC authenticeert alleen integriteit en herkomst — het is geen vertrouwelijkheid. Plaats geen gegevens in de gebeurtenis-payload die de ontvanger niet mag zien.

Payload-ondertekening is HMAC-SHA256 via PHP’s hash_hmac(), dus het steunt op de host-cryptoprovider. In een FIPS-beperkte build faalt een niet-goedgekeurde primitieve op de cryptografische grens in plaats van te degraderen. De webhook-laag voegt geen eigen cryptografisch beleid toe.

  • Payload-authenticatie implementeert HMAC, de keyed-hash message authentication code van FIPS PUB 198-1 §1, geïnstantieerd met SHA-256.
  • Replaybescherming volgt de webhook-securityrichtlijnen van de OWASP Cheat Sheet Series: het gebeurtenistijdstempel reist in een dedicated header en wordt geseed in de berekening van de handtekening, zodat een gemanipuleerd tijdstempel de verificatie doet mislukken.
  • Body-tijdstempels gebruiken het RFC 3339 extended date-time-formaat. Code-declared: RFC 3339 is niet opgehaald uit het RAG-corpus voor deze pagina.
  • Dit zijn capability-uitspraken gegrond in de productbron en de geciteerde clausules. NextPDF doet geen conformiteits- of certificeringsclaim voor dit oppervlak.
  • Alle klassen declareren strict_types=1 en zijn final; WebhookRegistration, WebhookPayload, WebhookRetryPolicy en DeadLetterEntry zijn final readonly met promoted public properties.
  • De module draagt een @since-annotatie van 2.2.0; het aan een tijdstempel gebonden handtekeningschema is een gedocumenteerde breaking change in 3.1.0.
  • De delivery engine neemt PSR-18/PSR-17-abstracties, dus een mock-HTTP-client oefent het volledige verzend-, retry- en dead-letter-pad offline uit. De logger is standaard null; injecteer een PSR-3-logger in productie, anders komen fouten alleen via retourwaarden naar boven.
  • Ontvangerimplementaties zouden hash_equals() moeten gebruiken voor de handtekeningvergelijking en een versheidsvenster op X-NextPDF-Timestamp moeten afdwingen.
  • Aanbevolen grenstests: tenant-mismatch-registratie, fan-out met lege gebeurtenissenlijst, terminale 4xx, uitputting van retries, SSRF-geblokkeerde URL, afwijzing van een handtekening met gemanipuleerd tijdstempel tegen een vast vector, en begrenzing van het aantal pogingen bij dead-letter.

Deze pagina documenteert alleen extern waarneembaar gedrag en het ondersteunde publieke API-oppervlak. Interne namespace-paden, helperklassen, mechanismetabellen, runbook-bestandsnamen en ticketprefixen vallen buiten de scope.