コンテンツにスキップ
getnextpdf.com

Enterprise エディション

Webhook — 詳細リファレンス

NextPDF\Enterprise\Webhook 名前空間は、ジョブイベント向けのテナントスコープの webhook 配信を提供します。パブリックサーフェスは 6 つのシンボルです。WebhookManagerWebhookRegistrationWebhookPayloadWebhookDeliveryWebhookRetryPolicyDeadLetterEntry。マネージャーはテナントごとにエンドポイントを登録し、ジョブイベントをサブスクライブしている登録へディスパッチします。配信エンジンは HMAC-SHA256 署名済みの JSON ペイロードを POST し、すべての宛先を Core の SSRF イグレスゲートに対して検証し、指数バックオフで再試行し、恒久的な失敗をインメモリのデッドレターキューに記録します。3.1.0 以降、署名は X-NextPDF-Timestamp ヘッダーを MAC のベース文字列に束縛するため、受信側は鮮度と完全性を同時に検証できます。ワークフローレベルのガイドについては、Webhook を参照してください。

このケイパビリティは NextPDF Enterprisenextpdf/enterprise)に同梱され、Enterprise ティアのライセンスエンベロープで有効化されます。その資格を持たないデプロイでは、このケイパビリティのクラスはロードされません。エディションを比較してライセンスを取得

Webhook サーフェスは Enterprise の基本ケイパビリティであり、Enterprise パッケージがインストールされれば利用できます。機能ごとに独立したフラグはありません。NextPDF Core(Apache-2.0)と NextPDF Pro には webhook の登録または配信サーフェスがありません。マネージャー、登録、ペイロード、配信エンジン、再試行ポリシー、デッドレターエントリは nextpdf/enterprise にのみ同梱されています。

