Bỏ qua để đến nội dung
getnextpdf.com

Enterprise phiên bản

Webhook — tài liệu tham chiếu chuyên sâu

Namespace NextPDF\Enterprise\Webhook cung cấp việc gửi webhook theo phạm vi thuê bao cho các sự kiện công việc. Bề mặt công khai gồm sáu ký hiệu: WebhookManager, WebhookRegistration, WebhookPayload, WebhookDelivery, WebhookRetryPolicy, và DeadLetterEntry. Manager đăng ký các endpoint theo từng thuê bao và gửi các sự kiện công việc tới những đăng ký có theo dõi. Engine gửi thực hiện POST một payload JSON đã ký HMAC-SHA256, kiểm tra mọi đích đến qua cổng chặn ra SSRF của Core, thử lại với backoff theo cấp số nhân, và ghi các thất bại vĩnh viễn vào một hàng đợi dead-letter trong bộ nhớ. Kể từ 3.1.0, chữ ký ràng buộc header X-NextPDF-Timestamp vào chuỗi cơ sở của MAC, nên bên nhận xác minh độ tươi mới và tính toàn vẹn cùng lúc. Để xem hướng dẫn ở mức quy trình, hãy xem Webhook.

Năng lực này đi kèm trong NextPDF Enterprise (nextpdf/enterprise) và kích hoạt bằng một license envelope bậc Enterprise. Một triển khai không có quyền đó sẽ không nạp các lớp của năng lực này. So sánh các phiên bản và lấy giấy phép.

Bề mặt webhook là một năng lực Enterprise cơ sở, sẵn dùng ngay khi gói Enterprise được cài đặt; không có cờ riêng theo từng tính năng. NextPDF Core (Apache-2.0) và NextPDF Pro không có bề mặt đăng ký hay gửi webhook nào; manager, đăng ký, payload, engine gửi, chính sách thử lại, và mục dead-letter chỉ đi kèm trong nextpdf/enterprise.

