{
  "id": "DDMTmgdk71WkyCSu",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Real-time using WooCommerce Webhook",
  "tags": [],
  "nodes": [
    {
      "id": "fa1bf279-3572-473d-be40-cb188bc6dfe7",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2992,
        -240
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "02cfb150-eb94-4184-9fb2-1079012e4c1f",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        -1728,
        -304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "b0851e90-a23d-45d7-9ff9-8b2ac3058d31",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.percent_increase }}",
              "rightValue": 100
            },
            {
              "id": "71ef4b6f-f439-47a5-b1ed-7c5b381b06d7",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.current_returns }}",
              "rightValue": 25
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4542948e-6687-4eb7-9377-8513ccf0a34f",
      "name": "Send a message",
      "type": "n8n-nodes-base.slack",
      "position": [
        -1040,
        -320
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Return Surge Detected\n\nSKU: {{$json.sku}}\nIncrease: {{$json.percent_increase ?? 'N/A'}}%\n\nCurrent Returns: {{$json.current_returns}}\nPrevious Returns: {{$json.previous_returns}}\n\nReasons:\n{{ Object.entries($json.reasons ?? {})\n  .map(r => r[0] + \" : \" + r[1])\n  .join(\"\\n\") }}\n\nStatus: {{$json.status}}\nRun Date (UTC): {{$json.run_date_utc}}\nCooldown Key: {{$json.cooldown_key}}\nAction: Please review recent orders, packaging, and QC logs for this SKU.\n",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0A3WQEKQ58",
          "cachedResultName": "n8n-demo"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "9af0266e-0136-4605-9c04-1b1974633b9a",
      "name": "Create a record",
      "type": "n8n-nodes-base.airtable",
      "position": [
        -528,
        -320
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "app6M3NseJ4VEy2Ro",
          "cachedResultUrl": "https://airtable.com/app6M3NseJ4VEy2Ro",
          "cachedResultName": "n8n Learning"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblT2vWZ8en2ZRdox",
          "cachedResultUrl": "https://airtable.com/app6M3NseJ4VEy2Ro/tblT2vWZ8en2ZRdox",
          "cachedResultName": "woocommerce"
        },
        "columns": {
          "value": {
            "sku": "={{ $json.sku }}",
            "resons": "={{ $json.reasons_raw }}",
            "cooldown_key": "={{ $json.cooldown_key }}",
            "run_date_utc": "={{ $json.run_date_utc }}",
            "current_returns": "={{ $json.current_returns }}",
            "percent_increase": "={{ $json.percent_increase }}",
            "previous_returns": "={{ $json.previous_returns }}"
          },
          "schema": [
            {
              "id": "sku",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sku",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "current_returns",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "current_returns",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "previous_returns",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "previous_returns",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "percent_increase",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "percent_increase",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "run_date_utc",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "run_date_utc",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "options",
              "display": true,
              "options": [],
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cooldown_key",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "cooldown_key",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "resons",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "resons",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "0e2a51dd-9df1-4ec1-8fd4-00076b35ecb9",
      "name": "Orders_Fetch",
      "type": "n8n-nodes-base.code",
      "position": [
        -2128,
        -256
      ],
      "parameters": {
        "jsCode": "// Get data from specific nodes by name\nconst orders = $items(\"HTTP Orders\");\nconst refunds = $items(\"HTTP Refunds\");\n\nconst output = [];\n\n// Create lookup map for orders\nconst orderMap = {};\nfor (const o of orders) {\n  orderMap[o.json.id] = o.json;\n}\n\n// Loop refunds and attach line items\nfor (const r of refunds) {\n  const refund = r.json;\n  const order = orderMap[refund.parent_id];\n\n  if (!order || !order.line_items?.length) continue;\n\n  for (const li of order.line_items) {\n    output.push({\n      order_id: refund.parent_id,\n      refund_id: refund.id,\n      sku: li.sku || li.name || li.product_id,\n      quantity: li.quantity,\n      reason: refund.reason,\n      refund_date_utc: refund.date_created_gmt\n    });\n  }\n}\n\nreturn output.map(o => ({ json: o }));\n "
      },
      "typeVersion": 2
    },
    {
      "id": "22b0ae06-01a6-4ae9-b9eb-2c253b7bef54",
      "name": "Refund_details",
      "type": "n8n-nodes-base.code",
      "position": [
        -1984,
        -256
      ],
      "parameters": {
        "jsCode": "// ================= CONFIG =================\nconst WINDOW_HOURS = 24;\n\n// ================= TIME WINDOWS =================\nconst now = new Date();\nconst currentStart = new Date(now.getTime() - WINDOW_HOURS * 60 * 60 * 1000);\nconst previousStart = new Date(currentStart.getTime() - WINDOW_HOURS * 60 * 60 * 1000);\n\n// ================= AGGREGATION MAP =================\nconst stats = {}; // \u2705 THIS WAS MISSING (root cause)\n\n// ================= PROCESS RETURNS =================\nfor (const item of items) {\n  const sku =\n    item.json.sku ||\n    item.json.product_id ||\n    \"UNKNOWN_SKU\";\n\n  const refundDateRaw = item.json.refund_date_utc;\n  const reason = item.json.reason || \"Unknown\";\n\n  if (!refundDateRaw) continue;\n\n  const refundDate = new Date(refundDateRaw);\n  if (isNaN(refundDate)) continue;\n\n  if (!stats[sku]) {\n    stats[sku] = {\n      current: 0,\n      previous: 0,\n      reasons: {}\n    };\n  }\n\n  // \ud83d\udc49 Count refunds (NOT quantity \u2014 more reliable)\n  if (refundDate >= currentStart) {\n    stats[sku].current += 1;\n  } else if (refundDate >= previousStart && refundDate < currentStart) {\n    stats[sku].previous += 1;\n  }\n\n  stats[sku].reasons[reason] =\n    (stats[sku].reasons[reason] || 0) + 1;\n}\n\n// ================= BUILD OUTPUT =================\nconst output = [];\n\nfor (const sku in stats) {\n  const current = stats[sku].current;\n  const previous = stats[sku].previous;\n\n  const absolute_increase = current - previous;\n\n  const percent_increase =\n    previous === 0\n      ? (current > 0 ? null : 0) // null = no baseline\n      : Math.round((absolute_increase / previous) * 100);\n\n  output.push({\n    json: {\n      sku,\n      current_returns: current,\n      previous_returns: previous,\n      absolute_increase,\n      percent_increase,\n      reasons: stats[sku].reasons\n    }\n  });\n}\n\nreturn output;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "748f44df-885d-498e-b355-ac5e2936a753",
      "name": "Time_window",
      "type": "n8n-nodes-base.code",
      "position": [
        -2672,
        -240
      ],
      "parameters": {
        "jsCode": "const now = new Date();\nconst WINDOW_HOURS = 24;\nconst ms = WINDOW_HOURS * 60 * 60 * 1000;\n\nconst currentEnd = now;\nconst currentStart = new Date(now.getTime() - ms);\nconst prevEnd = currentStart;\nconst prevStart = new Date(prevEnd.getTime() - ms);\n\nfunction iso(d) {\n  return d.toISOString().split('.')[0] + 'Z';\n}\n\nreturn [\n  {\n    json: {\n      timeWindow: {\n        currentStart: iso(currentStart),\n        currentEnd: iso(currentEnd),\n        prevStart: iso(prevStart),\n        prevEnd: iso(prevEnd),\n      }\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ecbf350d-858c-4959-9e1a-77f59ad8772d",
      "name": "HTTP Orders",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2480,
        -368
      ],
      "parameters": {
        "url": "https://{your_wocommerce_domain}/wp-json/wc/v3/orders",
        "options": {},
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "queryParameters": {
          "parameters": [
            {}
          ]
        }
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "2e89c61b-d2b4-444c-8b91-36ecaefded9e",
      "name": "HTTP Refunds",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2288,
        -368
      ],
      "parameters": {
        "url": "=https://{your_wocommerce_domain}/wp-json/wc/v3/refunds",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth"
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "retryOnFail": false,
      "typeVersion": 4.3,
      "alwaysOutputData": false
    },
    {
      "id": "805bd643-a0da-4bac-9767-a01201809d24",
      "name": "Edit Fields1",
      "type": "n8n-nodes-base.set",
      "position": [
        -1472,
        -320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "27d7d91b-4e21-4bb8-8c61-bbb9d91f3eb3",
              "name": "status",
              "type": "string",
              "value": "alerted"
            },
            {
              "id": "36e7c447-0b6f-41c1-b133-3295deeca6a5",
              "name": "run_date_utc",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            },
            {
              "id": "289792bc-e6bd-4525-a18f-c5a9b1c983d0",
              "name": "cooldown_key",
              "type": "string",
              "value": "={{ $json.sku + \"_\" + new Date().toISOString().slice(0,10) }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "bfd46423-5da8-43f6-8224-8620810c15c7",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        -768,
        -320
      ],
      "parameters": {
        "jsCode": "const output = [];\n\nfor (const item of items) {\n  const text = item.json.message?.text || \"\";\n\n  // Helper function\n  const getMatch = (regex) => {\n    const m = text.match(regex);\n    return m ? m[1].trim() : null;\n  };\n\n  // Extract basic fields\n  const sku = getMatch(/SKU:\\s*(.+)/);\n  const increaseRaw = getMatch(/Increase:\\s*([^\\n]+)/);\n  const currentReturns = getMatch(/Current Returns:\\s*(\\d+)/);\n  const previousReturns = getMatch(/Previous Returns:\\s*(\\d+)/);\n  const status = getMatch(/Status:\\s*(.+)/);\n  const runDateUtc = getMatch(/Run Date \\(UTC\\):\\s*(.+)/);\n  const cooldownKey = getMatch(/Cooldown Key:\\s*(.+)/);\n\n  // Percent increase (handle N/A)\n  const percentIncrease =\n    increaseRaw && increaseRaw !== \"N/A%\"\n      ? parseInt(increaseRaw.replace(\"%\", \"\"), 10)\n      : null;\n\n  // Extract reasons block\n  const reasonsBlockMatch = text.match(/Reasons:\\n([\\s\\S]*?)\\n\\nStatus:/);\n  const reasonsText = reasonsBlockMatch ? reasonsBlockMatch[1].trim() : \"\";\n\n  // Convert reasons into object\n  const reasons = {};\n  reasonsText.split(\"\\n\").forEach(line => {\n    const parts = line.split(\":\");\n    if (parts.length === 2) {\n      const reason = parts[0].trim();\n      const count = parseInt(parts[1].trim(), 10);\n      if (!isNaN(count)) {\n        reasons[reason] = count;\n      }\n    }\n  });\n\n  output.push({\n    json: {\n      sku,\n      current_returns: currentReturns ? parseInt(currentReturns, 10) : null,\n      previous_returns: previousReturns ? parseInt(previousReturns, 10) : null,\n      percent_increase: percentIncrease,\n      reasons_raw: reasonsText,               // human-readable\n      reasons_json: JSON.stringify(reasons),  // machine-readable\n      status,\n      run_date_utc: runDateUtc,\n      cooldown_key: cooldownKey\n    }\n  });\n}\n\nreturn output;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "63504e6d-2fdd-4daf-9bc5-9583fbd6d902",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3520,
        -688
      ],
      "parameters": {
        "width": 416,
        "height": 880,
        "content": "## How it Works:\n\nThis workflow automatically monitors WooCommerce refund activity on a scheduled basis.\nOn each run, it fetches recent orders and refunds, maps refunds back to their parent orders, and analyzes return trends at the SKU level.\n\nThe workflow compares return counts across rolling time windows to detect unusual spikes.\nWhen a return surge is identified, it enriches the data with execution metadata and sends a detailed alert to Slack.\n\nAll detected alerts are then normalized and logged into Airtable, creating a historical record that helps teams investigate product, packaging, or fulfillment issues efficiently.\n\nOverall, this workflow reduces manual monitoring, enables early issue detection, and improves operational response time.\n\n## Setup Steps:\n\n1. Create a Schedule Trigger in n8n to run the workflow at a fixed interval.\n\n2. Configure WooCommerce HTTP credentials for Orders and Refunds APIs.\n\n3. Fetch orders and refunds using HTTP Orders and HTTP Refunds nodes.\n\n4. Use a Code node to map refunds to parent orders and extract SKU details.\n\n5. Aggregate return data and calculate increases using the Refund_details node.\n\n6. Add an IF condition to detect return surges based on defined thresholds.\n\n7.Use Set Fields to add alert metadata (status, run date, cooldown key).\n\n8. Send formatted alerts to Slack using the Slack node.\n\n9. Normalize alert data and store records in Airtable for tracking and reporting."
      },
      "typeVersion": 1
    },
    {
      "id": "401013de-e238-4e95-8fed-5bc98ec58657",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3088,
        -416
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 320,
        "content": "\n###  This scheduled workflow monitors WooCommerce product returns to detect\n### unusual SKU-level spikes and automatically sends alerts with logged data.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1504b6c4-6154-4ac5-b8fc-193784cd2f73",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2752,
        -656
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 560,
        "content": "## Data Collection & Return Analysis:\n\n### \u2022 HTTP Orders \u2013 Fetches WooCommerce orders and line-item details.\n### \u2022 HTTP Refunds \u2013 Fetches refund records with reasons and timestamps.\n### \u2022 Orders_Fetch \u2013 Maps refunds to parent orders and extracts SKU data.\n### \u2022 Refund_details \u2013 Calculates return counts, increases, and reasons per SKU.\n\n### This enables accurate, SKU-level detection of return surges.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "8979e15d-606a-4981-962c-3935e5114f4f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1152,
        -592
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 432,
        "content": "##  Alerts & Logging:\n\n### \u2022 Slack \u2013 Sends return surge alerts with SKU, counts, increase %, and reasons.\n### \u2022 Code in JavaScript \u2013 Normalizes Slack alert text into structured fields.\n### \u2022 Airtable \u2013 Stores alert records for audit, trend analysis, and reporting.\n\n### This ensures alerts are actionable and historically traceable."
      },
      "typeVersion": 1
    },
    {
      "id": "5cecead3-eb18-49bd-aa00-1cb20d702026",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1808,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 496,
        "content": "## Surge Filter & Enrichment:\n\n###  \u2022 IF \u2013 Detects return surges based on thresholds\n  (\u2265100% increase OR \u226525 current returns).\n\n### \u2022 Set Fields \u2013 Adds alert metadata (status, run date, cooldown key)\n  for downstream alerting and logging.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f619fc94-2995-458b-9464-bcf19210a5b9",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Edit Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Orders": {
      "main": [
        [
          {
            "node": "HTTP Refunds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Time_window": {
      "main": [
        [
          {
            "node": "HTTP Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields1": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Refunds": {
      "main": [
        [
          {
            "node": "Orders_Fetch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Orders_Fetch": {
      "main": [
        [
          {
            "node": "Refund_details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Refund_details": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a record": {
      "main": [
        []
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Time_window",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Create a record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}