t spacing regardless of request latency.
Implementation:
{
"name": "ThrottledBatchProcessor",
"nodes": [
{
"name": "SourceTrigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [-400, 200],
"parameters": {
"path": "batch-ingest",
"responseMode": "lastNode"
}
},
{
"name": "BatchSplitter",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [-200, 200],
"parameters": {
"batchSize": 1,
"options": {}
}
},
{
"name": "ServiceCaller",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [0, 200],
"parameters": {
"method": "GET",
"url": "https://api.provider.com/v1/data/{{ $json.itemId }}",
"responseFormat": "json"
}
},
{
"name": "DelayNode",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [200, 200],
"parameters": {
"resume": "timeInterval",
"amount": 6,
"unit": "seconds"
}
},
{
"name": "ResultAggregator",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [400, 200],
"parameters": {}
}
],
"connections": {
"SourceTrigger": {"main": [[{"node": "BatchSplitter", "type": "main", "index": 0}]]},
"BatchSplitter": {"main": [[{"node": "ServiceCaller", "type": "main", "index": 0}], [{"node": "ResultAggregator", "type": "main", "index": 0}]]},
"ServiceCaller": {"main": [[{"node": "DelayNode", "type": "main", "index": 0}]]},
"DelayNode": {"main": [[{"node": "BatchSplitter", "type": "main", "index": 0}]]}
}
}
Rationale:
- BatchSize=1: Ensures serialization. Without this, all items would hit the Wait node simultaneously, creating parallel pauses that violate rate limits.
- Delay Placement: The Wait node follows the HTTP request. This guarantees a minimum interval between calls, accounting for variable network latency.
- Loop Connection: The Delay node output connects back to the BatchSplitter, creating a controlled iteration loop.
Pattern 2: Human-in-the-Loop Approval Gate
Scenario: Requiring manager approval before processing a high-value transaction.
Architecture: Suspend execution until a webhook signal is received. The resume URL is dynamically generated and embedded in a notification message.
Implementation:
// Approval Gate Configuration
// Node: ApprovalRequest (Wait Mode: webhook)
const approvalConfig = {
resume: "webhook",
resumeUrlExpression: "={{ $execution.resumeUrl }}",
options: {
responseCode: 200
}
};
// Email Notification Payload
const emailPayload = {
to: "manager@company.com",
subject: "Action Required: Transaction Approval",
body: `
Transaction ID: {{ $json.txnId }}
Amount: {{ $json.amount }}
Click here to approve: {{ $execution.resumeUrl }}?action=approve
Click here to reject: {{ $execution.resumeUrl }}?action=reject
`
};
// Post-Resume Logic
// Node: ApprovalRouter (Switch/IF)
const approvalRouter = {
conditions: [
{
value: "={{ $json.action }}",
operation: "equals",
output: 0, // Approve path
match: "approve"
},
{
value: "={{ $json.action }}",
operation: "equals",
output: 1, // Reject path
match: "reject"
}
]
};
Rationale:
- Dynamic URL:
$execution.resumeUrl provides a unique, secure endpoint for each execution instance.
- Query Parameters: Appending
?action=approve allows the workflow to capture the decision context upon resumption.
- State Preservation: All transaction data remains available after resumption, eliminating the need to re-fetch context.
Pattern 3: Context-Aware Scheduling
Scenario: Sending a follow-up message at a specific future time determined by dynamic data (e.g., business hours calculation).
Architecture: Use the dateTime resume mode with an expression that calculates the target timestamp.
Implementation:
{
"name": "ScheduledFollowUp",
"nodes": [
{
"name": "EventTrigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [-300, 100],
"parameters": {
"path": "signup-event"
}
},
{
"name": "WelcomeMessage",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 1,
"position": [-100, 100],
"parameters": {
"to": "={{ $json.email }}",
"subject": "Welcome!"
}
},
{
"name": "FollowUpScheduler",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [100, 100],
"parameters": {
"resume": "dateTime",
"dateTime": "={{ $json.nextContactTime }}",
"options": {}
}
},
{
"name": "CheckInMessage",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 1,
"position": [300, 100],
"parameters": {
"to": "={{ $json.email }}",
"subject": "How are you doing?"
}
}
],
"connections": {
"EventTrigger": {"main": [[{"node": "WelcomeMessage", "type": "main", "index": 0}]]},
"WelcomeMessage": {"main": [[{"node": "FollowUpScheduler", "type": "main", "index": 0}]]},
"FollowUpScheduler": {"main": [[{"node": "CheckInMessage", "type": "main", "index": 0}]]}
}
}
Rationale:
- Expression-Based Timing: Allows scheduling based on calculated values rather than fixed intervals.
- Atomicity: The workflow remains a single execution unit, simplifying monitoring and error handling compared to splitting into separate cron-triggered workflows.
Pitfall Guide
Production environments reveal subtle failure modes when implementing state suspension. The following pitfalls address common mistakes and provide remediation strategies.
| Pitfall Name | Explanation | Fix |
|---|
| Timeout Mismatch | The global execution timeout may be shorter than the wait duration. If timeout is 10 minutes and wait is 1 hour, the workflow terminates before resuming. | Increase the workflow timeout setting or use sub-workflows with independent timeout configurations. |
| Parallel Wait Explosion | When multiple items pass through a Wait node without serialization, each item creates a separate paused execution. 100 items result in 100 concurrent pauses, increasing DB load. | Use Split In Batches with batchSize=1 to serialize waits, or implement a queue-based approach for high-volume scenarios. |
| Resume URL Lifecycle | The generated webhook URL is only valid while the execution is paused. Calling it after completion or failure returns a 404 error. | Implement idempotent checks in the calling system. Verify execution status before invoking the resume URL. |
| Network Topology Blindness | If n8n is hosted on localhost or behind a firewall, the resume URL is unreachable from external systems (e.g., email clients, third-party APIs). | Use a tunneling service (ngrok, cloudflared) or deploy n8n to a publicly accessible endpoint. |
| Cloud Retention Limits | n8n Cloud plans impose maximum wait durations (e.g., 30 days on higher tiers). Exceeding this limit causes execution failure. | Check plan limits before designing long waits. For extended durations, consider self-hosted deployments or external scheduling. |
| Recurrence Misconception | The Wait node pauses once; it does not loop automatically. Using it for periodic tasks results in single execution only. | Use the Schedule Trigger node for recurring tasks. Reserve Wait nodes for one-time suspensions within a workflow. |
| Error Handling Gaps | Errors occurring before the Wait node may leave the workflow in an inconsistent state if not handled properly. | Implement error workflows or retry mechanisms before the Wait node. Ensure critical state is saved prior to suspension. |
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Delay < 1 minute | Wait Node (Time Interval) | Simple implementation; low overhead. | Low |
| Delay > 30 days | External Scheduler + Webhook | Avoids cloud retention limits; scalable. | Medium |
| Human Approval | Wait Node (Webhook) | Native integration; preserves state. | Low |
| Recurring Task | Schedule Trigger | Designed for periodic execution; no state suspension needed. | Low |
| Rate-Limited API | Wait Node + Batch Splitter | Ensures consistent spacing; prevents throttling. | Low |
| Dynamic Scheduling | Wait Node (DateTime) | Flexible timing based on data; atomic execution. | Low |
Configuration Template
Use this template to standardize Wait node configurations across workflows. Adjust parameters based on the specific suspension mode.
{
"name": "StandardWaitConfiguration",
"nodes": [
{
"name": "PausePoint",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [0, 0],
"parameters": {
"resume": "timeInterval",
"amount": 5,
"unit": "minutes",
"options": {
"responseCode": 200
}
}
}
],
"connections": {},
"settings": {
"executionTimeout": 3600,
"saveExecutionProgress": true
}
}
Usage Notes:
- Change
resume to webhook, dateTime, or form as needed.
- Adjust
amount and unit for time-based waits.
- Set
executionTimeout in workflow settings to accommodate long pauses.
- Enable
saveExecutionProgress for better monitoring of suspended states.
Quick Start Guide
- Add Wait Node: Drag a Wait node into your workflow canvas.
- Select Mode: Choose the appropriate suspension mode (Time Interval, DateTime, Webhook, or Form).
- Configure Parameters: Set duration, target time, or webhook options based on your use case.
- Connect Output: Link the Wait node output to the next step in your workflow.
- Execute and Test: Run the workflow and verify that execution pauses and resumes as expected. Monitor execution logs for state transitions.