Ký hiệuTham sốHành vi mặc địnhTrả vềNém hoặc thất bại vớiGhi chú
WebhookManager::__constructWebhookDelivery $delivery, ?LoggerInterface $logger = nullTạo một manager với chỉ mục đăng ký trong bộ nhớ rỗngWebhookManager mớiKhông némCác đăng ký được lập chỉ mục theo từng thuê bao
WebhookManager::registerTenantContext $tenant, WebhookRegistration $registrationThêm đăng ký vào chỉ mục của thuê bao đang gọivoidInvalidArgumentException khi thuê bao của đăng ký không khớp thuê bao của ngữ cảnhĐăng ký chéo thuê bao bị từ chối trước khi lưu
WebhookManager::unregisterTenantContext $tenant, string $registrationIdThay thế đăng ký khớp bằng một bản sao đã vô hiệu hóaboolKhông ném; trả về false khi không tìm thấy idVô hiệu hóa mềm; lịch sử được giữ lại
WebhookManager::activeRegistrationsTenantContext $tenantLọc các đăng ký của thuê bao xuống những đăng ký đang hoạt độnglist<WebhookRegistration>Không némChỉ các đăng ký của thuê bao đang gọi mới hiển thị
WebhookManager::dispatchTenantContext $tenant, JobEvent $eventGửi sự kiện tới mọi đăng ký đang hoạt động có theo dõi loại sự kiện đóint (số lần gửi thành công)Truyền tiếp JsonException khi dữ liệu sự kiện không thể mã hóa JSON; các thất bại khi gửi không némMột id lần gửi 32-hex mới được sinh cho mỗi lần gửi tới đăng ký
WebhookRegistration::__constructstring $id, string $tenantId, string $url, array $events, string $secret, bool $active = true, ?string $description = nullLưu nguyên văn các giá trị được cung cấpWebhookRegistration mớiKhông khai báo @throws; PHP phát sinh TypeError khi kiểu đối số không khớp dưới strict_typesfinal readonly; $events rỗng nghĩa là theo dõi tất cả
WebhookRegistration::subscribesToJobEventType $eventTypetrue khi $events rỗng hoặc chứa loại đóboolKhông némSo sánh danh tính nghiêm ngặt
WebhookRegistration::deactivateTrả về một bản sao không hoạt độngselfKhông némThực thể gốc không thay đổi
WebhookPayload::fromJobEventJobEvent $event, string $tenantId, string $deliveryIdSao chép id công việc, loại sự kiện, dữ liệu, và dấu thời gian từ sự kiệnselfKhông némFactory tĩnh được dispatch dùng
WebhookPayload::toJsonTuần tự hóa thân sáu trường với các dấu gạch chéo không thoátnon-empty-stringJsonException khi dữ liệu sự kiện không thể mã hóa JSONJSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES
WebhookPayload::toArrayTrả về thân dưới dạng mảng kết hợparray<string, mixed>Không némDấu thời gian định dạng RFC 3339 mở rộng
WebhookPayload::signedTimestampThời gian sự kiện theo giây Unix, kẹp về không hoặc lớn hơnint<0, max>Không némPhát ra dưới dạng X-NextPDF-Timestamp và ràng buộc vào MAC
WebhookPayload::signstring $secretHMAC-SHA256 trên chuỗi cơ sở {signedTimestamp}.{jsonBody}non-empty-string (hex)JsonException qua toJson() khi thân không thể mã hóaRàng buộc header dấu thời gian vào thân bằng mật mã
WebhookDelivery::__constructClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, WebhookRetryPolicy $retryPolicy = new WebhookRetryPolicy(), ?LoggerInterface $logger = nullEngine gửi PSR-18/PSR-17 với hàng đợi dead-letter rỗngWebhookDelivery mớiKhông némChính sách mặc định: 5 lần thử, cơ sở 1 s, giới hạn 300 s
WebhookDelivery::deliverWebhookRegistration $registration, WebhookPayload $payloadPOST payload đã ký với việc kiểm tra chặn ra SSRF theo từng lần thử và backoff theo cấp số nhânboolJsonException trước lần thử đầu tiên khi thân không thể mã hóa; ngược lại không ném — false nghĩa là payload đã được định tuyến tới hàng đợi dead-lettertrue chỉ khi có phản hồi 2xx
WebhookDelivery::deadLettersTrả về mọi mục đã ghilist<DeadLetterEntry>Không némTrong bộ nhớ, phạm vi tiến trình
WebhookDelivery::clearDeadLettersLàm rỗng hàng đợi dead-lettervoidKhông némKhông thể hoàn tác; hãy xuất các mục trước nếu cần phát lại
WebhookRetryPolicy::__constructint $maxRetries = 5, int $baseDelaySeconds = 1, int $maxDelaySeconds = 300Lưu các giá trị của chính sáchWebhookRetryPolicy mớiKhông khai báo @throws; các tham số được ghi là positive-int$maxRetries đếm tổng số lần thử
WebhookRetryPolicy::delayForAttemptint $attemptbaseDelaySeconds × 2^(attempt − 1), giới hạn ở maxDelaySecondspositive-intKhông némSố thứ tự lần thử tính từ 1
WebhookRetryPolicy::shouldRetryint $currentAttempttrue khi lần thử hiện tại còn dưới mức tối đaboolKhông némPhần chờ được bỏ qua sau lần thử cuối cùng
WebhookRetryPolicy::default5 lần thử, cơ sở 1 s, giới hạn 300 sselfKhông némFactory tĩnh; mặc định cho sản phẩm
WebhookRetryPolicy::aggressive10 lần thử, cơ sở 2 s, giới hạn 600 sselfKhông némFactory tĩnh cho các endpoint quan trọng
DeadLetterEntry::__constructstring $id, string $registrationId, WebhookPayload $payload, int $attempts, string $lastError, ?int $lastHttpStatus, DateTimeImmutable $failedAt, bool $replayed = falseLưu nguyên văn bản ghi thất bạiDeadLetterEntry mớiKhông khai báo @throws; TypeError dưới strict_typesfinal readonly; $lastHttpStatus null nghĩa là thất bại truyền tải
DeadLetterEntry::markReplayedTrả về một bản sao với replayed = trueselfKhông némCùng id; mục gốc không thay đổi
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
  • Các đăng ký được lập chỉ mục theo từng thuê bao. register() từ chối một đăng ký có mã định danh thuê bao không khớp với ngữ cảnh đang gọi. unregister() là một thao tác vô hiệu hóa mềm: đăng ký được thay bằng một bản sao không hoạt động, giữ lại lịch sử trong khi loại nó khỏi các lần gửi tương lai.
  • dispatch() chỉ duyệt qua các đăng ký đang hoạt động của thuê bao đang gọi có theo dõi loại sự kiện được gửi. Một danh sách sự kiện theo dõi rỗng nghĩa là theo dõi tất cả. Giá trị trả về đếm số lần gửi thành công.
  • Mỗi lần gửi là một HTTP POST với một thân JSON và năm header: Content-Type: application/json, X-NextPDF-Signature (sha256=<hex>), X-NextPDF-Timestamp (giây unix), X-NextPDF-Delivery-Id, và X-NextPDF-Event.
  • Các trường của thân JSON là delivery_id, job_id, event_type, data, timestamp (RFC 3339 mở rộng), và tenant_id, được tuần tự hóa với các dấu gạch chéo không thoát. Các giá trị loại sự kiện đến từ JobEventType trong nextpdf/core: progress, completed, failed, cancelled.
  • Sơ đồ chữ ký (thay đổi trong 3.1.0, phá vỡ tương thích). Chuỗi cơ sở HMAC-SHA256 là {signedTimestamp}.{jsonBody}, được lập khóa bằng secret của đăng ký — không phải chỉ riêng thân. Giá trị X-NextPDF-Timestamp là thành phần dấu thời gian của MAC, nên một header dấu thời gian bị giả mạo hoặc phát lại sẽ làm chữ ký không hợp lệ.
  • Xác minh ở bên nhận: đọc header X-NextPDF-Timestamp T; từ chối khi T nằm ngoài một cửa sổ độ tươi mới chấp nhận được (ví dụ 300 s); tính lại hash_hmac('sha256', T . '.' . rawBody, secret) trên các byte thô nhận được; so sánh theo thời gian hằng số với giá trị header sau khi loại bỏ tiền tố sha256=.
  • Thân, chữ ký, và id lần gửi được tính một lần cho mỗi lần gửi và giữ nguyên qua các lần thử lại.
  • Cổng chặn ra SSRF. Trước mỗi lần thử, URL đích đi qua cổng UrlValidator::validateExternalUrl() của Core: chỉ scheme HTTPS; các dải loopback, riêng tư, dành riêng, carrier-grade-NAT, cloud-metadata, và các dải chuyển tiếp IPv6 nhúng IPv4 đều bị chặn; tên host được phân giải DNS (A và AAAA) và các host không phân giải được bị từ chối theo kiểu fail-closed. Một URL bị chặn sẽ không bao giờ được gửi: vòng lặp thử bị hủy và payload được định tuyến thẳng tới hàng đợi dead-letter với lỗi cuối cùng Blocked SSRF destination: và trạng thái HTTP null.
  • Phân loại kết quả theo từng lần thử: 2xx là thành công và trả về ngay lập tức; một 4xx khác 429 là cuối cùng và chuyển thẳng tới dead-letter; mọi kết quả khác — 3xx, 429, 5xx, hoặc một ngoại lệ truyền tải — đều có thể thử lại đến tổng số lần thử của chính sách.
  • Backoff theo cấp số nhân: thời gian chờ trước lần thử kế tiếp là baseDelaySeconds × 2^(attempt − 1), giới hạn ở maxDelaySeconds. Phần chờ được bỏ qua sau lần thử cuối cùng.
  • Khi không lần thử nào thành công, một DeadLetterEntry ghi lại một id duy nhất, id đăng ký, payload gốc, số lần thử (kẹp ở mức tối đa của chính sách), thông báo lỗi cuối cùng, trạng thái HTTP cuối cùng (null khi thất bại truyền tải hoặc bị chặn SSRF), và dấu thời gian thất bại.
  • Hàng đợi dead-letter nằm trong bộ nhớ và bị giới hạn trong vòng đời tiến trình. markReplayed() tạo ra một bản sao được gắn cờ; nó không gửi lại, và hàng đợi giữ lại mục gốc.

