Your First Workflow
This guide builds a workflow that responds to a paid order and demonstrates the step pattern — the core of how JsWorkflows handles work that takes more than a few seconds.
The problem steps solve
Section titled “The problem steps solve”When Shopify fires a webhook (e.g. orders/paid), your start step must respond within a few seconds or Shopify will retry the delivery. That rules out sending emails, calling multiple APIs, waiting for rate limits, or doing any real processing inside start.
The solution is to use start only to acknowledge and queue, then do the real work in subsequent steps that run asynchronously after Shopify has already received your response.
What we’re building
Section titled “What we’re building”A workflow that:
- Receives an
orders/paidwebhook and immediately returns to Shopify (fast, reliable) - Waits 5 minutes, then sends a Slack notification with order details
The workflow class
Section titled “The workflow class”Create a new workflow in the editor with the orders/paid trigger, then replace the code with:
class Workflow { /** * Step 1 — runs immediately when the webhook fires. * Do as little as possible here: validate, deduplicate, then schedule real work. */ async start(data, headers, api) { // data IS the Shopify order object — no wrapper const orderId = data.id; const orderName = data.name; // e.g. "#1001" const total = data.total_price; const currency = data.currency;
// Prevent duplicate processing if Shopify retries the webhook const { locked } = await api.dedupe(`order-notify:${orderId}`); if (locked) { api.log(`Order ${orderName} already queued, skipping duplicate.`); return; }
// Schedule the real work to run 5 minutes from now, after Shopify // has already received our response. Pass whatever the next step needs. await api.scheduleNextStep({ delay: '5 min', action: 'notifySlack', payload: { orderId, orderName, total, currency }, });
api.log(`Queued Slack notification for ${orderName}`); // start() returns here — Shopify gets a fast response }
/** * Step 2 — runs 5 minutes later, completely independently of step 1. * All the real work happens here. */ async notifySlack({ orderId, orderName, total, currency }, headers, api) { const { token, error } = await api.getOAuthToken('my-slack'); if (error) { api.log('Could not get Slack token:', error); return; }
const res = await fetch('https://slack.com/api/chat.postMessage', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ channel: '#orders', text: `*New paid order* ${orderName} — ${total} ${currency}`, }), });
const json = await res.json(); if (!json.ok) { api.log('Slack error:', json.error); return; }
api.log(`Notified Slack for order ${orderName}`); }}What’s happening
Section titled “What’s happening”The start step does three things and exits immediately:
- Extracts the fields the next step will need from the order payload
- Calls
api.dedupe()to skip duplicate webhook deliveries - Calls
api.scheduleNextStep()to queuenotifySlackfor 5 minutes later
Shopify’s webhook delivery gets a response in under a second. No Shopify retries, no timeouts.
The notifySlack step runs 5 minutes later in a fresh worker invocation. It has full access to the platform API and as much time as it needs to make external calls.
Test it step by step
Section titled “Test it step by step”-
In the workflow editor open More actions → Test data. This shows the Request body and Request headers — both are editable.
For Shopify webhook triggers the request body is pre-filled with the topic’s sample payload. You can view that sample at any time (read-only) via More actions → Sample payload.
Edit the request body down to just the fields this workflow needs:
{"id": 12345,"name": "#1001","total_price": "99.00","currency": "USD"} -
Click More actions → Run test. The workflow runs from
startexactly as on a live trigger — thenotifySlackstep will be scheduled and executed after the configured delay. A Live Output panel opens below the editor showing a real-time table of step executions: status, duration, step name, retries, and payload size for each step as it runs.
Enable it
Section titled “Enable it”Toggle the workflow Active. From this point every orders/paid event your store fires will run this workflow.
Next steps
Section titled “Next steps”- Learn everything about steps, fan-out, and best practices
- Explore the full Shopify Webhook trigger
- Browse all workflow api methods