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:
- Navigate to an existing global action, or create a new one in
api/actions
- Click the + button to add a new trigger and select BigCommerce
- 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 payloadjson{"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:
1import { applyParams, save, ActionOptions } from "gadget-server";23export const run: ActionRun = async ({ params, record }) => {4 applyParams(params, record);5 await save(record);6};78export 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 store10 const storeId = record.storeId;11 const store = await api.bigcommerce.store.findById(storeId, {12 select: { storeHash: true },13 });1415 // use the storeHash to get a BigCommerce API client for the current store16 const bigcommerce = await connections.bigcommerce.forStoreHash(store.storeHash);1718 // ... use the BigCommerce API client19};2021export const options: ActionOptions = {22 actionType: "create",23};
1import { applyParams, save, ActionOptions } from "gadget-server";23export const run: ActionRun = async ({ params, record }) => {4 applyParams(params, record);5 await save(record);6};78export 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 store10 const storeId = record.storeId;11 const store = await api.bigcommerce.store.findById(storeId, {12 select: { storeHash: true },13 });1415 // use the storeHash to get a BigCommerce API client for the current store16 const bigcommerce = await connections.bigcommerce.forStoreHash(store.storeHash);1718 // ... use the BigCommerce API client19};2021export 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.
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:
1import { ActionOptions } from "gadget-server";23export const run: ActionRun = async ({ params, logger, api, connections }) => {4 // get the order ID from the webhook payload5 const orderId = params.id;6 // get the BigCommerce API client for the current shop7 const bigcommerce = connections.bigcommerce.current;8 // get the order data9 const order = await bigcommerce.v2.get("/orders/{order_id}", {10 path: {11 order_id: orderId,12 },13 });1415 // do custom work with the order16 // 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 order25 discount_amount: 10.0,26 },27 });28 }29};3031export const options: ActionOptions = {32 triggers: {33 api: false,34 bigcommerce: {35 // action is triggered by the store/order/updated webhook36 webhooks: ["store/order/updated"],37 },38 },39};
1import { ActionOptions } from "gadget-server";23export const run: ActionRun = async ({ params, logger, api, connections }) => {4 // get the order ID from the webhook payload5 const orderId = params.id;6 // get the BigCommerce API client for the current shop7 const bigcommerce = connections.bigcommerce.current;8 // get the order data9 const order = await bigcommerce.v2.get("/orders/{order_id}", {10 path: {11 order_id: orderId,12 },13 });1415 // do custom work with the order16 // 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 order25 discount_amount: 10.0,26 },27 });28 }29};3031export const options: ActionOptions = {32 triggers: {33 api: false,34 bigcommerce: {35 // action is triggered by the store/order/updated webhook36 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:
1import { ActionOptions } from "gadget-server";23export const run: ActionRun = async ({ params, logger, api, connections }) => {4 // get the order ID from the webhook payload5 const orderId = params.id;6 // get the BigCommerce API client for the current shop7 const bigcommerce = connections.bigcommerce.current;8 // get the order data9 const order = await bigcommerce.v2.get(`/orders/${orderId}`);1011 // see if the order is stored in Gadget12 const orderRecord = await api.bigcommerce.order.findFirst({13 filter: { bigcommerceId: { equals: order.id } },14 });1516 // save to Gadget by running the order update action17 await api.bigcommerce.order.update(orderRecord.id, {18 customerMessage: order.customer_message,19 });20};2122export const options: ActionOptions = {23 triggers: {24 api: false,25 bigcommerce: {26 // action is triggered by the store/order/updated webhook27 webhooks: ["store/order/updated"],28 },29 },30};
1import { ActionOptions } from "gadget-server";23export const run: ActionRun = async ({ params, logger, api, connections }) => {4 // get the order ID from the webhook payload5 const orderId = params.id;6 // get the BigCommerce API client for the current shop7 const bigcommerce = connections.bigcommerce.current;8 // get the order data9 const order = await bigcommerce.v2.get(`/orders/${orderId}`);1011 // see if the order is stored in Gadget12 const orderRecord = await api.bigcommerce.order.findFirst({13 filter: { bigcommerceId: { equals: order.id } },14 });1516 // save to Gadget by running the order update action17 await api.bigcommerce.order.update(orderRecord.id, {18 customerMessage: order.customer_message,19 });20};2122export const options: ActionOptions = {23 triggers: {24 api: false,25 bigcommerce: {26 // action is triggered by the store/order/updated webhook27 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:
1import { applyParams, save, ActionOptions } from "gadget-server";23export const run: ActionRun = async ({4 params,5 record,6 logger,7 api,8 connections,9}) => {10 applyParams(params, record);11 await save(record);12};1314export const onSuccess: ActionOnSuccess = async ({15 params,16 record,17 logger,18 api,19 connections,20}) => {21 // get the API client for the store22 const bigcommerce = await connections.bigcommerce.forStoreHash(record.storeHash!);2324 // 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 necessary30 await bigcommerce.v2.put("/orders/{order_id}", {31 path: {32 order_id: record.bigcommerceId,33 },34 body: {35 // fields to update on order36 discount_amount: 10.0,37 },38 });39 }40};4142export const options: ActionOptions = {43 actionType: "update",44};
1import { applyParams, save, ActionOptions } from "gadget-server";23export const run: ActionRun = async ({4 params,5 record,6 logger,7 api,8 connections,9}) => {10 applyParams(params, record);11 await save(record);12};1314export const onSuccess: ActionOnSuccess = async ({15 params,16 record,17 logger,18 api,19 connections,20}) => {21 // get the API client for the store22 const bigcommerce = await connections.bigcommerce.forStoreHash(record.storeHash!);2324 // 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 necessary30 await bigcommerce.v2.put("/orders/{order_id}", {31 path: {32 order_id: record.bigcommerceId,33 },34 body: {35 // fields to update on order36 discount_amount: 10.0,37 },38 });39 }40};4142export 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.