{
  "id": "NEImGuMG5P2Ug9KMWyn0R",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "LINE OA \u2013 Stateless Email Approval for Orders",
  "tags": [],
  "nodes": [
    {
      "id": "6eab5afa-3308-47c6-8914-d24128b8d30c",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        25664,
        8608
      ],
      "parameters": {
        "width": 540,
        "height": 776,
        "content": "## How it works\n1. **Google Sheets Trigger** fires when a new row is added to your Orders sheet\n2. **New order?** skips rows that already have a status \u2014 preventing duplicate processing\n3. **Config** node sets your reviewer email, LINE User ID, approval threshold, and n8n webhook base URL\n4. A unique approval token is generated and written back to the sheet row\n5. If the order amount is **below** the threshold \u2192 auto-approved immediately with a LINE push notification\n6. If the amount **meets** the threshold \u2192 an HTML email is sent with independent Approve / Reject links (one email per order, no blocking)\n7. **Webhook B** handles each link click: validates the token, checks the order has not already been reviewed, then updates Google Sheets and sends a LINE push notification\n\n## Setup steps\n1. Create a **Google Sheets OAuth2** credential \u2192 assign to the *Google Sheets Trigger* node\n2. Create a **Google Sheets Service Account** credential \u2192 assign to all other Google Sheets nodes\n3. Update `YOUR_GOOGLE_SHEET_URL` in every Google Sheets node with your spreadsheet URL\n4. Create a **Gmail OAuth2** credential \u2192 assign to the *Send Approval Request* node\n5. Create an **HTTP Bearer Auth** credential with your LINE Channel Access Token \u2192 assign to both LINE Push nodes\n6. In the **Config** node: set `approverEmail`, `lineUserId`, `approvalThreshold`, and `n8nWebhookBaseUrl` (e.g. `https://your-domain.zeabur.app/webhook` or `https://your-instance.app.n8n.cloud/webhook`)\n7. Required sheet columns: `orderId` \u00b7 `customerName` \u00b7 `amount` \u00b7 `approvalToken` \u00b7 `status`\n8. Activate the workflow \u2014 it will automatically process new rows and send LINE notifications"
      },
      "typeVersion": 1
    },
    {
      "id": "4dd06ec8-aa34-49c6-94bb-907d2b139063",
      "name": "Section A",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        26272,
        8832
      ],
      "parameters": {
        "color": 7,
        "width": 1160,
        "height": 168,
        "content": "## \u26a1 Section A \u2013 Order Intake\n\nPolls Google Sheets every minute. Fires for new rows only. Skips rows that already have a `status` value to prevent duplicate processing. Generates a unique approval token per order."
      },
      "typeVersion": 1
    },
    {
      "id": "85d8890d-d80b-48d3-9340-8111af285b71",
      "name": "Email Path",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        27568,
        8640
      ],
      "parameters": {
        "color": 7,
        "width": 700,
        "height": 380,
        "content": "## \ud83d\udce7 Email Review Path\n\nAmount meets the threshold. Generates independent Approve / Reject links (orderId + token), marks the row as Pending, and emails the reviewer. Each email is stateless \u2014 multiple orders can wait for review simultaneously."
      },
      "typeVersion": 1
    },
    {
      "id": "ff49d8f6-4977-4974-970d-cc2c86b787bb",
      "name": "Auto Path",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        27584,
        9088
      ],
      "parameters": {
        "color": 7,
        "width": 620,
        "height": 312,
        "content": "## \u2705 Auto-Approve Path\n\nAmount is below threshold. Marks the order as Auto-Approved immediately and sends a LINE push notification. No email required."
      },
      "typeVersion": 1
    },
    {
      "id": "73737ef4-9a99-41bd-8fef-31c14f783db4",
      "name": "Section B",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        26272,
        9408
      ],
      "parameters": {
        "color": 7,
        "width": 780,
        "height": 168,
        "content": "## \ud83d\udd17 Section B \u2013 Approval Handler\n\nTriggered when a reviewer clicks Approve or Reject in the email. Validates the one-time token, checks the order has not already been processed, then routes to approve or reject. Each click runs as a fully independent execution."
      },
      "typeVersion": 1
    },
    {
      "id": "f99fc61f-88e4-49ab-8915-43e1513b6e3d",
      "name": "Approval Actions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        27600,
        9424
      ],
      "parameters": {
        "color": 7,
        "width": 876,
        "height": 568,
        "content": "## \ud83c\udfc1 Approval Actions\n\nUpdates Google Sheets with the final status (Approved / Rejected), records a `reviewedAt` timestamp, then sends a LINE push notification. Returns an HTML confirmation page to the reviewer's browser."
      },
      "typeVersion": 1
    },
    {
      "id": "ac6e3843-0468-45d1-8061-e5ae5b7d25f8",
      "name": "Sticky Note \u2013 More Templates",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        28512,
        9600
      ],
      "parameters": {
        "color": 7,
        "width": 412,
        "height": 240,
        "content": "## Want more automation templates?\n\nCheck out the **Content Automation Bundle** \u2014 6 n8n workflows for AI content creation, social media publishing, and Threads analytics.\n\n\ud83d\udc49 https://jasonchuang0818.gumroad.com/l/n8n-content-automation-bundle"
      },
      "typeVersion": 1
    },
    {
      "id": "b4cead6b-f732-4dc6-97f3-5c90abd6c152",
      "name": "Google Sheets Trigger",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        26288,
        9040
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Orders"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_GOOGLE_SHEET_URL"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "39f1f5af-62b1-4efb-b607-e3a5a69e4cc3",
      "name": "New order?",
      "type": "n8n-nodes-base.if",
      "position": [
        26512,
        9040
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.status }}",
              "operation": "isEmpty"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "46fd5076-d227-4411-a505-b8a9c2f80c1d",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        26736,
        9040
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-01",
              "name": "approverEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "cfg-02",
              "name": "lineUserId",
              "type": "string",
              "value": "YOUR_LINE_USER_ID"
            },
            {
              "id": "cfg-03",
              "name": "approvalThreshold",
              "type": "number",
              "value": 500
            },
            {
              "id": "cfg-04",
              "name": "n8nWebhookBaseUrl",
              "type": "string",
              "value": "https://YOUR_N8N_DOMAIN/webhook"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "53714208-292c-4f6f-80fc-207f317ee83f",
      "name": "Prepare Order Data",
      "type": "n8n-nodes-base.set",
      "position": [
        26944,
        9040
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "prep-01",
              "name": "orderId",
              "type": "string",
              "value": "={{ $('Google Sheets Trigger').item.json.orderId }}"
            },
            {
              "id": "prep-02",
              "name": "customerName",
              "type": "string",
              "value": "={{ $('Google Sheets Trigger').item.json.customerName }}"
            },
            {
              "id": "prep-03",
              "name": "amount",
              "type": "number",
              "value": "={{ $('Google Sheets Trigger').item.json.amount }}"
            },
            {
              "id": "prep-04",
              "name": "approverEmail",
              "type": "string",
              "value": "={{ $json.approverEmail }}"
            },
            {
              "id": "prep-05",
              "name": "lineUserId",
              "type": "string",
              "value": "={{ $json.lineUserId }}"
            },
            {
              "id": "prep-06",
              "name": "approvalThreshold",
              "type": "number",
              "value": "={{ $json.approvalThreshold }}"
            },
            {
              "id": "prep-07",
              "name": "n8nWebhookBaseUrl",
              "type": "string",
              "value": "={{ $json.n8nWebhookBaseUrl }}"
            },
            {
              "id": "prep-08",
              "name": "approvalToken",
              "type": "string",
              "value": "={{ $('Google Sheets Trigger').item.json.orderId + '-' + $now.toMillis() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c85d872c-7c8d-4756-b619-956ab72fd700",
      "name": "Write Approval Token",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        27168,
        9040
      ],
      "parameters": {
        "columns": {
          "value": {
            "orderId": "={{ $json.orderId }}",
            "approvalToken": "={{ $json.approvalToken }}"
          },
          "schema": [
            {
              "id": "orderId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "orderId",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "approvalToken",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "approvalToken",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "orderId"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Orders"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_GOOGLE_SHEET_URL"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 4.4
    },
    {
      "id": "df92024d-4e3b-4dde-94f3-883599efa579",
      "name": "Needs Approval?",
      "type": "n8n-nodes-base.if",
      "position": [
        27392,
        9040
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $('Prepare Order Data').item.json.amount }}",
              "value2": "={{ $('Prepare Order Data').item.json.approvalThreshold }}",
              "operation": "largerEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9525d87e-1d7c-48d8-a6c3-f278635b5286",
      "name": "Build Approval Links",
      "type": "n8n-nodes-base.set",
      "position": [
        27616,
        8832
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "link-01",
              "name": "approveUrl",
              "type": "string",
              "value": "={{ $('Prepare Order Data').item.json.n8nWebhookBaseUrl + '/order-approval?orderId=' + $('Prepare Order Data').item.json.orderId + '&action=approve&token=' + $('Prepare Order Data').item.json.approvalToken }}"
            },
            {
              "id": "link-02",
              "name": "rejectUrl",
              "type": "string",
              "value": "={{ $('Prepare Order Data').item.json.n8nWebhookBaseUrl + '/order-approval?orderId=' + $('Prepare Order Data').item.json.orderId + '&action=reject&token=' + $('Prepare Order Data').item.json.approvalToken }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a3b43dda-bc7f-43ae-9b20-ff6914f975cc",
      "name": "Mark as Pending Approval",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        27824,
        8832
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "\u23f3 Pending Approval",
            "orderId": "={{ $('Prepare Order Data').item.json.orderId }}"
          },
          "schema": [
            {
              "id": "orderId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "orderId",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "orderId"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Orders"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_GOOGLE_SHEET_URL"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 4.4
    },
    {
      "id": "3a4c6ca1-1ef5-413a-b409-e63f29dac9e3",
      "name": "Send Approval Request",
      "type": "n8n-nodes-base.gmail",
      "position": [
        28048,
        8832
      ],
      "parameters": {
        "sendTo": "={{ $('Prepare Order Data').item.json.approverEmail }}",
        "message": "=<h2 style=\"color:#1a1a1a\">Order Approval Required</h2>\n<p>The following order exceeds your approval threshold and requires your review:</p>\n<table style=\"border-collapse:collapse;margin-bottom:16px\">\n  <tr><td style=\"padding:6px 16px 6px 0;color:#666\"><b>Order ID</b></td><td>#{{ $('Prepare Order Data').item.json.orderId }}</td></tr>\n  <tr><td style=\"padding:6px 16px 6px 0;color:#666\"><b>Customer</b></td><td>{{ $('Prepare Order Data').item.json.customerName }}</td></tr>\n  <tr><td style=\"padding:6px 16px 6px 0;color:#666\"><b>Amount</b></td><td><strong>${{ $('Prepare Order Data').item.json.amount }}</strong></td></tr>\n</table>\n<p>\n  <a href=\"{{ $('Prepare Order Data').item.json.n8nWebhookBaseUrl }}/order-approval?orderId={{ $('Prepare Order Data').item.json.orderId }}&amp;action=approve&amp;token={{ $('Prepare Order Data').item.json.approvalToken }}\" style=\"background:#22c55e;color:white;padding:12px 28px;text-decoration:none;border-radius:6px;font-weight:bold;display:inline-block\">\u2705 Approve</a>\n  &nbsp;&nbsp;\n  <a href=\"{{ $('Prepare Order Data').item.json.n8nWebhookBaseUrl }}/order-approval?orderId={{ $('Prepare Order Data').item.json.orderId }}&amp;action=reject&amp;token={{ $('Prepare Order Data').item.json.approvalToken }}\" style=\"background:#ef4444;color:white;padding:12px 28px;text-decoration:none;border-radius:6px;font-weight:bold;display:inline-block\">\u274c Reject</a>\n</p>\n<p style=\"color:#999;font-size:12px\">Each link can only be used once. Multiple orders can be reviewed independently \u2014 clicking one link does not affect any others.</p>",
        "options": {},
        "subject": "=\ud83d\udd14 Approval Required: Order #{{ $('Prepare Order Data').item.json.orderId }} \u2014 ${{ $('Prepare Order Data').item.json.amount }}"
      },
      "typeVersion": 2.1,
      "continueOnFail": true
    },
    {
      "id": "fbdf392e-2014-45d7-b032-2dcd80e41822",
      "name": "Mark as Auto-Approved",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        27616,
        9232
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "\u2705 Auto-Approved",
            "orderId": "={{ $('Prepare Order Data').item.json.orderId }}"
          },
          "schema": [
            {
              "id": "orderId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "orderId",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "orderId"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Orders"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_GOOGLE_SHEET_URL"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 4.4
    },
    {
      "id": "684ce42a-21a1-4279-b963-77826f2bb11b",
      "name": "Prepare LINE Notification (Auto)",
      "type": "n8n-nodes-base.set",
      "position": [
        27824,
        9232
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "line-auto-01",
              "name": "lineMessage",
              "type": "string",
              "value": "=\u2705 Order auto-approved!\nOrder: #{{ $('Prepare Order Data').item.json.orderId }}\nCustomer: {{ $('Prepare Order Data').item.json.customerName }}\nAmount: ${{ $('Prepare Order Data').item.json.amount }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c96e6c80-c9aa-4982-93f3-a391a0980bb3",
      "name": "LINE Push \u2013 Auto Approved",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        28048,
        9232
      ],
      "parameters": {
        "url": "https://api.line.me/v2/bot/message/push",
        "body": "={{ JSON.stringify({ to: $('Prepare Order Data').item.json.lineUserId, messages: [{ type: 'text', text: $json.lineMessage }] }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpBearerAuth"
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "00faff5c-fc18-4d61-92f2-0a81d969e569",
      "name": "Webhook \u2013 Approval Handler",
      "type": "n8n-nodes-base.webhook",
      "position": [
        26288,
        9616
      ],
      "parameters": {
        "path": "order-approval",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "25532be8-3a02-4255-a0d6-51a9eb80a188",
      "name": "Extract Approval Params",
      "type": "n8n-nodes-base.set",
      "position": [
        26512,
        9616
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ext-01",
              "name": "orderId",
              "type": "string",
              "value": "={{ $json.query.orderId }}"
            },
            {
              "id": "ext-02",
              "name": "action",
              "type": "string",
              "value": "={{ $json.query.action }}"
            },
            {
              "id": "ext-03",
              "name": "webhookToken",
              "type": "string",
              "value": "={{ $json.query.token }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f3ffa329-3af4-4ee6-a892-46505e36e32f",
      "name": "Read Order from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        26736,
        9616
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.orderId }}",
              "lookupColumn": "orderId"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Orders"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_GOOGLE_SHEET_URL"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 4.4
    },
    {
      "id": "b578a9c1-0b51-4aef-b7cf-c70a73492837",
      "name": "Token Valid?",
      "type": "n8n-nodes-base.if",
      "position": [
        26944,
        9616
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.approvalToken }}",
              "value2": "={{ $('Extract Approval Params').item.json.webhookToken }}"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "855ef536-e483-4db3-b969-1c2160aed595",
      "name": "Respond \u2013 Invalid Token",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        27168,
        9808
      ],
      "parameters": {
        "options": {
          "responseCode": 403,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "<!DOCTYPE html><html><body style=\"font-family:sans-serif;text-align:center;padding:60px\"><h2 style=\"color:#ef4444\">\u274c Invalid or Expired Link</h2><p>This approval link is invalid. Please contact your administrator.</p></body></html>"
      },
      "typeVersion": 1.1
    },
    {
      "id": "4c7ab7e6-1686-4f72-b711-3a497a9caf2f",
      "name": "Already Processed?",
      "type": "n8n-nodes-base.if",
      "position": [
        27168,
        9616
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.status }}",
              "value2": "\u23f3 Pending Approval",
              "operation": "notEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5be37fa2-70c9-4c8a-b00e-8343689996e6",
      "name": "Respond \u2013 Already Processed",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        27408,
        9440
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "=<!DOCTYPE html><html><body style=\"font-family:sans-serif;text-align:center;padding:60px\"><h2 style=\"color:#f59e0b\">\u26a0\ufe0f Already Processed</h2><p>Order #{{ $('Extract Approval Params').item.json.orderId }} has already been reviewed.</p><p>Current status: <strong>{{ $json.status }}</strong></p></body></html>"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d1b28499-b7eb-485a-8ca8-ec11208cbe8a",
      "name": "Approve or Reject?",
      "type": "n8n-nodes-base.if",
      "position": [
        27392,
        9616
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $('Extract Approval Params').item.json.action }}",
              "value2": "approve"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7b4adf5f-f036-4c9d-8b4a-ad341d96e16c",
      "name": "Mark as Approved",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        27632,
        9600
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "\u2705 Approved",
            "orderId": "={{ $json.orderId }}",
            "reviewedAt": "={{ $now.toFormat('yyyy-MM-dd HH:mm:ss') }}"
          },
          "schema": [
            {
              "id": "orderId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "orderId",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "reviewedAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "reviewedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "orderId"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Orders"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_GOOGLE_SHEET_URL"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 4.4
    },
    {
      "id": "779352ed-2b2e-427d-abe4-48bf86fb5a8d",
      "name": "Prepare LINE Notification (Approved)",
      "type": "n8n-nodes-base.set",
      "position": [
        27840,
        9600
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "line-appr-01",
              "name": "lineMessage",
              "type": "string",
              "value": "=\u2705 Order approved!\nOrder: #{{ $json.orderId }}\nCustomer: {{ $json.customerName }}\nAmount: ${{ $json.amount }}"
            },
            {
              "id": "line-appr-02",
              "name": "lineUserId",
              "type": "string",
              "value": "={{ $json.lineUserId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "3f6148b2-e810-4704-ae6e-9c0a17684420",
      "name": "LINE Push \u2013 Approved",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        28064,
        9600
      ],
      "parameters": {
        "url": "https://api.line.me/v2/bot/message/push",
        "body": "={{ JSON.stringify({ to: $json.lineUserId, messages: [{ type: 'text', text: $json.lineMessage }] }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpBearerAuth"
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "6e08156e-62f0-4dd6-b9eb-3bf72e198209",
      "name": "Respond \u2013 Order Approved",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        28288,
        9600
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "=<!DOCTYPE html><html><body style=\"font-family:sans-serif;text-align:center;padding:60px\"><h2 style=\"color:#22c55e\">\u2705 Order Approved</h2><p>Order #{{ $('Read Order from Sheets').item.json.orderId }} has been approved.</p><p>A LINE notification has been sent.</p></body></html>"
      },
      "typeVersion": 1.1
    },
    {
      "id": "90f76d61-27bc-456b-bc3f-067ad2f534ba",
      "name": "Mark as Rejected",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        27632,
        9808
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "\u274c Rejected",
            "orderId": "={{ $json.orderId }}",
            "reviewedAt": "={{ $now.toFormat('yyyy-MM-dd HH:mm:ss') }}"
          },
          "schema": [
            {
              "id": "orderId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "orderId",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "reviewedAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "reviewedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "orderId"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Orders"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "YOUR_GOOGLE_SHEET_URL"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 4.4
    },
    {
      "id": "9cfedf3f-f33e-4051-892c-20572209b68d",
      "name": "Prepare LINE Notification (Rejected)",
      "type": "n8n-nodes-base.set",
      "position": [
        27840,
        9808
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "line-rej-01",
              "name": "lineMessage",
              "type": "string",
              "value": "=\u274c Order rejected.\nOrder: #{{ $('Read Order from Sheets').item.json.orderId }}\nCustomer: {{ $('Read Order from Sheets').item.json.customerName }}\nAmount: ${{ $('Read Order from Sheets').item.json.amount }}"
            },
            {
              "id": "line-rej-02",
              "name": "lineUserId",
              "type": "string",
              "value": "={{ $('Read Order from Sheets').item.json.lineUserId }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f4313550-714b-4eb8-9a53-3c0b6ad02529",
      "name": "LINE Push \u2013 Rejected",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        28064,
        9808
      ],
      "parameters": {
        "url": "https://api.line.me/v2/bot/message/push",
        "body": "={{ JSON.stringify({ to: $json.lineUserId, messages: [{ type: 'text', text: $json.lineMessage }] }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpBearerAuth"
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "fc99ddab-933c-47bb-a923-7cf50ce07c5e",
      "name": "Respond \u2013 Order Rejected",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        28288,
        9808
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "=<!DOCTYPE html><html><body style=\"font-family:sans-serif;text-align:center;padding:60px\"><h2 style=\"color:#ef4444\">\u274c Order Rejected</h2><p>Order #{{ $('Read Order from Sheets').item.json.orderId }} has been rejected.</p><p>The status has been updated in the system.</p></body></html>"
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "timezone": "Asia/Taipei",
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "timeSavedMode": "fixed",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "216415cf-cc14-4183-add9-a579bf83d47a",
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Prepare Order Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New order?": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Token Valid?": {
      "main": [
        [
          {
            "node": "Already Processed?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond \u2013 Invalid Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Approval?": {
      "main": [
        [
          {
            "node": "Build Approval Links",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark as Auto-Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Approved": {
      "main": [
        [
          {
            "node": "Prepare LINE Notification (Approved)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Rejected": {
      "main": [
        [
          {
            "node": "Prepare LINE Notification (Rejected)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Already Processed?": {
      "main": [
        [
          {
            "node": "Respond \u2013 Already Processed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Approve or Reject?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approve or Reject?": {
      "main": [
        [
          {
            "node": "Mark as Approved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark as Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Order Data": {
      "main": [
        [
          {
            "node": "Write Approval Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Approval Links": {
      "main": [
        [
          {
            "node": "Mark as Pending Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Approval Token": {
      "main": [
        [
          {
            "node": "Needs Approval?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets Trigger": {
      "main": [
        [
          {
            "node": "New order?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Auto-Approved": {
      "main": [
        [
          {
            "node": "Prepare LINE Notification (Auto)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LINE Push \u2013 Approved": {
      "main": [
        [
          {
            "node": "Respond \u2013 Order Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LINE Push \u2013 Rejected": {
      "main": [
        [
          {
            "node": "Respond \u2013 Order Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Order from Sheets": {
      "main": [
        [
          {
            "node": "Token Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Approval Params": {
      "main": [
        [
          {
            "node": "Read Order from Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Pending Approval": {
      "main": [
        [
          {
            "node": "Send Approval Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook \u2013 Approval Handler": {
      "main": [
        [
          {
            "node": "Extract Approval Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare LINE Notification (Auto)": {
      "main": [
        [
          {
            "node": "LINE Push \u2013 Auto Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare LINE Notification (Approved)": {
      "main": [
        [
          {
            "node": "LINE Push \u2013 Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare LINE Notification (Rejected)": {
      "main": [
        [
          {
            "node": "LINE Push \u2013 Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}