AutomationFlowsData & Sheets › Order Intake

Order Intake

01-order-intake. Uses postgres, httpRequest, stopAndError. Webhook trigger; 16 nodes.

Webhook trigger★★★★☆ complexity16 nodesPostgresHTTP RequestStop And Error
Data & Sheets Trigger: Webhook Nodes: 16 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → Postgres recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "orderintake00001",
  "name": "01-order-intake",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "authentication": "headerAuth",
        "path": "order-intake",
        "responseMode": "responseNode",
        "options": {
          "rawBody": true
        }
      },
      "id": "10000000-0000-4000-8000-000000000001",
      "name": "Order Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        300
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "manual",
        "includeOtherFields": true,
        "assignments": {
          "assignments": [
            {
              "id": "cfg1",
              "name": "UPSTREAM_API_URL",
              "type": "string",
              "value": "http://mock-api:3000/process"
            }
          ]
        },
        "options": {}
      },
      "id": "10000000-0000-4000-8000-000000000016",
      "name": "Workflow Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.5,
      "position": [
        110,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const crypto = require('crypto');\nconst item = $input.first();\nconst secret = $env.ORDER_INTAKE_HMAC_SECRET || '';\nconst raw = item.binary && item.binary.data\n  ? Buffer.from(item.binary.data.data, 'base64').toString('utf8')\n  : JSON.stringify(item.json.body || {});\nconst headers = item.json.headers || {};\nconst provided = String(headers['x-signature'] || '').replace(/^sha256=/, '').trim();\nconst expected = crypto.createHmac('sha256', secret).update(raw).digest('hex');\nlet authOk = false;\ntry {\n  authOk = provided.length === expected.length &&\n    crypto.timingSafeEqual(Buffer.from(provided, 'hex'), Buffer.from(expected, 'hex'));\n} catch (e) { authOk = false; }\nconst body = item.json.body || {};\nconst idempotencyKey = String(headers['idempotency-key'] || body.order_id || '');\nreturn [{ json: { authOk, idempotencyKey, order_id: body.order_id ?? null, amount: body.amount ?? null, body } }];"
      },
      "id": "10000000-0000-4000-8000-000000000002",
      "name": "Verify HMAC",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "auth-cond",
              "leftValue": "={{ $json.authOk }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "10000000-0000-4000-8000-000000000003",
      "name": "Auth OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ ({ status: 'unauthorized', error: 'invalid or missing signature' }) }}",
        "options": {
          "responseCode": 401
        }
      },
      "id": "10000000-0000-4000-8000-000000000004",
      "name": "Respond Unauthorized",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        660,
        480
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const j = $input.first().json;\nconst body = j.body || {};\nconst errors = [];\nif (!body.order_id || typeof body.order_id !== 'string') errors.push('order_id is required and must be a string');\nif (typeof body.amount !== 'number' || !(body.amount > 0)) errors.push('amount is required and must be a positive number');\nreturn [{ json: { ...j, validationOk: errors.length === 0, errors } }];"
      },
      "id": "10000000-0000-4000-8000-000000000005",
      "name": "Validate Order",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        220
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "valid-cond",
              "leftValue": "={{ $json.validationOk }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "10000000-0000-4000-8000-000000000006",
      "name": "Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        880,
        220
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ ({ status: 'invalid', errors: $json.errors }) }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "10000000-0000-4000-8000-000000000007",
      "name": "Respond Invalid",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1100,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH ins AS (INSERT INTO idempotency_keys (idempotency_key, order_id, execution_id) VALUES ($1, $2, $3) ON CONFLICT (idempotency_key) DO NOTHING RETURNING idempotency_key) SELECT EXISTS (SELECT 1 FROM ins) AS inserted;",
        "options": {
          "queryReplacement": "={{ [$('Verify HMAC').item.json.idempotencyKey, $('Verify HMAC').item.json.order_id, $execution.id] }}"
        }
      },
      "id": "10000000-0000-4000-8000-000000000008",
      "name": "Idempotency Insert",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.7,
      "position": [
        1100,
        160
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "new-cond",
              "leftValue": "={{ $json.inserted }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "10000000-0000-4000-8000-000000000009",
      "name": "Is New?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1320,
        160
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ ({ status: 'duplicate', order_id: $('Verify HMAC').item.json.order_id }) }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "10000000-0000-4000-8000-000000000010",
      "name": "Respond Duplicate",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1540,
        320
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Workflow Config').first().json.UPSTREAM_API_URL }}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ order_id: $('Verify HMAC').item.json.order_id, amount: $('Verify HMAC').item.json.amount }) }}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "10000000-0000-4000-8000-000000000011",
      "name": "Call Upstream",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1540,
        100
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 500,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ ({ status: 'ok', order_id: $('Verify HMAC').item.json.order_id }) }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "10000000-0000-4000-8000-000000000012",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1760,
        20
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO dead_letter (workflow, execution_id, order_id, reason, payload) VALUES ($1, $2, $3, $4, $5::jsonb)",
        "options": {
          "queryReplacement": "={{ ['01-order-intake', $execution.id, $('Verify HMAC').item.json.order_id, 'upstream call failed after retries', JSON.stringify($('Verify HMAC').item.json.body)] }}"
        }
      },
      "id": "10000000-0000-4000-8000-000000000013",
      "name": "DLQ Insert",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.7,
      "position": [
        1760,
        220
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ ({ status: 'failed', order_id: $('Verify HMAC').item.json.order_id, detail: 'upstream unavailable; order dead-lettered' }) }}",
        "options": {
          "responseCode": 502
        }
      },
      "id": "10000000-0000-4000-8000-000000000014",
      "name": "Respond Failure",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1980,
        220
      ]
    },
    {
      "parameters": {
        "errorType": "errorMessage",
        "errorMessage": "={{ 'order-intake: upstream failed after retries for order ' + $('Verify HMAC').item.json.order_id }}"
      },
      "id": "10000000-0000-4000-8000-000000000015",
      "name": "Stop And Error",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        2200,
        220
      ]
    }
  ],
  "connections": {
    "Order Webhook": {
      "main": [
        [
          {
            "node": "Workflow Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Config": {
      "main": [
        [
          {
            "node": "Verify HMAC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify HMAC": {
      "main": [
        [
          {
            "node": "Auth OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Auth OK?": {
      "main": [
        [
          {
            "node": "Validate Order",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Unauthorized",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Order": {
      "main": [
        [
          {
            "node": "Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid?": {
      "main": [
        [
          {
            "node": "Idempotency Insert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Invalid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Idempotency Insert": {
      "main": [
        [
          {
            "node": "Is New?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is New?": {
      "main": [
        [
          {
            "node": "Call Upstream",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Upstream": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "DLQ Insert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DLQ Insert": {
      "main": [
        [
          {
            "node": "Respond Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond Failure": {
      "main": [
        [
          {
            "node": "Stop And Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "errorWorkflow": "globalerrhandler"
  },
  "active": false
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

01-order-intake. Uses postgres, httpRequest, stopAndError. Webhook trigger; 16 nodes.

Source: https://github.com/jarrod-wright/n8n-workflow-hardening/blob/main/01-order-intake/workflow.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

CMM. Uses httpRequest, postgres, redis. Webhook trigger; 90 nodes.

HTTP Request, Postgres, Redis
Data & Sheets

This solution enables you to manage all your Notion and Todoist tasks from different workspaces as well as your calendar events in a single place. This is 2 way sync with partial support for recurring

Redis, Notion, Todoist +6
Data & Sheets

Notion to Clockify Sync Template. Uses scheduleTrigger, clockify, compareDatasets, stopAndError. Webhook trigger; 68 nodes.

Clockify, Stop And Error, Notion +1
Data & Sheets

This workflow synchronizes three entities from Notion to Clockify, allowing tracked time to be linked to client-related projects or tasks.

Clockify, Stop And Error, Notion +1
Data & Sheets

Notion to Clockify Sync Template. Uses clockify, stopAndError, notion, httpRequest. Webhook trigger; 68 nodes.

Clockify, Stop And Error, Notion +1