Skip to content

Commit 9351674

Browse files
Muhammad FaragMuhammadFarag
authored andcommitted
Register PubSub Webhooks
1 parent f9e5e32 commit 9351674

7 files changed

Lines changed: 598 additions & 213 deletions

File tree

docs/usage/webhooks.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,23 @@ After your handlers are loaded, you need to register which topics you want your
6262

6363
In your OAuth callback action, you can use the `Shopify\Webhooks\Registry::register` method to subscribe to any topic allowed by your app's scopes. This method can safely be called multiple times for a shop, as it will update existing webhooks if necessary.
6464

65+
### EventBridge and PubSub Webhooks
66+
67+
You can also register webhooks for delivery to Amazon EventBridge or Google Cloud Pub/Sub. In this case the `path` argument to `Registry::register` needs to be of a specific form.
68+
69+
For EventBridge, the `path` must be the [ARN of the partner event source](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_EventSource.html).
70+
71+
For Pub/Sub, the `path` must be of the form `pubsub://[PROJECT-ID]:[PUB-SUB-TOPIC-ID]`. For example, if you created a topic with id `red` in the project `blue`, then the value of `path` would be `pubsub://blue:red`.
72+
6573
The parameters this method accepts are:
6674

67-
| Parameter | Type | Required? | Default Value | Notes |
68-
| --- | --- | :---: | :---: | --- |
69-
| `path` | `string` | Yes | - | The URL path for the callback. If using EventBridge, this is the full resource address. |
70-
| `topic` | `string` | Yes | - | The topic to subscribe to. May be a string or a value from the Topics class. |
71-
| `shop` | `string` | Yes | - | The shop to use for requests. |
72-
| `accessToken` | `string` | Yes | - | The access token to use for requests. |
73-
| `deliveryMethod` | `string` | No | `Registry::DELIVERY_METHOD_HTTP` | The delivery method for this webhook. |
75+
| Parameter | Type | Required? | Default Value | Notes |
76+
|:-----------------|:---------|:---------:|:--------------------------------:|:-----------------------------------------------------------------------------|
77+
| `path` | `string` | Yes | - | The URL path for the callback for HTTP delivery, EventBridge or Pub/Sub URLs |
78+
| `topic` | `string` | Yes | - | The topic to subscribe to. May be a string or a value from the Topics class. |
79+
| `shop` | `string` | Yes | - | The shop to use for requests. |
80+
| `accessToken` | `string` | Yes | - | The access token to use for requests. |
81+
| `deliveryMethod` | `string` | No | `Registry::DELIVERY_METHOD_HTTP` | The delivery method for this webhook. |
7482