シンボルパラメーターデフォルトの挙動戻り値スローまたは失敗備考
WebhookManager::__constructWebhookDelivery $delivery?LoggerInterface $logger = null空のインメモリ登録インデックスを持つマネージャーを作成新しい WebhookManagerスローしない登録はテナントごとに索引付け
WebhookManager::registerTenantContext $tenantWebhookRegistration $registration呼び出し元テナントのインデックスに登録を追加void登録テナントがコンテキストテナントと一致しない場合に InvalidArgumentExceptionクロステナント登録は保存前に拒否
WebhookManager::unregisterTenantContext $tenantstring $registrationId一致する登録を無効化したコピーに置換boolスローしない。id が見つからない場合は false を返すソフト無効化。履歴は保持
WebhookManager::activeRegistrationsTenantContext $tenantテナントの登録をアクティブなものにフィルタlist<WebhookRegistration>スローしない呼び出し元テナントの登録のみ可視
WebhookManager::dispatchTenantContext $tenantJobEvent $eventイベントタイプをサブスクライブしているすべてのアクティブな登録へイベントを配信int(成功した配信数)イベントデータが JSON エンコード不能な場合は JsonException を伝播。配信失敗はスローしない登録ごとの配信で新しい 32 桁 hex 配信 id を生成
WebhookRegistration::__constructstring $idstring $tenantIdstring $urlarray $eventsstring $secretbool $active = true?string $description = null与えられた値をそのまま格納新しい WebhookRegistration@throws 宣言なし。strict_types 下で引数型不一致時に PHP が TypeError を送出final readonly。空の $events は全サブスクライブを意味する
WebhookRegistration::subscribesToJobEventType $eventType$events が空、またはそのタイプを含む場合に trueboolスローしない厳密な同一性比較
WebhookRegistration::deactivate非アクティブなコピーを返すselfスローしない元のインスタンスは不変
WebhookPayload::fromJobEventJobEvent $eventstring $tenantIdstring $deliveryIdイベントからジョブ id、イベントタイプ、データ、タイムスタンプをコピーselfスローしないdispatch が使う静的ファクトリ
WebhookPayload::toJson6 フィールドのボディをスラッシュ非エスケープでシリアライズnon-empty-stringイベントデータが JSON エンコード不能な場合は JsonExceptionJSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES
WebhookPayload::toArrayボディを連想配列として返すarray<string, mixed>スローしないタイムスタンプは RFC 3339 extended 形式
WebhookPayload::signedTimestampUnix 秒のイベント時刻、ゼロ以上にクランプint<0, max>スローしないX-NextPDF-Timestamp として発行され、MAC に束縛
WebhookPayload::signstring $secretベース文字列 {signedTimestamp}.{jsonBody} に対する HMAC-SHA256non-empty-string(hex)ボディがエンコード不能な場合、toJson() 経由で JsonExceptionタイムスタンプヘッダーをボディへ暗号的に束縛
WebhookDelivery::__constructClientInterface $httpClientRequestFactoryInterface $requestFactoryStreamFactoryInterface $streamFactoryWebhookRetryPolicy $retryPolicy = new WebhookRetryPolicy()?LoggerInterface $logger = null空のデッドレターキューを持つ PSR-18/PSR-17 配信エンジン新しい WebhookDeliveryスローしないデフォルトポリシー: 5 回試行、1s ベース、300s 上限
WebhookDelivery::deliverWebhookRegistration $registrationWebhookPayload $payload試行ごとの SSRF イグレス検証と指数バックオフで署名済みペイロードを POSTboolボディがエンコード不能な場合、初回試行前に JsonException。それ以外はスローしない — false はペイロードがデッドレターキューへ送られたことを意味するtrue は 2xx 応答時のみ
WebhookDelivery::deadLetters記録済みの全エントリを返すlist<DeadLetterEntry>スローしないインメモリ、プロセススコープ
WebhookDelivery::clearDeadLettersデッドレターキューを空にするvoidスローしない不可逆。リプレイが必要ならまずエントリをエクスポート
WebhookRetryPolicy::__constructint $maxRetries = 5int $baseDelaySeconds = 1int $maxDelaySeconds = 300ポリシー値を格納新しい WebhookRetryPolicy@throws 宣言なし。パラメーターは positive-int として文書化$maxRetries は総試行回数を数える
WebhookRetryPolicy::delayForAttemptint $attemptbaseDelaySeconds × 2^(attempt − 1)maxDelaySeconds で上限positive-intスローしない試行番号は 1 始まり
WebhookRetryPolicy::shouldRetryint $currentAttempt現在の試行が最大未満の間 trueboolスローしない最終試行後は待機をスキップ
WebhookRetryPolicy::default5 回試行、1s ベース、300s 上限selfスローしない静的ファクトリ。本番デフォルト
WebhookRetryPolicy::aggressive10 回試行、2s ベース、600s 上限selfスローしない重要エンドポイント向けの静的ファクトリ
DeadLetterEntry::__constructstring $idstring $registrationIdWebhookPayload $payloadint $attemptsstring $lastError?int $lastHttpStatusDateTimeImmutable $failedAtbool $replayed = false失敗レコードをそのまま格納新しい DeadLetterEntry@throws 宣言なし。strict_types 下で TypeErrorfinal readonly。null の $lastHttpStatus はトランスポート失敗を意味する
DeadLetterEntry::markReplayedreplayed = true のコピーを返すselfスローしない同じ id。元のエントリは不変
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
  • 登録はテナントごとに索引付けされます。register() は、テナント識別子が呼び出しコンテキストと一致しない登録を拒否します。unregister() はソフト無効化です。登録は非アクティブなコピーに置き換えられ、履歴を保持しつつ、今後のディスパッチからは除外されます。
  • dispatch() は、ディスパッチされたイベントタイプをサブスクライブしている、呼び出し元テナントのアクティブな登録だけを反復します。サブスクライブイベントリストが空の場合は、全サブスクライブを意味します。戻り値は成功した配信数を数えます。
  • 各配信は、JSON ボディと 5 つのヘッダーを持つ HTTP POST です。Content-Type: application/jsonX-NextPDF-Signaturesha256=<hex>)、X-NextPDF-Timestamp(unix 秒)、X-NextPDF-Delivery-IdX-NextPDF-Event
  • JSON ボディのフィールドは delivery_idjob_idevent_typedatatimestamp(RFC 3339 extended)、tenant_id で、スラッシュ非エスケープでシリアライズされます。イベントタイプの値は nextpdf/coreJobEventType に由来します。progresscompletedfailedcancelled
  • 署名スキーム(3.1.0 で変更、破壊的)。 HMAC-SHA256 のベース文字列はボディ単独ではなく {signedTimestamp}.{jsonBody} であり、登録シークレットをキーとします。X-NextPDF-Timestamp の値は MAC のタイムスタンプ成分であるため、改ざんまたはリプレイされたタイムスタンプヘッダーは署名を無効にします。
  • 受信側の検証: X-NextPDF-Timestamp ヘッダー T を読み取り、T が許容される鮮度ウィンドウ(例: 300s)の外にある場合は拒否します。受信した生バイト列に対して hash_hmac('sha256', T . '.' . rawBody, secret) を再計算し、sha256= プレフィックスを除去したヘッダー値と定数時間で比較します。
  • ボディ、署名、配信 id は配信ごとに一度計算され、再試行をまたいで一定に保たれます。
  • SSRF イグレスゲート。 すべての試行の前に、宛先 URL は Core の UrlValidator::validateExternalUrl() ゲートを通過します。HTTPS スキームのみ。ループバック、プライベート、予約、キャリアグレード NAT、クラウドメタデータ、IPv4 埋め込み IPv6 移行レンジはブロックされます。ホスト名は DNS 解決(A および AAAA)され、解決不能なホストはフェイルクローズで拒否されます。ブロックされた URL は決して送信されません。試行ループは中止され、ペイロードは Blocked SSRF destination: の last error と null の HTTP ステータスを伴って直接デッドレターキューへ送られます。
  • 試行ごとの結果分類: 2xx は成功で直ちに返ります。429 以外の 4xx は終端で直接デッドレターへ送られます。それ以外のすべての結果 — 3xx、429、5xx、またはトランスポート例外 — は、ポリシーの総試行回数まで再試行可能です。
  • バックオフは指数的です。次の試行前の待機は baseDelaySeconds × 2^(attempt − 1) で、maxDelaySeconds で上限が定められます。最終試行の後は待機がスキップされます。
  • どの試行も成功しなかった場合、DeadLetterEntry が一意の id、登録 id、元のペイロード、試行回数(ポリシー最大値にクランプ)、最後のエラーメッセージ、最後の HTTP ステータス(トランスポート失敗または SSRF ブロック時は null)、失敗タイムスタンプを記録します。
  • デッドレターキューはインメモリであり、プロセスのライフタイムにスコープされます。markReplayed() はフラグ付きのコピーを生成します。再送はせず、キューは元のエントリを保持します。
  • 空のイベントリスト。 登録はすべてのイベントタイプを受信します。受信側がすべてのイベントを見るべきでない場合は、リストを明示的にスコープしてください。
  • 終端の 4xx とトランスポート失敗。 4xx の拒否は値の入った lastHttpStatus を記録し、接続失敗は null を記録します。null を使って受信側の拒否とトランスポート失敗を区別してください。
  • SSRF ブロックされた宛先。 HTTP、プライベート、ループバック、またはメタデータアドレスを指す登録は、初回試行で Blocked SSRF destination: エラーと null ステータスを伴ってデッドレターされます。アウトバウンドリクエストは行われません。URL を修正して再登録してください。
  • アップグレード後のレガシー受信側。 3.1.0 以前のボディ単独 HMAC をまだ検証している受信側は、3.1.0 の配信に対してフェイルクローズします。受信側を {timestamp}.{body} ベース文字列へ移行し、X-NextPDF-Timestamp を消費してください。
  • エンコード不能なイベントデータ。 toJson()sign()JsonException をスローし、これはいずれの試行が行われる前に deliver()dispatch() の外へ伝播します。
  • 同期ブロッキング。 deliver() は試行の間にインラインでスリープします。累積バックオフは、デフォルトポリシー下で 15s、aggressive ポリシー下で約 17 分に達します。受信側のレイテンシが信頼できない場合は、キューワーカーからディスパッチしてください。
  • 試行回数のクランプ。 記録される試行回数は、内部ループカウンターが枯渇時にそれを超えて進んだ場合でも、ポリシー最大値を決して超えません。
  • キューの増大と耐久性。 デッドレターキューはプロセス内で無制限に増大し、再起動で消えます。耐久的なリプレイが必要な場合は、clearDeadLetters() を呼ぶ前に deadLetters() でエントリをエクスポートし、外部に永続化してください。
  • リプレイはオペレーター駆動。 再配信とは、エントリのペイロードで再び deliver() を呼ぶことを意味します。markReplayed() はコピー上に事実を記録するだけです。
  • DNS リバインディングの残余。 URL はすべての試行で再検証されますが、これはリバインディングウィンドウを狭めるものの閉じはしません。PSR-18 抽象は接続を検証済み IP にピン留めできません。この残余が問題になる箇所ではネットワークレイヤーのイグレス制御を追加してください。
  • シークレットの取り扱い。 登録シークレットは認証情報です。HMAC は完全性と起源のみを認証し、機密性ではありません。受信側が見るべきでないデータをイベントペイロードに含めないでください。

