Skip to content

This guide builds a simple workflow from scratch so you can learn the basic JsWorkflows pattern before moving on to more advanced templates and operational workflows.

If you prefer, you can also start from a template instead of writing your first workflow yourself. This page focuses on the from-scratch path so you can understand how workflow steps are structured.

This video follows the same example workflow used in this guide: creating an orders/paid workflow, scheduling a follow-up step, testing the workflow, and checking the run output.

A workflow that:

  1. receives an orders/paid webhook
  2. schedules a follow-up step for 5 minutes later
  3. sends a Slack notification with the order details

This is a simple example, but it teaches a pattern you will use in more advanced workflows too, including approvals, operational checks, delayed follow-up, and multi-step automations.

For Shopify webhook workflows, JsWorkflows acknowledges the incoming webhook before your workflow code runs. Your start step is not responsible for sending the HTTP response back to Shopify.

Even so, it is usually best to keep start focused on orchestration:

  • validate the incoming data
  • prepare the payload for later steps
  • hand the real work off to another step

This matters because:

  • start is the reserved entry point for a run
  • start cannot be scheduled again with api.scheduleNextStep()
  • delayed or retryable work should happen in later named steps
  • scheduled steps run later as separate workflow executions

That makes workflows easier to recover, retry, extend, and reason about when something goes wrong downstream.

Create a new workflow in the editor with the orders/paid trigger, then replace the code with:

export class Workflow {
/**
* Entry step.
* Keep this focused on validation, payload preparation, and scheduling.
*/
async start(data, _headers, api) {
const orderId = data.id;
const orderName = data.name;
const total = data.total_price;
const currency = data.currency;
await api.scheduleNextStep({
delay: '5 min',
action: 'notifySlack',
payload: { orderId, orderName, total, currency },
});
console.log(`Queued Slack notification for ${orderName}`);
}
/**
* Follow-up step.
* This is where delayed or retryable work should happen.
*/
async notifySlack({ orderId, orderName, total, currency }, _headers, api) {
const { token, error } = await api.getOAuthToken('my-slack');
if (error || !token) {
console.log('Could not get Slack token:', error || 'Missing Slack token');
return;
}
const res = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
channel: 'C0123456789',
text: `*New paid order* ${orderName}${total} ${currency}`,
}),
});
if (!res.ok) {
console.log(`Slack HTTP ${res.status}:`, await res.text());
return;
}
const json = await res.json();
if (!json.ok) {
console.log('Slack API error:', json.error || 'unknown_error');
return;
}
console.log(`Notified Slack for order ${orderName}`);
}
}

In every step, the third argument api gives your workflow access to JsWorkflows platform helpers such as scheduling, OAuth tokens, logging, and state.

In this example:

  • api.scheduleNextStep() queues the later Slack step
  • api.getOAuthToken('my-slack') loads the access token for a saved Slack OAuth connection

The string 'my-slack' is the OAuth handle. You create it when you connect Slack in JsWorkflows, then reuse that handle in your workflow code.

To create that Slack connection:

  • go to Settings → OAuth2 Tokens, or
  • in the workflow editor open More actions → Manage OAuth2 tokens

Then connect Slack and give the connection a handle such as my-slack. Your workflow code uses that handle later with api.getOAuthToken(...).

The start step:

  1. reads the fields needed from the webhook payload
  2. schedules notifySlack for 5 minutes later
  3. exits after the follow-up work has been queued

The notifySlack step:

  1. gets the Slack OAuth token by handle
  2. posts the message to Slack using the channel ID
  3. logs the result

The scheduled step runs later as a separate execution. It is not the original start step waiting in memory for 5 minutes. The payload passed to api.scheduleNextStep() is what the later step receives when it starts.

This same pattern scales into more advanced workflows:

  • delayed follow-up checks
  • approval and review flows
  • fan-out processing
  • retryable external API operations

For Shopify webhook workflows, JsWorkflows already deduplicates duplicate webhook deliveries before your workflow code runs. You would typically use api.dedupe() yourself only when you need business-level deduplication for your own workflow logic.

  1. In the workflow editor, open More actions → Test data. This shows the Request body and Request headers, both of which are editable.

    For Shopify webhook triggers, the request body is pre-filled with the topic’s sample payload. You can also inspect the sample at any time through More actions → Sample payload.

  2. For a faster first test, temporarily change delay: '5 min' in the code to delay: 10. That is the minimum allowed delay and lets you verify the full flow quickly before switching it back to 5 minutes.

  3. Leave the sample payload as-is, or adjust a few fields such as the order name, total, or currency if you want to make the test output easier to recognize.

  4. Click More actions → Run test.

    The workflow starts from start exactly as a live run would. The notifySlack step is then scheduled and will run after the configured delay.

  5. Watch the Live Output panel below the editor. It shows step executions in real time, including status, duration, step name, retries, and payload size.

When you are ready, toggle the workflow Active.

From that point on, every orders/paid event that matches the workflow trigger can start a run.

Your first workflow does not need to be complicated. The important thing is to learn the basic structure:

  • start is the reserved entry step
  • start is best used for validation, payload preparation, and scheduling
  • later steps do the delayed, retryable, or external work

Once that pattern is clear, the rest of the platform becomes much easier to understand.