7583
This method will return a `RegisterResponse` object, which holds the following data:
7684

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Shopify\Webhooks\Delivery;
4+
5+
use Shopify\Context;
6+
use Shopify\Exception\InvalidArgumentException;
7+
use Shopify\Utils;
8+
use Shopify\Webhooks\DeliveryMethod;
9+
10+
class EventBridge extends DeliveryMethod
11+
{
12+
13+
/**
14+
* @throws \Shopify\Exception\InvalidArgumentException
15+
*/
16+
public function __construct()
17+
{
18+
if (!Utils::isApiVersionCompatible('2020-07')) {
19+
throw new InvalidArgumentException(
20+
"EventBridge webhooks are not supported in API version " . Context::$API_VERSION
21+
);
22+
}
23+
}
24+
25+
/**
26+
* @inheritDoc
27+
*/
28+
public function getCallbackAddress(string $path): string
29+
{
30+
return $path;
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function parseCheckQueryResult(array $body): array
37+
{
38+
$edges = $body['data']['webhookSubscriptions']['edges'] ?? [];
39+
40+
$webhookId = null;
41+
$currentAddress = null;
42+
if (count($edges ?? [])) {
43+
$node = $edges[0]['node'];
44+
$webhookId = (string)$node['id'];
45+
$currentAddress = $node['endpoint']['arn'];
46+
}
47+
return [$webhookId, $currentAddress];
48+
}
49+
50+
/**
51+
* @inheritDoc
52+
*/
53+
public function buildCheckQuery(string $topic): string
54+
{
55+
return <<<QUERY
56+
{
57+
webhookSubscriptions(first: 1, topics: $topic) {
58+
edges {
59+
node {
60+
id
61+
endpoint {
62+
__typename
63+
... on WebhookEventBridgeEndpoint {
64+
arn
65+
}
66+
}
67+
}
68+
}
69+
}
70+
}
71+
QUERY;
72+
}
73+
74+
/**
75+
* @inheritDoc
76+
*/
77+
protected function getMutationName(?string $webhookId): string
78+
{
79+
return $webhookId ? "eventBridgeWebhookSubscriptionUpdate" : "eventBridgeWebhookSubscriptionCreate";
80+
}
81+
82+
/**
83+
* @inheritDoc
84+
*/
85+
protected function queryEndpoint(string $address): string
86+
{
87+
return "{arn: \"$address\"}";
88+
}
89+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace Shopify\Webhooks\Delivery;
4+
5+
use Shopify\Context;
6+
use Shopify\Utils;
7+
use Shopify\Webhooks\DeliveryMethod;
8+
9+
class HttpDelivery extends DeliveryMethod
10+
{
11+
/**
12+
* @inheritDoc
13+
*/
14+
public function getCallbackAddress(string $path): string
15+
{
16+
return 'https://' . Context::$HOST_NAME . '/' . ltrim($path, '/');
17+
}
18+
19+
/**
20+
* Builds a GraphQL query to check whether this topic is already registered for the shop.
21+
*
22+
* @param string $topic
23+
*
24+
* @return string
25+
* @throws \Shopify\Exception\InvalidArgumentException
26+
*/
27+
public function buildCheckQuery(string $topic): string
28+
{
29+
if (Utils::isApiVersionCompatible('2020-07')) {
30+
return <<<QUERY
31+
{
32+
webhookSubscriptions(first: 1, topics: $topic) {
33+
edges {
34+
node {
35+
id
36+
endpoint {
37+
__typename
38+
... on WebhookHttpEndpoint {
39+
callbackUrl
40+
}
41+
}
42+
}
43+
}
44+
}
45+
}
46+
QUERY;
47+
} else {
48+
return <<<LEGACY_QUERY
49+
{
50+
webhookSubscriptions(first: 1, topics: $topic) {
51+
edges {
52+
node {
53+
id
54+
callbackUrl
55+
}
56+
}
57+
}
58+
}
59+
LEGACY_QUERY;
60+
}
61+
}
62+
63+
/**
64+
* @inheritDoc
65+
*/
66+
public function parseCheckQueryResult(array $body): array
67+
{
68+
$edges = $body['data']['webhookSubscriptions']['edges'] ?? [];
69+
$webhookId = null;
70+
$currentAddress = null;
71+
if (count($edges ?? [])) {
72+
$node = $edges[0]['node'];
73+
$webhookId = (string)$node['id'];
74+
if (array_key_exists('endpoint', $node)) {
75+
$currentAddress = $node['endpoint']['callbackUrl'];
76+
} else {
77+
$currentAddress = $node['callbackUrl'];
78+
}
79+
}
80+
return [$webhookId, $currentAddress];
81+
}
82+
83+
/**
84+
* @inheritDoc
85+
*/
86+
protected function getMutationName(?string $webhookId): string
87+
{
88+
return $webhookId ? "webhookSubscriptionUpdate" : "webhookSubscriptionCreate";
89+
}
90+
91+
/**
92+
* @inheritDoc
93+
*/
94+
protected function queryEndpoint(string $address): string
95+
{
96+
return "{callbackUrl: \"$address\"}";
97+
}
98+
}

src/Webhooks/Delivery/PubSub.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace Shopify\Webhooks\Delivery;
4+
5+
use Shopify\Context;
6+
use Shopify\Exception\InvalidArgumentException;
7+
use Shopify\Utils;
8+
use Shopify\Webhooks\DeliveryMethod;
9+
10+
class PubSub extends DeliveryMethod
11+
{
12+
13+
/**
14+
* @throws \Shopify\Exception\InvalidArgumentException
15+
*/
16+
public function __construct()
17+
{
18+
if (!Utils::isApiVersionCompatible('2021-07')) {
19+
throw new InvalidArgumentException(
20+
"PubSub webhooks are not supported in API version " . Context::$API_VERSION
21+
);
22+
}
23+
}
24+
25+
/**
26+
* @inheritDoc
27+
*/
28+
public function getCallbackAddress(string $path): string
29+
{
30+
return $path;
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function parseCheckQueryResult(array $body): array
37+
{
38+
$edges = $body['data']['webhookSubscriptions']['edges'] ?? [];
39+
40+
$webhookId = null;
41+
$currentAddress = null;
42+
if (count($edges ?? [])) {
43+
$node = $edges[0]['node'];
44+
45+
$webhookId = (string)$node['id'];
46+
$currentAddress = "pubsub://" . $node['endpoint']['pubSubProject'] . ":" . $node['endpoint']['pubSubTopic'];
47+
}
48+
return [$webhookId, $currentAddress];
49+
}
50+
51+
/**
52+
* @inheritDoc
53+
*/
54+
public function buildCheckQuery(string $topic): string
55+
{
56+
return <<<QUERY
57+
{
58+
webhookSubscriptions(first: 1, topics: $topic) {
59+
edges {
60+
node {
61+
id
62+
endpoint {
63+
__typename
64+
... on WebhookPubSubEndpoint {
65+
pubSubProject
66+
pubSubTopic
67+
}
68+
}
69+
}
70+
}
71+
}
72+
}
73+
QUERY;
74+
}
75+
76+
/**
77+
* @inheritDoc
78+
*/
79+
protected function getMutationName(?string $webhookId): string
80+
{
81+
return $webhookId ? "pubSubWebhookSubscriptionUpdate" : "pubSubWebhookSubscriptionCreate";
82+
}
83+
84+
/**
85+
* @inheritDoc
86+
*/
87+
protected function queryEndpoint(string $address): string
88+
{
89+
$addressWithoutProtocol = explode("//", $address);
90+
list($project, $topic) = explode(":", $addressWithoutProtocol[1]);
91+
return "{pubSubProject: \"$project\", pubSubTopic: \"$topic\"}";
92+
}
93+
}

0 commit comments

Comments
 (0)