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:
Fetch storeHash and use it to init the BigCommerce API clientJavaScript1import {2 applyParams,3 save,4 ActionOptions,5 CreateBigCommerceProductActionContext,6} from "gadget-server";78/**9 * @param { CreateBigCommerceProductActionContext } context10 */11export async function run({ params, record, logger, api, connections }) {12 applyParams(params, record);13 await save(record);14}1516/**17 * @param { CreateBigCommerceProductActionContext } context18 */19export async function onSuccess({ record, api, connections }) {20 // get the storeHash using the storeId from the current record as a selection criteria21 const store = await api.bigcommerce.store.findById(record.storeId, {22 select: { storeHash: true },23 });24 // use the storeHash to get a BigCommerce API client for the current store25 const bigcommerce = await connections.bigcommerce.forStoreHash(store.storeHash);2627 // ... use the BigCommerce API client28}2930/** @type { ActionOptions } */31export const options = {32 actionType: "create",33};
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:
Avoid doing this in a global action, webhook will loop infinitely!JavaScript1import { BigCommerceWebhookLoopSampleGlobalActionContext } from "gadget-server";23/**4 * @param { BigCommerceWebhookLoopSampleGlobalActionContext } context5 */6export async function run({ params, logger, api, connections }) {7 // get the order ID from the webhook payload8 const orderId = params.id;9 // get the BigCommerce API client for the current shop10 const bigcommerce = connections.bigcommerce.current;11 // get the order data12 const order = await bigcommerce.v2.get("/orders/{order_id}", {13 path: {14 order_id: orderId,15 },16 });1718 // do custom work with the order19 // in this case, a secret discount!20 if (order.customer_message.includes("Gadget")) {21 // update the same order!22 await bigcommerce.v2.put("/orders/{order_id}", {23 path: {24 order_id: orderId,25 },26 body: {27 // fields to update on order28 discount_amount: 10.0,29 },30 });31 }32}3334export const options = {35 triggers: {36 api: false,37 bigcommerce: {38 // action is triggered by the store/order/updated webhook39 webhooks: ["store/order/updated"],40 },41 },42};
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 dataJavaScript1import { BigCommerceWebhookLoopSampleGlobalActionContext } from "gadget-server";23/**4 * @param { BigCommerceWebhookLoopSampleGlobalActionContext } context5 */6export async function run({ params, logger, api, connections }) {7 // get the order ID from the webhook payload8 const orderId = params.id;9 // get the BigCommerce API client for the current shop10 const bigcommerce = connections.bigcommerce.current;11 // get the order data12 const order = await bigcommerce.v2.get(`/orders/${orderId}`);1314 // see if the order is stored in Gadget15 const orderRecord = await api.bigcommerce.order.findFirst({16 filter: { bigcommerceId: { equals: order.id } },17 });1819 // save to Gadget by running the order update action20 await api.bigcommerce.order.update(orderRecord.id, {21 customerMessage: customer_message,22 });23}2425export const options = {26 triggers: {27 api: false,28 bigcommerce: {29 // action is triggered by the store/order/updated webhook30 webhooks: ["store/order/updated"],31 },32 },33};
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.jsJavaScript1import {2 applyParams,3 save,4 ActionOptions,5 UpdateBigCommerceOrderActionContext,6} from "gadget-server";78/**9 * @param { UpdateBigCommerceOrderActionContext } context10 */11export async function run({ params, record, logger, api, connections }) {12 applyParams(params, record);13 await save(record);14}1516/**17 * @param { UpdateBigCommerceOrderActionContext } context18 */19export async function onSuccess({ params, record, logger, api, connections }) {20 // get the API client for the store21 const bigcommerce = await connections.bigcommerce.forStoreHash(record.storeHash);2223 // if the customer_message hasn't changed, don't write to the store!24 if (25 !record.changed("customerMessage") &&26 record.customerMessage.includes("Gadget")27 ) {28 // only write back to BigCommerce if necessary29 await bigcommerce.v2.put("/orders/{order_id}", {30 path: {31 order_id: record.bigcommerceId,32 },33 body: {34 // fields to update on order35 discount_amount: 10.0,36 },37 });38 }39}4041/** @type { ActionOptions } */42export const options = {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.