Executive Summary
This workflow processes Shopify orders via webhook, validates inventory, charges payment through Stripe, and triggers fulfillment via a third-party 3PL API. It handles approximately 200 orders per day reliably under normal conditions but has critical gaps in idempotency and error recovery that will cause duplicate charges and lost orders under webhook retry conditions.
Overall Score
38 / 72
Grade: C
Idempotency
3/12
Retry & Error Handling
5/12
Audit Trail
4/12
Secrets Management
10/12
Dead Letter Queue
6/12
Monitoring
10/12
1. Idempotency 3 / 12
Critical
Findings
- Webhook endpoint has no deduplication logic. If Shopify retries a webhook (which it does after a 5-second timeout), the workflow processes the same order twice. In testing, a simulated retry 8 seconds after the original webhook produced a duplicate
order_createdrecord and a second Stripe charge attempt. - The "Create Order" node writes to the database without checking if the order already exists. The Postgres INSERT node uses no
ON CONFLICTclause and has no preceding lookup node. Any replay of the same payload creates a duplicate row.
Recommendations
- Add a Function node immediately after the Webhook Trigger that hashes the payload (
X-Shopify-Hmac-SHA256header +order_id) and checks a deduplication table. If the hash exists, return early with a 200 response and skip processing. - Implement a lookup-before-insert pattern on the database write node: query for the
order_idfirst, and only proceed to INSERT if no row is found. Alternatively, add a unique constraint onorder_idand useON CONFLICT DO NOTHING.
2. Retry & Error Handling 5 / 12
At Risk
Findings
- The Stripe payment node has no retry configuration. A transient HTTP 429 (rate limit) or 503 from Stripe kills the entire workflow execution. At 200 orders/day, Stripe rate limits are plausible during burst periods (flash sales, restocks).
- No Error Trigger workflow exists. Failures are completely silent — the only way to detect a failed execution is to manually check the n8n execution list. There is no alerting, no notification, and no automated recovery.
- The fulfillment API call uses default timeout settings. The 3PL API occasionally responds in 15–20 seconds; the default n8n HTTP Request timeout of 10 seconds causes intermittent failures that appear as "network errors" in the execution log.
Recommendations
- Add per-node retry with exponential backoff on all external API call nodes: 3 retries at 2s / 4s / 8s intervals. n8n supports this natively in the node settings panel under "Settings > Retry On Fail."
- Create a dedicated Error Trigger workflow that: (a) sends a Slack message with the failed execution ID, node name, and error message; (b) writes the full failed payload to a dead-letter Postgres table for replay.
- Increase the HTTP Request timeout on the fulfillment node to 30 seconds, or add a dedicated timeout-and-retry wrapper.
3. Audit Trail 4 / 12
At Risk
Findings
- No structured logging exists. The only execution record is n8n's built-in execution history, which is pruned after 30 days by default. After that window, there is no record that an order was processed, what the payload contained, or whether it succeeded.
- No correlation ID is generated per webhook event. When debugging a failed order, there is no way to trace a single order's path through the workflow across multiple nodes without manually clicking through the execution detail view.
Recommendations
- Add a Function node at the very start of the workflow that generates a UUID v4 correlation ID and attaches it to the workflow's shared data context. Pass this ID through every subsequent node so it appears in all downstream API calls and database writes.
- Add a "Log to Postgres" node at three key checkpoints: (1) webhook received, (2) payment processed, (3) fulfillment triggered. Each log entry should include the correlation ID, timestamp, node name, and a status field.
4. Secrets Management 10 / 12
Passing
Findings
- API credentials are stored in n8n's built-in credential store. This is the correct approach — no hardcoded API keys in node configurations or Function node code.
- No external secrets manager integration. For the current scale (single instance, one operator), this is acceptable. It becomes a gap if the instance is shared across a team or if credentials need to be synchronized across environments.
- Credentials have not been rotated in 6+ months. The Stripe API key was created on initial setup and has never been cycled. The Shopify webhook signing secret is also original.
Recommendations
- Implement a quarterly credential rotation schedule. Set a calendar reminder and document the rotation procedure for each credential (Stripe, Shopify, 3PL, Postgres).
- Consider migrating to environment variable-based credential management (
N8N_CREDENTIALS_OVERWRITE_DATA) for easier rotation without touching the n8n UI.
5. Dead Letter Queue 6 / 12
At Risk
Findings
- No dead-letter mechanism exists. If a webhook payload fails processing, it is effectively lost after n8n's execution log is pruned. There is no persistent store of failed payloads that can be replayed.
- Partial failures have no recovery path. If payment succeeds but fulfillment fails (e.g., 3PL API is down), the order is charged but never shipped. There is no compensating transaction, no retry queue, and no alert. The customer is charged with no fulfillment.
Recommendations
- Add an Error Trigger workflow that writes failed payloads — including the full original webhook body, the node that failed, and the error message — to a dedicated
dead_lettersPostgres table with astatuscolumn (pending/replayed/discarded). - Implement a weekly review process: query the
dead_letterstable forstatus = 'pending', investigate each entry, and either replay it manually or mark it as discarded with a reason. - For the partial-failure scenario (payment OK, fulfillment failed): add an IF node after payment that gates fulfillment, and on fulfillment failure, trigger a refund workflow or at minimum an urgent Slack alert.
6. Monitoring 10 / 12
Passing
Findings
- n8n's built-in execution metrics are enabled and the dashboard is actively checked. The operator reviews the execution list daily, which provides basic visibility into success/failure rates.
- No external monitoring or uptime check exists. If the n8n instance goes down entirely (process crash, server restart, disk full), there is no alert. The operator would only discover the outage when orders stop appearing in the database.
- No SLA or latency tracking. There is no measurement of end-to-end execution time per order. Gradual performance degradation (e.g., the 3PL API slowing from 2s to 12s) would not be detected until it causes timeouts.
Recommendations
- Add an external uptime monitor (UptimeRobot, Better Stack, or similar) that pings the n8n instance's
/healthzendpoint every 60 seconds and alerts via Slack/email on failure. - Add a "duration logging" Function node at the end of the workflow that calculates
Date.now() - $workflow.startedAtand writes it to a metrics table. Set an alert threshold (e.g., > 30 seconds) that triggers investigation.
Action Plan
Priority Matrix
| Priority | Finding | Recommended Fix | Effort |
|---|---|---|---|
| P0 | Webhook has no deduplication | Add dedup hash table + lookup node | 2–3 hrs |
| P0 | No Error Trigger workflow | Create error handler with Slack alert + dead-letter write | 2–3 hrs |
| P1 | No structured logging | Add correlation ID + checkpoint log nodes | 3–4 hrs |
| P1 | No dead-letter table | Create Postgres DLQ + weekly review process | 2–3 hrs |
| P1 | No partial-failure recovery | Add fulfillment gate + compensating refund workflow | 4–6 hrs |
| P2 | Credential rotation overdue | Rotate all keys + set quarterly schedule | 1–2 hrs |
| P2 | No external uptime monitoring | Configure UptimeRobot + Slack alerts | 30 min |
| P2 | No latency tracking | Add duration logging + threshold alerts | 1–2 hrs |
What Happens Next