Skip to content

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.

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.

A workflow that:

  1. Receives an orders/paid webhook and immediately returns to Shopify (fast, reliable)
  2. Waits 5 minutes, then sends a Slack notification with order details

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}`);
}
}

The start step does three things and exits immediately:

  1. Extracts the fields the next step will need from the order payload
  2. Calls api.dedupe() to skip duplicate webhook deliveries
  3. Calls api.scheduleNextStep() to queue notifySlack for 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.

  1. 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"
    }
  2. Click More actions → Run test. The workflow runs from start exactly as on a live trigger — the notifySlack step 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.

Toggle the workflow Active. From this point every orders/paid event your store fires will run this workflow.