Trường hợp biên và các chế độ thất bại

Phần tiêu đề “Trường hợp biên và các chế độ thất bại”
  • Danh sách sự kiện rỗng. Đăng ký nhận mọi loại sự kiện. Hãy giới hạn danh sách một cách tường minh khi bên nhận không được thấy tất cả sự kiện.
  • 4xx cuối cùng so với thất bại truyền tải. Một từ chối 4xx ghi một lastHttpStatus có giá trị; một thất bại kết nối ghi null. Hãy dùng null để phân biệt từ chối của bên nhận với thất bại truyền tải.
  • Đích bị chặn SSRF. Một đăng ký trỏ tới một địa chỉ HTTP, riêng tư, loopback, hoặc metadata sẽ vào dead-letter ngay lần thử đầu tiên với lỗi Blocked SSRF destination: và trạng thái null. Không có yêu cầu đi ra nào được thực hiện. Hãy sửa URL và đăng ký lại.
  • Bên nhận cũ sau khi nâng cấp. Một bên nhận vẫn xác minh HMAC chỉ-trên-thân trước 3.1.0 sẽ fail closed đối với các lần gửi 3.1.0. Hãy chuyển bên nhận sang chuỗi cơ sở {timestamp}.{body} và tiêu thụ X-NextPDF-Timestamp.
  • Dữ liệu sự kiện không thể mã hóa. toJson()sign() ném JsonException, ngoại lệ này truyền ra khỏi deliver()dispatch() trước khi thực hiện bất kỳ lần thử nào.
  • Chặn đồng bộ. deliver() ngủ nội tuyến giữa các lần thử. Backoff tích lũy đạt 15 s dưới chính sách mặc định và khoảng 17 phút dưới chính sách aggressive. Hãy gửi từ một queue worker khi độ trễ của bên nhận không đáng tin.
  • Kẹp số lần thử. Số lần thử được ghi không bao giờ vượt quá mức tối đa của chính sách, ngay cả khi bộ đếm vòng lặp nội bộ tiến vượt quá nó khi cạn kiệt.
  • Tăng trưởng hàng đợi và tính bền vững. Hàng đợi dead-letter tăng trưởng không giới hạn trong tiến trình và biến mất khi khởi động lại. Hãy xuất các mục qua deadLetters() và lưu bền vững chúng ra bên ngoài trước khi gọi clearDeadLetters() khi cần phát lại bền vững.
  • Phát lại do người vận hành điều khiển. Gửi lại nghĩa là gọi deliver() lần nữa với payload của mục; markReplayed() chỉ ghi lại sự kiện đó trên một bản sao.
  • Rủi ro tồn dư DNS-rebinding. URL được kiểm tra lại ở mỗi lần thử, điều này thu hẹp nhưng không đóng hoàn toàn cửa sổ rebinding: lớp trừu tượng PSR-18 không thể ghim kết nối vào IP đã được xác thực. Hãy thêm các kiểm soát chặn ra ở tầng mạng khi rủi ro tồn dư này quan trọng.
  • Xử lý secret. Secret của đăng ký là một thông tin xác thực. HMAC chỉ xác thực tính toàn vẹn và nguồn gốc — nó không phải là tính bảo mật. Đừng đặt vào payload sự kiện dữ liệu mà bên nhận không được thấy.

