# BigCommerce webhook subscriptions  This guide will show you how to subscribe to [BigCommerce webhooks](https://developer.bigcommerce.com/docs/integrations/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](https://docs.gadget.dev/guides/actions/types-of-actions#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](https://github.com/Space48/bigcommerce-api-js). ```json // in sample BigCommerce store/product/created payload { "id": 113, "storeHash": "wvpfsac141", "type": "product" } ``` For more information on reading and writing to BigCommerce, see the [BigCommerce data guide](https://docs.gadget.dev/guides/plugins/bigcommerce/data). ### 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](https://docs.gadget.dev/guides/plugins/bigcommerce/data#storing-data-in-gadget). If you are calling another global action from your webhook-triggered action, the `storeHash` can be [passed in the `params` object](https://docs.gadget.dev/guides/actions/code#accepting-more-parameters). Here is an example of a model action that fetches the `storeHash` and uses it to initialize the BigCommerce API: ```typescript import { applyParams, save, ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ params, record }) => { applyParams(params, record); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ record, api, connections }) => { // get the storeHash using the storeId from the current record as a selection criteria in a model related to a store const storeId = record.storeId; const store = await api.bigcommerce.store.findById(storeId, { select: { storeHash: true }, }); // use the storeHash to get a BigCommerce API client for the current store const bigcommerce = await connections.bigcommerce.forStoreHash(store.storeHash); // ... use the BigCommerce API client }; export const options: ActionOptions = { actionType: "create", }; ``` ## 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: ```typescript import { ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ params, logger, api, connections }) => { // get the order ID from the webhook payload const orderId = params.id; // get the BigCommerce API client for the current shop const bigcommerce = connections.bigcommerce.current; // get the order data const order = await bigcommerce.v2.get("/orders/{order_id}", { path: { order_id: orderId, }, }); // do custom work with the order // in this case, a secret discount! if (order.customer_message.includes("Gadget")) { // update the same order! await bigcommerce.v2.put("/orders/{order_id}", { path: { order_id: orderId, }, body: { // fields to update on order discount_amount: 10.0, }, }); } }; export const options: ActionOptions = { triggers: { api: false, bigcommerce: { // action is triggered by the store/order/updated webhook webhooks: ["store/order/updated"], }, }, }; ``` 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](https://docs.gadget.dev/api/example-app/development/gadget-record#change-tracking-dirty-tracking). 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: ```typescript import { ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ params, logger, api, connections }) => { // get the order ID from the webhook payload const orderId = params.id; // get the BigCommerce API client for the current shop const bigcommerce = connections.bigcommerce.current; // get the order data const order = await bigcommerce.v2.get(`/orders/${orderId}`); // see if the order is stored in Gadget const orderRecord = await api.bigcommerce.order.findFirst({ filter: { bigcommerceId: { equals: order.id } }, }); // save to Gadget by running the order update action await api.bigcommerce.order.update(orderRecord.id, { customerMessage: order.customer_message, }); }; export const options: ActionOptions = { triggers: { api: false, bigcommerce: { // action is triggered by the store/order/updated webhook webhooks: ["store/order/updated"], }, }, }; ``` 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: ```typescript import { applyParams, save, ActionOptions } from "gadget-server"; export const run: ActionRun = async ({ params, record, logger, api, connections, }) => { applyParams(params, record); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, connections, }) => { // get the API client for the store const bigcommerce = await connections.bigcommerce.forStoreHash(record.storeHash!); // if the customer_message hasn't changed, don't write to the store! if ( !record.changed("customerMessage") && record.customerMessage?.includes("Gadget") ) { // only write back to BigCommerce if necessary await bigcommerce.v2.put("/orders/{order_id}", { path: { order_id: record.bigcommerceId, }, body: { // fields to update on order discount_amount: 10.0, }, }); } }; export const options: ActionOptions = { actionType: "update", }; ``` 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](https://developer.bigcommerce.com/docs/integrations/webhooks#security).