Skip to content

v4 API: Webhooks #92

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions src/ConvertKit_API.php
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,38 @@ public function delete_broadcast(int $id)
return $this->delete(sprintf('broadcasts/%s', $id));
}

/**
* List webhooks.
*
* @param boolean $include_total_count To include the total count of records in the response, use true.
* @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.0.0
*
* @see https://developers.convertkit.com/v4.html#list-webhooks
*
* @return false|mixed
*/
public function get_webhooks(
bool $include_total_count = false,
string $after_cursor = '',
string $before_cursor = '',
int $per_page = 100
) {
// Send request.
return $this->get(
endpoint: 'webhooks',
args: $this->build_total_count_and_pagination_params(
include_total_count: $include_total_count,
after_cursor: $after_cursor,
before_cursor: $before_cursor,
per_page: $per_page
)
);
}

/**
* Creates a webhook that will be called based on the chosen event types.
*
Expand All @@ -1531,7 +1563,7 @@ public function delete_broadcast(int $id)
*
* @since 1.0.0
*
* @see https://developers.convertkit.com/#create-a-webhook
* @see https://developers.convertkit.com/v4.html#create-a-webhook
*
* @throws \InvalidArgumentException If the event is not supported.
*
Expand All @@ -1543,6 +1575,8 @@ public function create_webhook(string $url, string $event, string $parameter = '
switch ($event) {
case 'subscriber.subscriber_activate':
case 'subscriber.subscriber_unsubscribe':
case 'subscriber.subscriber_bounce':
case 'subscriber.subscriber_complain':
case 'purchase.purchase_create':
$eventData = ['name' => $event];
break;
Expand Down Expand Up @@ -1590,7 +1624,7 @@ public function create_webhook(string $url, string $event, string $parameter = '

// Send request.
return $this->post(
'automations/hooks',
'webhooks',
[
'target_url' => $url,
'event' => $eventData,
Expand All @@ -1601,17 +1635,17 @@ public function create_webhook(string $url, string $event, string $parameter = '
/**
* Deletes an existing webhook.
*
* @param integer $rule_id Rule ID.
* @param integer $id Webhook ID.
*
* @since 1.0.0
*
* @see https://developers.convertkit.com/#destroy-webhook
* @see https://developers.convertkit.com/v4.html#delete-a-webhook
*
* @return false|object
*/
public function destroy_webhook(int $rule_id)
public function delete_webhook(int $id)
{
return $this->delete(sprintf('automations/hooks/%s', $rule_id));
return $this->delete(sprintf('webhooks/%s', $id));
}

/**
Expand Down
166 changes: 135 additions & 31 deletions tests/ConvertKitAPITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ class ConvertKitAPITest extends TestCase
*/
protected $subscriber_ids = [];

/**
* Webhook IDs to delete on teardown of a test.
*
* @since 2.0.0
*
* @var array<int, int>
*/
protected $webhook_ids = [];

/**
* Broadcast IDs to delete on teardown of a test.
*
Expand Down Expand Up @@ -107,6 +116,11 @@ protected function tearDown(): void
$this->api->unsubscribe($id);
}

// Delete any Webhooks.
foreach ($this->webhook_ids as $id) {
$this->api->delete_webhook($id);
}

// Delete any Broadcasts.
foreach ($this->broadcast_ids as $id) {
$this->api->delete_broadcast($id);
Expand Down Expand Up @@ -3846,7 +3860,82 @@ public function testDeleteBroadcastWithInvalidBroadcastID()
}

/**
* Test that create_webhook() and destroy_webhook() works.
* Test that get_webhooks() returns the expected data
* when pagination parameters and per_page limits are specified.
*
* @since 2.0.0
*
* @return void
*/
public function testGetWebhooksPagination()
{
// Create webhooks first.
$results = [
$this->api->create_webhook(
url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
event: 'subscriber.subscriber_activate',
),
$this->api->create_webhook(
url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
event: 'subscriber.subscriber_activate',
),
];

// Set webhook_ids to ensure webhooks are deleted after test.
$this->webhook_ids = [
$results[0]->webhook->id,
$results[1]->webhook->id,
];

// Get webhooks.
$result = $this->api->get_webhooks(
per_page: 1
);

// Assert webhooks and pagination exist.
$this->assertDataExists($result, 'webhooks');
$this->assertPaginationExists($result);

// Assert a single webhook was returned.
$this->assertCount(1, $result->webhooks);

// Assert has_previous_page and has_next_page are correct.
$this->assertFalse($result->pagination->has_previous_page);
$this->assertTrue($result->pagination->has_next_page);

// Use pagination to fetch next page.
$result = $this->api->get_webhooks(
per_page: 1,
after_cursor: $result->pagination->end_cursor
);

// Assert webhooks and pagination exist.
$this->assertDataExists($result, 'webhooks');
$this->assertPaginationExists($result);

// Assert a single webhook was returned.
$this->assertCount(1, $result->webhooks);

// Assert has_previous_page and has_next_page are correct.
$this->assertTrue($result->pagination->has_previous_page);
$this->assertFalse($result->pagination->has_next_page);

// Use pagination to fetch previous page.
$result = $this->api->get_webhooks(
per_page: 1,
before_cursor: $result->pagination->start_cursor
);

// Assert webhooks and pagination exist.
$this->assertDataExists($result, 'webhooks');
$this->assertPaginationExists($result);

// Assert a single webhook was returned.
$this->assertCount(1, $result->webhooks);
}

