Shopify Module
Run a workflow for every Shopify order, customer or product change, and work the store from your flows: read, find, create and update products, stock, orders and customers; cancel, close and fulfil orders; run any Admin GraphQL query.
The Shopify Module adds one Start event on its own Channel, the Shopify Start (Shopify), and four Tasks that work on any Channel. Typical uses: send a WhatsApp confirmation and create a CRM record for every paid order; tag VIP customers after their fifth order; alert the team when a product's stock drops; fulfil an order and set its tracking number when the warehouse confirms a shipment; keep a product catalogue in sync from a supplier file.
Requirements
- A Shopify store and a custom app on it: Settings → Apps and sales channels → Develop apps → Create an app. Give it the Admin API access scopes of what the flow does (
read_orders/write_orders,read_products/write_products,read_inventory/write_inventory,read_customers/write_customers,read_fulfillments/write_fulfillments,read_locations), install it, and copy its Admin API access token (shpat_…) and API secret key. - Admin API version 2025-01 or later (the default is
2026-01). - The Shopify Start needs the engine to be reachable from Shopify over https (
Engine:PublicBaseUrl).
Configuration
The Shopify Module is configured per Environment, so Development and Production can use different stores.
| Field | Type | Required | Description |
|---|---|---|---|
| Store domain | string | Yes | The store's myshopify.com domain, e.g. my-store.myshopify.com (a custom domain does not work for the API). |
| Admin API access token | password | Yes | The custom app's access token (shpat_…). Stored encrypted. |
| Webhook signing secret | password | For the Start | What Shopify signs deliveries with. Webhooks the deploy registers (and any webhook created through the API) are signed with the custom app's API secret key; webhooks created in Settings → Notifications → Webhooks are signed with the secret shown on that page. |
| API version | string | No | YYYY-MM. Empty: 2026-01. |
| Default inventory location | string | No | Name or id of the location Set / Adjust inventory use when the Task leaves its Location empty. Empty: the store's first active location. |
Shopify Start
Starts the Process when the store sends a webhook for one of the listed topics. The event is available as {shopify}.
| Field | Type | Default | Description |
|---|---|---|---|
| Topics | string | orders/create | Comma-separated webhook topics as Shopify names them: orders/create, orders/paid, orders/fulfilled, orders/cancelled, orders/updated, customers/create, customers/update, products/create, products/update, inventory_levels/update, fulfillments/create, refunds/create, draft_orders/create, checkouts/create… At least one. |
| Register the webhooks in Shopify on deploy | boolean | on | Every deploy creates the webhook subscriptions for the topics in the store (through the custom app's token) and removes the ones no longer listed; deploying a version without the Start removes them. Off: create the webhooks yourself in Shopify (Settings → Notifications → Webhooks, format JSON) with the address the deploy log shows: {engine}/api/ShopifyModule/webhook/{environment token}. |
| Event variable | variable | {shopify} | Receives the event. |
A deploy is refused when the Topics list is empty or malformed, when the Environment has no store settings or no webhook signing secret, or when the engine's public address is not set and the webhooks are to be registered automatically. Only the subscriptions at this Environment's address are touched: other apps' and other Environments' webhooks stay.
Every delivery is checked against the signing secret (X-Shopify-Hmac-Sha256) and refused when it comes from another store; Shopify's retries of the same event (X-Shopify-Event-Id) start the flow once. A topic the Start does not list fails the run with a message naming it (remove the webhook in Shopify, or add the topic).
{shopify}
| Field | Description |
|---|---|
topic, kind, action | orders/create → orders, create. Branch on {shopify.topic} when several topics are listed. |
shop | The store (my-store.myshopify.com). |
id, gid | The resource's id (as text) and global id (gid://shopify/Order/…). |
resource | The payload as Shopify sends it, snake_case: {shopify.resource.name}, {shopify.resource.email}, {shopify.resource.total_price}, {shopify.resource.line_items.0.title}, {shopify.resource.shipping_address.city}. |
eventId, webhookId, triggeredAt, apiVersion | Delivery details. |
from, to, channel, isTest | Shared by every Start: the customer's email (or the store) and the store; Shopify, or Chat in a test. |
Test chat. Testing the flow from the chat runs the Start with a sample order (or customer / product, by the first topic) with {shopify.isTest} = true. Test data can set topic, shop, email, name, total, orderName, title, or a whole resource as JSON.
Shopify products
One Task, seven operations. The result is written to the Output variable.
| Operation | Fields | Output |
|---|---|---|
| Get product | Product: id, global id or handle | The product: id, legacyId, title, handle, status, vendor, productType, tags, descriptionHtml, totalInventory, onlineStoreUrl, featuredImage.url, priceRangeV2.minVariantPrice.amount, options, variants (each id, sku, price, compareAtPrice, inventoryQuantity, selectedOptions, inventoryItem.id). |
| Find products | Search query (Shopify syntax: title:*shirt* status:active tag:sale vendor:Acme), Maximum results (1–250, default 25) | A list of products, most recently updated first. |
| Create product | Title (required), Description (HTML), Vendor, Product type, Status (empty: draft), Price (of the default variant), Tags | The new product. |
| Update product | Product, then any of Title, Description, Vendor, Product type, Status, Add tags, Remove tags | The updated product. |
| Get inventory | Variant: SKU, id or global id; Location (empty: every location) | variantId, sku, title, productId, productTitle, inventoryItemId, tracked, available (the sum) and levels (per location: location, locationId, available, on_hand, committed, incoming, reserved). |
| Set inventory | Variant, Location (empty: the Environment's default), Quantity (the new available quantity), Reason | The same shape, for that location. |
| Adjust inventory | Variant, Location, Quantity (the change, e.g. -1), Reason | The same shape, for that location. |
Reasons are Shopify's: correction (default), received, damaged, shrinkage, restock, cycle_count_available, movement_created… Stock can only be changed on variants whose quantity Shopify tracks.
Shopify orders
| Operation | Fields | Output |
|---|---|---|
| Get order | Order: number (#1001), id or global id | The order: id, legacyId, name, email, phone, createdAt, displayFinancialStatus, displayFulfillmentStatus, currencyCode, subtotalPrice, totalShippingPrice, totalTax, totalDiscounts, totalPrice, totalRefunded (each {amount, currencyCode}), customer, shippingAddress, billingAddress, shippingLine, lineItems (each title, quantity, sku, variantTitle, originalUnitPrice, discountedTotal), fulfillments (each status, trackingInfo), customAttributes, note, tags. |
| Find orders | Search query (financial_status:paid fulfillment_status:unfulfilled email:ada@example.com created_at:>2026-01-01 tag:vip), Maximum results | A list of orders, newest first. |
| Update order | Order, then any of Note, Customer email, Add tags, Remove tags | The updated order. |
| Cancel order | Order, Reason (customer, declined, fraud, inventory, staff, other), Refund the customer, Restock the items, Notify the customer, Staff note | The order, read back (Shopify cancels in the background: cancelledAt may still be empty). |
| Close order | Order | The closed (archived) order. |
| Fulfill order | Order, Tracking number, Carrier, Tracking URL, Notify the customer | orderName, fulfillmentOrderIds and fulfillment (id, status, trackingInfo). Every open item of the order is fulfilled in one fulfillment; an order with nothing left to fulfil fails. |
Shopify customers
| Operation | Fields | Output |
|---|---|---|
| Get customer | Customer: email, id or global id | The customer: id, legacyId, firstName, lastName, displayName, email, phone, note, tags, state, numberOfOrders, amountSpent, defaultAddress, addresses, emailMarketingConsent.marketingState. |
| Find customers | Search query (tag:vip country:IT orders_count:>5), Maximum results | A list of customers, newest first. |
| Create customer | Email or Phone (one required), First name, Last name, Note, Tags, Subscribed to email marketing | The new customer. |
| Update customer | Customer, then any of Email, Phone, First name, Last name, Note, Add tags, Remove tags | The updated customer. |
Shopify GraphQL
For anything the other Tasks don't cover (discounts, metafields, collections, draft orders, shop details…).
| Field | Description |
|---|---|
| GraphQL query | A query or mutation document. Declare its variables ($id: ID!). |
| Variables (JSON) | A JSON object with the variables. {placeholders} inside its string values are replaced with flow variables, e.g. {"id": "gid://shopify/Order/{shopify.resource.id}"}. |
| Output variable | The response's data, tidied. |
Example: query($id: ID!) { order(id: $id) { name metafield(namespace: "custom", key: "gift") { value } } } with {"id": "gid://shopify/Order/{shopify.resource.id}"} → {result.order.metafield.value}.
A mutation whose payload reports userErrors fails the Task with their messages.
Results and errors
Every result is a plain object: GraphQL connections come back as lists, money bags (totalPriceSet.shopMoney) as totalPrice: {amount, currencyCode}, and every object with a global id also has legacyId (the number). Webhook payloads keep Shopify's own keys.
A missing product, order or customer, an empty required field, settings missing in the Environment and a malformed value are task errors (fix the flow or the settings). Shopify refusing the token (401/403: check the token and the app's scopes), a rate limit that two retries did not clear (429 / THROTTLED), a mutation's userErrors and an unreachable store are module errors. Give the Tasks an error flow.