From 988f2c24776a9bf385de552f4142d2a2f9060f7d Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 12 Nov 2026 04:09:37 +0000 Subject: [PATCH] Add Webhook Endpoint Deliveries Methods --- src/ConvertKit_API_Traits.php | 78 ++++++++++++++ tests/TestsTrait.php | 187 ++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) diff --git a/src/ConvertKit_API_Traits.php b/src/ConvertKit_API_Traits.php index 82f3302..afa7f47 100644 --- a/src/ConvertKit_API_Traits.php +++ b/src/ConvertKit_API_Traits.php @@ -2593,6 +2593,84 @@ public function revoke_webhook_endpoint_previous_secret(int $id) return $this->post(sprintf('webhook_endpoints/%s/revoke_previous_secret', $id)); } + /** + * List deliveries for a webhook endpoint. + * + * Delivery history is retained for 30 days. + * + * @param integer $id Webhook Endpoint ID. + * @param string $status Delivery status (pending|delivered|failed|retrying). + * @param string $event_type Event type e.g. subscriber.created. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.9.0 + * + * @see https://developers.kit.com/api-reference/webhooks/list-webhook-deliveries + * + * @return false|mixed + */ + public function get_webhook_endpoint_deliveries( + int $id, + string $status = '', + string $event_type = '', + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = []; + + if (!empty($status)) { + $options['status'] = $status; + } + if (!empty($event_type)) { + $options['event_type'] = $event_type; + } + + // Send request. + return $this->get( + sprintf('webhook_endpoints/%s/deliveries', $id), + $this->build_total_count_and_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) + ); + } + + /** + * Get a delivery for a webhook endpoint, including each delivery attempt. + * + * @param integer $id Webhook Endpoint ID. + * @param integer $delivery_id Delivery ID. + * @param boolean $include_payload Include the events sent in the delivery. + * + * @since 2.9.0 + * + * @see https://developers.kit.com/api-reference/webhooks/get-webhook-delivery-details + * + * @return mixed|object + */ + public function get_webhook_endpoint_delivery(int $id, int $delivery_id, bool $include_payload = false) + { + // Build parameters. + $options = []; + + // The API only accepts the strings true or false for this parameter. + if ($include_payload) { + $options['include_payload'] = 'true'; + } + + // Send request. + return $this->get( + sprintf('webhook_endpoints/%s/deliveries/%s', $id, $delivery_id), + $options + ); + } + /** * Verify the signature of a webhook endpoint delivery. * diff --git a/tests/TestsTrait.php b/tests/TestsTrait.php index 8959832..c32fbbd 100644 --- a/tests/TestsTrait.php +++ b/tests/TestsTrait.php @@ -6754,6 +6754,193 @@ public function testRevokeWebhookEndpointPreviousSecret() $this->assertArrayNotHasKey('secret', $webhook_endpoint); } + /** + * Test that get_webhook_endpoint_deliveries() returns the expected data. + * + * @since 2.9.0 + * + * @return void + */ + public function testGetWebhookEndpointDeliveries() + { + // Create a webhook endpoint first. + $result = $this->api->create_webhook_endpoint( + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), + events: ['subscriber.created'] + ); + $id = $result->webhook_endpoint->id; + + // Set webhook_endpoint_ids to ensure webhook endpoints are deleted after test. + $this->webhook_endpoint_ids = [ $id ]; + + // Get deliveries. + $result = $this->api->get_webhook_endpoint_deliveries($id); + + // Assert deliveries and pagination exist. + $this->assertDataExists($result, 'deliveries'); + $this->assertPaginationExists($result); + } + + /** + * Test that get_webhook_endpoint_deliveries() returns the expected data + * when a status is specified. + * + * @since 2.9.0 + * + * @return void + */ + public function testGetWebhookEndpointDeliveriesWithStatus() + { + // Create a webhook endpoint first. + $result = $this->api->create_webhook_endpoint( + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), + events: ['subscriber.created'] + ); + $id = $result->webhook_endpoint->id; + + // Set webhook_endpoint_ids to ensure webhook endpoints are deleted after test. + $this->webhook_endpoint_ids = [ $id ]; + + // Get failed deliveries. + $result = $this->api->get_webhook_endpoint_deliveries( + id: $id, + status: 'failed' + ); + + // Assert deliveries and pagination exist. + $this->assertDataExists($result, 'deliveries'); + $this->assertPaginationExists($result); + + // Assert only failed deliveries were returned. + foreach ($result->deliveries as $delivery) { + $this->assertEquals('failed', $delivery->status); + } + } + + /** + * Test that get_webhook_endpoint_deliveries() throws a ClientException when an invalid + * webhook endpoint ID is specified. + * + * @since 2.9.0 + * + * @return void + */ + public function testGetWebhookEndpointDeliveriesWithInvalidID() + { + $this->assertApiError( + function () { + return $this->api->get_webhook_endpoint_deliveries(12345); + } + ); + } + + /** + * Test that get_webhook_endpoint_deliveries() and get_webhook_endpoint_delivery() + * return the expected data once a delivery has been made to the webhook endpoint. + * + * @since 2.9.0 + * + * @return void + */ + public function testGetWebhookEndpointDelivery() + { + // Create a webhook endpoint first. + $result = $this->api->create_webhook_endpoint( + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), + events: ['subscriber.created'] + ); + $id = $result->webhook_endpoint->id; + + // Set webhook_endpoint_ids to ensure webhook endpoints are deleted after test. + $this->webhook_endpoint_ids = [ $id ]; + + // Create a subscriber, which triggers a delivery to the webhook endpoint. + $result = $this->api->create_subscriber( + email_address: $this->generateEmailAddress() + ); + + // Set subscriber_ids to ensure subscriber is unsubscribed after test. + $this->subscriber_ids = [ $result->subscriber->id ]; + + // Wait for the delivery to be made, as deliveries are sent asynchronously. + $delivery = $this->retryUntil( + function () use ($id) { + $result = $this->api->get_webhook_endpoint_deliveries( + id: $id, + event_type: 'subscriber.created' + ); + + return count($result->deliveries) ? $result->deliveries[0] : false; + } + ); + $this->assertNotFalse($delivery, 'No delivery was made to the webhook endpoint in time.'); + $delivery_id = $delivery->id; + + // Confirm filtering by an event type the webhook endpoint isn't subscribed to returns no deliveries. + $result = $this->api->get_webhook_endpoint_deliveries( + id: $id, + event_type: 'tag.created' + ); + $this->assertCount(0, $result->deliveries); + + // Get the delivery. + $result = $this->api->get_webhook_endpoint_delivery( + id: $id, + delivery_id: $delivery_id + ); + + // Confirm the delivery and its attempts are returned, without the payload. + $delivery = get_object_vars($result->delivery); + $this->assertEquals($delivery_id, $delivery['id']); + $this->assertEquals('subscriber.created', $delivery['event_type']); + $this->assertArrayHasKey('attempts', $delivery); + $this->assertIsArray($delivery['attempts']); + $this->assertArrayNotHasKey('payload', $delivery); + + // Get the delivery, including the payload. + $result = $this->api->get_webhook_endpoint_delivery( + id: $id, + delivery_id: $delivery_id, + include_payload: true + ); + + // Confirm the events sent in the delivery are returned. + $delivery = get_object_vars($result->delivery); + $this->assertArrayHasKey('payload', $delivery); + $this->assertIsArray($delivery['payload']); + $this->assertEquals('subscriber.created', $delivery['payload'][0]->type); + } + + /** + * Test that get_webhook_endpoint_delivery() throws a ClientException when an invalid + * delivery ID is specified. + * + * @since 2.9.0 + * + * @return void + */ + public function testGetWebhookEndpointDeliveryWithInvalidID() + { + // Create a webhook endpoint first. + $result = $this->api->create_webhook_endpoint( + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), + events: ['subscriber.created'] + ); + $id = $result->webhook_endpoint->id; + + // Set webhook_endpoint_ids to ensure webhook endpoints are deleted after test. + $this->webhook_endpoint_ids = [ $id ]; + + $this->assertApiError( + function () use ($id) { + return $this->api->get_webhook_endpoint_delivery( + id: $id, + delivery_id: 12345 + ); + } + ); + } + /** * Test that verify_webhook_signature() returns true when the signature is valid. *