Scheduling Steps Schedule subsequent steps and build multi-step or fan-out workflows. api.scheduleNextStep() queues the next step to execute after a delay. Calling it multiple times in the same step creates a fan-out — parallel branches that each run independently. Signature await api.scheduleNextStep({ delay, action, payload }); | Option | Type | Description | | delay | number \ | string | Seconds until the next step runs. Accepts a number or a duration string. Minimum: 10 seconds. Maximum: 400 days | | action | string | The method name on your Workflow class to call. It must be a named continuation step, not start | | payload | any | Data passed as the data argument to the next step | Duration strings Instead of raw seconds, you can pass a human-readable string: | String | Duration | | "30 sec" / "30 seconds" | 30 seconds | | "5 min" / "5 minutes" | 5 minutes | | "2 hr" / "2 hours" | 2 hours | | "1 day" / "3 days" | 1 or 3 days | | "1 week" / "2 weeks" | 1 or 2 weeks | Unsupported strings throw an error. Months and years are not supported because they are not a fixed number of seconds. Simple linear chain export class Workflow { async start(data, headers, api) { // Schedule the next step to run in 10 minutes await api.scheduleNextStep({ delay: '10 min', action: 'sendReminder', payload: { orderId: data.id, email: data.email }, }); } async sendReminder({ orderId, email }, headers, api) { console.log('Sending reminder for order', orderId, 'to', email); // ... send email via fetch() } } Fan-out (parallel branches) Call scheduleNextStep() multiple times in one step to launch parallel branches. Each call is an independent branch. The run is not marked complete until all branches finish. export class Workflow { async start(data, headers, api) { // Process each line item in parallel for (const item of data.lineitems) { await api.scheduleNextStep({ delay: 10, action: 'processItem', payload: { itemId: item.id, title: item.title }, }); } // start() completes here — all N branches are now running concurrently } async processItem({ itemId, title }, headers, api) { console.log('Processing item:', title); // Each branch runs independently } } Note: Credits are consumed per step execution. A fan-out of 10 branches uses 10 credits for those branches, plus 1 for the start step that scheduled them. Chain depth Each call to scheduleNextStep() increments the chain depth. Fan-out branches at the same level all share the same depth counter — a fan-out of 100 branches from start has depth 1, not 100. There is no per-plan chain depth limit. A platform-wide circuit breaker prevents runaway infinite loops. Deduplication — api.dedupe() api.dedupe() prevents a step from running more than once for the same logical event within a time window. It is useful for business-level idempotency, duplicate suppression on HTTP or external-event workflows, and other workflow-specific safety rules. const { locked } = await api.dedupe(uniqueid, delay); | Argument | Type | Default | Description | | uniqueid | string | required | An identifier that should be unique per logical event | | delay | number | 120 | How long (in seconds) to hold the lock before it expires | Returns { locked: boolean, key: string }. If locked is true, another invocation already acquired the lock — skip processing. For Shopify webhook triggers, duplicate delivery protection already happens before your workflow code runs. api.dedupe() is still useful there when you want additional workflow-specific or business-level deduplication. When you do use it in a Shopify webhook start(...), prefer headers['x-shopify-event-id'] as the primary dedupe key and fall back to a stable payload-based key if the header is missing. export class Workflow { async start(data, headers, api) { const dedupeKey = headers?.['x-shopify-event-id'] | | order-${data.admingraphqlapiid ?? data.id}; const { locked } = await api.dedupe(dedupeKey, 300); if (locked) { console.log('Duplicate event for order', data.id, '- skipping'); return; } // Safe to process — this invocation holds the lock await api.scheduleNextStep({ delay: 10, action: 'processOrder', payload: data }); } }