# Shopify Webhook

Trigger a workflow from any Shopify event topic.

The Shopify Webhook trigger fires your workflow whenever a selected event occurs in your store. Any Shopify webhook topic is supported.

## The `data` object

For Shopify Webhook triggers, `data` is the **raw Shopify webhook payload** passed directly as the top-level object. There is no wrapper — you access order fields, customer fields, etc. directly:

```js
export class Workflow {
  async start(data, headers, api) {
    // data IS the order (for orders/* topics)
    console.log('Order ID:', data.id);
    console.log('Total:', data.total_price);
    console.log('Customer email:', data.email);
  }
}
```

## Useful headers

The `headers` object includes all Shopify webhook headers:

| Header | Description |
| --- | --- |
| `x-shopify-shop-domain` | Your store's myshopify.com domain |
| `x-shopify-topic` | The webhook topic, e.g. `orders/paid` |
| `x-shopify-webhook-id` | Unique ID for this delivery |
| `x-shopify-attempt-number` | Which retry attempt this is (1 = first) |

```js
export class Workflow {
  async start(data, headers, api) {
    const shop = headers['x-shopify-shop-domain'];
    const topic = headers['x-shopify-topic'];
    console.log(`Received ${topic} from ${shop}`);
  }
}
```

## Common webhook topics

| Category | Topics |
| --- | --- |
| Orders | `orders/create`, `orders/updated`, `orders/paid`, `orders/fulfilled`, `orders/cancelled`, `orders/partially_fulfilled` |
| Customers | `customers/create`, `customers/update`, `customers/delete` |
| Products | `products/create`, `products/update`, `products/delete` |
| Inventory | `inventory_levels/update`, `inventory_items/update` |
| Refunds | `refunds/create` |
| Checkouts | `checkouts/create`, `checkouts/update`, `checkouts/delete` |
| Fulfilments | `fulfillments/create`, `fulfillments/update` |
| Collections | `collections/create`, `collections/update`, `collections/delete` |

## Calling the Shopify API from within the workflow

Use the global `fetch()` with your store's myshopify.com URL. The platform automatically injects the `X-Shopify-Access-Token` header — no manual token management needed. Use `env.SHOPIFY_STORE` and `env.SHOPIFY_API_VERSION` which are always available:

```js
export class Workflow {
  async start(data, headers, api) {
    // Token is injected automatically — env.SHOPIFY_STORE is "mystore.myshopify.com"
    const resp = await fetch(
      `https://${env.SHOPIFY_STORE}/admin/api/${env.SHOPIFY_API_VERSION}/graphql.json`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          query: `mutation UpdateOrderNote($input: OrderInput!) {
            orderUpdate(input: $input) {
              order { id note }
              userErrors { field message }
            }
          }`,
          variables: { input: { id: `gid://shopify/Order/${data.id}`, note: 'Processed by JsWorkflows' } },
        }),
      }
    );

    const { data: gqlData } = await resp.json();
    const { order, userErrors } = gqlData.orderUpdate;
    if (userErrors.length) { console.log('Errors:', userErrors); return; }
    console.log('Updated note:', order.note);
  }
}
```

## Deduplication

Shopify retries undelivered webhooks up to 19 times over 48 hours. JsWorkflows automatically deduplicates deliveries using the `x-shopify-webhook-id` header — if a webhook ID has already been processed, subsequent retries are silently ignored within a 120-second window.

> Caution: Only **active** workflows receive and process incoming webhooks. Inactive workflows ignore all events, even while the toggle is off.