/**
* Test that create_webhook(), get_webhooks() and delete_webhook() works.
*
* We do both, so we don't end up with unnecessary webhooks remaining
* on the ConvertKit account when running tests.
Expand All @@ -3855,47 +3944,66 @@ public function testDeleteBroadcastWithInvalidBroadcastID()
*
* @return void
*/
public function testCreateAndDestroyWebhook()
public function testCreateGetAndDeleteWebhook()
{
$this->markTestIncomplete();

// Create a webhook first.
$result = $this->api->create_webhook(
url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb',
url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
event: 'subscriber.subscriber_activate',
);
$ruleID = $result->rule->id;
$id = $result->webhook->id;

// Get webhooks.
$result = $this->api->get_webhooks();

// Destroy the webhook.
$result = $this->api->destroy_webhook($ruleID);
$this->assertEquals($result->success, true);
// Assert webhooks and pagination exist.
$this->assertDataExists($result, 'webhooks');
$this->assertPaginationExists($result);

// Get webhooks including total count.
$result = $this->api->get_webhooks(
include_total_count: true
);

// Assert webhooks and pagination exist.
$this->assertDataExists($result, 'webhooks');
$this->assertPaginationExists($result);

// Assert total count is included.
$this->assertArrayHasKey('total_count', get_object_vars($result->pagination));
$this->assertGreaterThan(0, $result->pagination->total_count);

// Delete the webhook.
$result = $this->api->delete_webhook($id);
}

/**
* Test that create_webhook() and destroy_webhook() works with an event parameter.
*
* We do both, so we don't end up with unnecessary webhooks remaining
* on the ConvertKit account when running tests.
* Test that create_webhook() works with an event parameter.
*
* @since 1.0.0
*
* @return void
*/
public function testCreateAndDestroyWebhookWithEventParameter()
public function testCreateWebhookWithEventParameter()
{
$this->markTestIncomplete();

// Create a webhook first.
// Create a webhook.
$url = 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds');
$result = $this->api->create_webhook(
url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb',
url: $url,
event: 'subscriber.form_subscribe',
parameter: $_ENV['CONVERTKIT_API_FORM_ID']
);
$ruleID = $result->rule->id;

// Destroy the webhook.
$result = $this->api->destroy_webhook($ruleID);
$this->assertEquals($result->success, true);
// Confirm webhook created with correct data.
$this->assertArrayHasKey('webhook', get_object_vars($result));
$this->assertArrayHasKey('id', get_object_vars($result->webhook));
$this->assertArrayHasKey('target_url', get_object_vars($result->webhook));
$this->assertEquals($result->webhook->target_url, $url);
$this->assertEquals($result->webhook->event->name, 'form_subscribe');
$this->assertEquals($result->webhook->event->form_id, $_ENV['CONVERTKIT_API_FORM_ID']);

// Delete the webhook.
$result = $this->api->delete_webhook($result->webhook->id);
}

/**
Expand All @@ -3908,29 +4016,25 @@ public function testCreateAndDestroyWebhookWithEventParameter()
*/
public function testCreateWebhookWithInvalidEvent()
{
$this->markTestIncomplete();

$this->expectException(InvalidArgumentException::class);
$this->api->create_webhook(
url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb',
url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'),
event: 'invalid.event'
);
}

/**
* Test that destroy_webhook() throws a ClientException when an invalid
* rule ID is specified.
* Test that delete_webhook() throws a ClientException when an invalid
* ID is specified.
*
* @since 1.0.0
*
* @return void
*/
public function testDestroyWebhookWithInvalidRuleID()
public function testDeleteWebhookWithInvalidID()
{
$this->markTestIncomplete();

$this->expectException(ClientException::class);
$this->api->destroy_webhook(12345);
$this->api->delete_webhook(12345);
}

/**
Expand Down