Việc ký payload là HMAC-SHA256 qua hash_hmac() của PHP, nên nó dựa vào nhà cung cấp mã hóa của host. Trong một bản build bị ràng buộc FIPS, một nguyên hàm không được chấp thuận sẽ thất bại tại ranh giới mã hóa thay vì hạ cấp. Lớp webhook không thêm chính sách mã hóa riêng nào.

  • Xác thực payload triển khai HMAC, mã xác thực thông điệp băm-có-khóa của FIPS PUB 198-1 §1, được hiện thực hóa với SHA-256.
  • Bảo vệ chống phát lại tuân theo hướng dẫn bảo mật webhook của OWASP Cheat Sheet Series: dấu thời gian sự kiện đi trong một header riêng và được gieo vào phép tính chữ ký, nên một dấu thời gian bị giả mạo sẽ không qua được xác minh.
  • Các dấu thời gian trong thân dùng định dạng date-time RFC 3339 mở rộng. Khai báo trong mã: RFC 3339 không được truy xuất từ kho RAG cho trang này.
  • Đây là các phát biểu về năng lực dựa trên mã nguồn sản phẩm và các điều khoản được trích dẫn. NextPDF không đưa ra tuyên bố phù hợp hay chứng nhận nào cho bề mặt này.
  • Mọi lớp khai báo strict_types=1 và là final; WebhookRegistration, WebhookPayload, WebhookRetryPolicy, và DeadLetterEntryfinal readonly với các thuộc tính công khai được promoted.
  • Module mang một chú thích @since2.2.0; sơ đồ chữ ký ràng buộc dấu thời gian là một thay đổi phá vỡ tương thích được ghi nhận trong 3.1.0.
  • Engine gửi nhận các lớp trừu tượng PSR-18/PSR-17, nên một mock HTTP client có thể chạy toàn bộ đường gửi, thử lại, và dead-letter ngoại tuyến. Logger mặc định là null; hãy tiêm một logger PSR-3 ở sản phẩm nếu không các thất bại chỉ hiện ra qua giá trị trả về.
  • Các triển khai bên nhận nên dùng hash_equals() để so sánh chữ ký và thực thi một cửa sổ độ tươi mới trên X-NextPDF-Timestamp.
  • Các test biên được khuyến nghị: đăng ký lệch thuê bao, fan-out với danh sách sự kiện rỗng, 4xx cuối cùng, cạn kiệt số lần thử, URL bị chặn SSRF, từ chối chữ ký khi dấu thời gian bị giả mạo so với một vector cố định, và kẹp số lần thử của dead-letter.

Trang này chỉ tài liệu hóa hành vi có thể quan sát được từ bên ngoài và bề mặt API công khai được hỗ trợ. Các đường dẫn namespace nội bộ, các lớp trợ giúp, các bảng cơ chế, các tên tệp runbook, và các tiền tố ticket đều nằm ngoài phạm vi.