BigCommerce webhook subscriptions 

This guide will show you how to subscribe to BigCommerce webhooks and use them to trigger global actions in Gadget.

Set up a webhook subscription 

After setting up a BigCommerce connection, you can add BigCommerce webhooks as triggers to global actions.

To add a BigCommerce webhook as a trigger:

  1. Navigate to an existing global action, or create a new one in api/actions
  2. Click the + button to add a new trigger and select BigCommerce
  3. Choose the webhook topic you want to trigger the action

When the selected webhooks are fired by BigCommerce, the global action will run.

Webhook payload 

Webhooks sent by BigCommerce only include the id of the resource that triggered the webhook. To fetch the full resource data, you can use the included BigCommerce API client.

sample BigCommerce store/product/created payload
json
{
"id": 113,
"storeHash": "wvpfsac141",
"type": "product"
}

For more information on reading and writing to BigCommerce, see the BigCommerce data guide.

Managing webhook scopes 

Gadget automatically registers the BigCommerce webhook scopes you select as triggers in global actions. This means you can access the webhook payload in the action.

To see all scopes registered for a store, you can view the registeredWebhooks field in api/models/bigcommerce/store/data.

Manually registering webhooks 

You can view the status of webhook registration on the store Installs page of the BigCommerce connection.

Hover over the Webhook Status of the store to see the list of webhooks that are registered for the store.

If webhooks are not all registered, you can click the Register webhooks button to manually register webhooks for a store. The only time that manual webhook registration is necessary is if there was a network issue during the initial registration.

Webhooks that are no longer used as triggers will be automatically unregistered.

Persisting session data in webhook-triggered actions 

If you call an action from a global action that is triggered by a BigCommerce webhook, session data will not persist in the called action. This means that you will not be able to use connections.bigcommerce.current to get an instance of the BigCommerce API client, or connections.bigcommerce.currentStoreHash to get the store hash.

If you want to use the BigCommerce API client in an action that is called from a webhook-triggered global action, you will need to use connections.bigcommerce.forStoreHash() to get the API client for the store.

This means you also need the storeHash. The best way to access the storeHash in model actions is to relate your model records to the bigcommerce/store model and then fetch the hash manually before initializing the API client. Read more about setting up models to store BigCommerce data in the BigCommerce data guide.

If you are calling another global action from your webhook-triggered action, the storeHash can be passed in the params object.

Here is an example of a model action that fetches the storeHash and uses it to initialize the BigCommerce API:

Fetch storeHash and use it to init the BigCommerce API client
JavaScript
1import { applyParams, save, ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({ params, record }) => {
4 applyParams(params, record);
5 await save(record);
6};
7
8export const onSuccess: ActionOnSuccess = async ({ record, api, connections }) => {
9 // get the storeHash using the storeId from the current record as a selection criteria in a model related to a store
10 const storeId = record.storeId;
11 const store = await api.bigcommerce.store.findById(storeId, {
12 select: { storeHash: true },
13 });
14
15 // use the storeHash to get a BigCommerce API client for the current store
16 const bigcommerce = await connections.bigcommerce.forStoreHash(store.storeHash);
17
18 // ... use the BigCommerce API client
19};
20
21export const options: ActionOptions = {
22 actionType: "create",
23};
1import { applyParams, save, ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({ params, record }) => {
4 applyParams(params, record);
5 await save(record);
6};
7
8export const onSuccess: ActionOnSuccess = async ({ record, api, connections }) => {
9 // get the storeHash using the storeId from the current record as a selection criteria in a model related to a store
10 const storeId = record.storeId;
11 const store = await api.bigcommerce.store.findById(storeId, {
12 select: { storeHash: true },
13 });
14
15 // use the storeHash to get a BigCommerce API client for the current store
16 const bigcommerce = await connections.bigcommerce.forStoreHash(store.storeHash);
17
18 // ... use the BigCommerce API client
19};
20
21export const options: ActionOptions = {
22 actionType: "create",
23};

BigCommerce webhook status and failures 

Webhooks in Gadget run on the built-in background action system. This means that you can view the payload of past webhooks by clicking on Queues in the Gadget editor.

Filter the Queues list

You can enter bigcommerce-webhook into the filter input to only display BigCommerce webhooks in the queue.

Clicking on an individual webhook will show you the payload that was sent by BigCommerce, as well as any errors that occurred during webhook processing.

If there is an error in the global action triggered by the BigCommerce webhook, the background actions system will automatically retry the action with the same webhook payload. If the retry count is reached and the action is not successful, the queued action will be marked as failed and you can view the error message.

Once the error has been fixed, you can re-run the action with the same webhook payload by clicking ⋮ → Retry now on the failed action.

Avoiding webhook loops 

When creating global actions triggered by BigCommerce webhooks, it is important to avoid creating loops where the action triggers the same webhook that triggered the action.

For example, if a store/order/updated webhook triggers an action that updates the same product in BigCommerce, the update will trigger the store/order/updated webhook again, creating an infinite loop:

Avoid doing this in a global action, webhook will loop infinitely!
JavaScript
1import { ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({ params, logger, api, connections }) => {
4 // get the order ID from the webhook payload
5 const orderId = params.id;
6 // get the BigCommerce API client for the current shop
7 const bigcommerce = connections.bigcommerce.current;
8 // get the order data
9 const order = await bigcommerce.v2.get("/orders/{order_id}", {
10 path: {
11 order_id: orderId,
12 },
13 });
14
15 // do custom work with the order
16 // in this case, a secret discount!
17 if (order.customer_message.includes("Gadget")) {
18 // update the same order!
19 await bigcommerce.v2.put("/orders/{order_id}", {
20 path: {
21 order_id: orderId,
22 },
23 body: {
24 // fields to update on order
25 discount_amount: 10.0,
26 },
27 });
28 }
29};
30
31export const options: ActionOptions = {
32 triggers: {
33 api: false,
34 bigcommerce: {
35 // action is triggered by the store/order/updated webhook
36 webhooks: ["store/order/updated"],
37 },
38 },
39};
1import { ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({ params, logger, api, connections }) => {
4 // get the order ID from the webhook payload
5 const orderId = params.id;
6 // get the BigCommerce API client for the current shop
7 const bigcommerce = connections.bigcommerce.current;
8 // get the order data
9 const order = await bigcommerce.v2.get("/orders/{order_id}", {
10 path: {
11 order_id: orderId,
12 },
13 });
14
15 // do custom work with the order
16 // in this case, a secret discount!
17 if (order.customer_message.includes("Gadget")) {
18 // update the same order!
19 await bigcommerce.v2.put("/orders/{order_id}", {
20 path: {
21 order_id: orderId,
22 },
23 body: {
24 // fields to update on order
25 discount_amount: 10.0,
26 },
27 });
28 }
29};
30
31export const options: ActionOptions = {
32 triggers: {
33 api: false,
34 bigcommerce: {
35 // action is triggered by the store/order/updated webhook
36 webhooks: ["store/order/updated"],
37 },
38 },
39};

This will constantly update the order with the discount, creating an infinite loop!

Avoiding these webhook loops requires some data to be stored in Gadget. Once data is stored in a model, you can use Gadget's record API that contains built-in change detection to see if code needs to be run, including any writes back to BigCommerce.

More information on change detection can be found in your API reference.

Here is how the above example can be modified to take advantage of change detection. First, save the order data in a bigcommerce/order data model by calling api.bigcommerce.order.update instead of writing back to BigCommerce directly:

Call a model action from the global action to save data
JavaScript
1import { ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({ params, logger, api, connections }) => {
4 // get the order ID from the webhook payload
5 const orderId = params.id;
6 // get the BigCommerce API client for the current shop
7 const bigcommerce = connections.bigcommerce.current;
8 // get the order data
9 const order = await bigcommerce.v2.get(`/orders/${orderId}`);
10
11 // see if the order is stored in Gadget
12 const orderRecord = await api.bigcommerce.order.findFirst({
13 filter: { bigcommerceId: { equals: order.id } },
14 });
15
16 // save to Gadget by running the order update action
17 await api.bigcommerce.order.update(orderRecord.id, {
18 customerMessage: order.customer_message,
19 });
20};
21
22export const options: ActionOptions = {
23 triggers: {
24 api: false,
25 bigcommerce: {
26 // action is triggered by the store/order/updated webhook
27 webhooks: ["store/order/updated"],
28 },
29 },
30};
1import { ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({ params, logger, api, connections }) => {
4 // get the order ID from the webhook payload
5 const orderId = params.id;
6 // get the BigCommerce API client for the current shop
7 const bigcommerce = connections.bigcommerce.current;
8 // get the order data
9 const order = await bigcommerce.v2.get(`/orders/${orderId}`);
10
11 // see if the order is stored in Gadget
12 const orderRecord = await api.bigcommerce.order.findFirst({
13 filter: { bigcommerceId: { equals: order.id } },
14 });
15
16 // save to Gadget by running the order update action
17 await api.bigcommerce.order.update(orderRecord.id, {
18 customerMessage: order.customer_message,
19 });
20};
21
22export const options: ActionOptions = {
23 triggers: {
24 api: false,
25 bigcommerce: {
26 // action is triggered by the store/order/updated webhook
27 webhooks: ["store/order/updated"],
28 },
29 },
30};

The bigcommerce/order record is updated in the database, and record.changes() is used to check and see if a write back to BigCommerce is required, breaking any possible webhook loops:

write to BigCommerce in api/models/bigcommerce/order/actions/update.js
JavaScript
1import { applyParams, save, ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({
4 params,
5 record,
6 logger,
7 api,
8 connections,
9}) => {
10 applyParams(params, record);
11 await save(record);
12};
13
14export const onSuccess: ActionOnSuccess = async ({
15 params,
16 record,
17 logger,
18 api,
19 connections,
20}) => {
21 // get the API client for the store
22 const bigcommerce = await connections.bigcommerce.forStoreHash(record.storeHash!);
23
24 // if the customer_message hasn't changed, don't write to the store!
25 if (
26 !record.changed("customerMessage") &&
27 record.customerMessage?.includes("Gadget")
28 ) {
29 // only write back to BigCommerce if necessary
30 await bigcommerce.v2.put("/orders/{order_id}", {
31 path: {
32 order_id: record.bigcommerceId,
33 },
34 body: {
35 // fields to update on order
36 discount_amount: 10.0,
37 },
38 });
39 }
40};
41
42export const options: ActionOptions = {
43 actionType: "update",
44};
1import { applyParams, save, ActionOptions } from "gadget-server";
2
3export const run: ActionRun = async ({
4 params,
5 record,
6 logger,
7 api,
8 connections,
9}) => {
10 applyParams(params, record);
11 await save(record);
12};
13
14export const onSuccess: ActionOnSuccess = async ({
15 params,
16 record,
17 logger,
18 api,
19 connections,
20}) => {
21 // get the API client for the store
22 const bigcommerce = await connections.bigcommerce.forStoreHash(record.storeHash!);
23
24 // if the customer_message hasn't changed, don't write to the store!
25 if (
26 !record.changed("customerMessage") &&
27 record.customerMessage?.includes("Gadget")
28 ) {
29 // only write back to BigCommerce if necessary
30 await bigcommerce.v2.put("/orders/{order_id}", {
31 path: {
32 order_id: record.bigcommerceId,
33 },
34 body: {
35 // fields to update on order
36 discount_amount: 10.0,
37 },
38 });
39 }
40};
41
42export const options: ActionOptions = {
43 actionType: "update",
44};

Using change detection and the record API, you can avoid webhook loops and ensure that your global actions are only triggered when necessary.

Webhook security 

Gadget keeps your webhook callback requests secure by adhering to BigCommerce's webhook security best practices.

Was this page helpful?