ペイロードの署名は PHP の hash_hmac() を通じた HMAC-SHA256 であるため、ホストの暗号プロバイダーに依存します。FIPS 制約付きのビルドでは、承認されていないプリミティブはダウングレードするのではなく暗号境界で失敗します。Webhook レイヤーは独自の暗号ポリシーを一切追加しません。

  • ペイロード認証は、FIPS PUB 198-1 §1 のキー付きハッシュ MAC である HMAC を実装し、SHA-256 でインスタンス化されています。
  • リプレイ保護は OWASP Cheat Sheet Series の webhook セキュリティガイダンスに従います。イベントタイムスタンプは専用ヘッダーで伝わり、署名計算にシードされるため、改ざんされたタイムスタンプは検証に失敗します。
  • ボディのタイムスタンプは RFC 3339 extended date-time 形式を使用します。コード宣言: RFC 3339 はこのページでは RAG コーパスから取得していません。
  • これらは製品ソースと引用クローズに基づくケイパビリティの記述です。NextPDF はこのサーフェスについて適合性または認証の主張を一切行いません。
  • すべてのクラスは strict_types=1 を宣言し final です。WebhookRegistrationWebhookPayloadWebhookRetryPolicyDeadLetterEntry は昇格されたパブリックプロパティを持つ final readonly です。
  • モジュールは 2.2.0@since アノテーションを持ちます。タイムスタンプ束縛の署名スキームは 3.1.0 で文書化された破壊的変更です。
  • 配信エンジンは PSR-18/PSR-17 抽象を取るため、モック HTTP クライアントで送信、再試行、デッドレターの全経路をオフラインで実行できます。ロガーはデフォルトで null です。本番では PSR-3 ロガーを注入してください。さもなければ失敗は戻り値でしか表面化しません。
  • 受信側の実装は、署名比較に hash_equals() を使用し、X-NextPDF-Timestamp に鮮度ウィンドウを強制すべきです。
  • 推奨される境界テスト: テナント不一致登録、空イベントリストのファンアウト、終端 4xx、再試行枯渇、SSRF ブロック URL、固定ベクトルに対するタイムスタンプ改ざん署名の拒否、デッドレター試行回数のクランプ。

このページは、外部から観測可能な挙動とサポートされるパブリック API サーフェスのみを記載します。内部名前空間パス、ヘルパークラス、メカニズム表、ランブックのファイル名、チケットプレフィックスは対象外です。