{
  "id": "43Wkp9nCvzl5KUjG",
  "name": "WhatsApp Order Tracking Automation",
  "tags": [
    {
      "id": "z6mxJFPmp5Jq0Lnu",
      "name": "whatsapp",
      "createdAt": "2026-05-11T13:01:00.955Z",
      "updatedAt": "2026-05-11T13:01:00.955Z"
    },
    {
      "id": "qGtyEjKjSjtOWklZ",
      "name": "ecommerce",
      "createdAt": "2026-05-11T13:08:23.544Z",
      "updatedAt": "2026-05-11T13:08:23.544Z"
    },
    {
      "id": "NJ0ha3cOcnMLzWsO",
      "name": "order-tracking",
      "createdAt": "2026-05-11T13:08:23.500Z",
      "updatedAt": "2026-05-11T13:08:23.500Z"
    }
  ],
  "nodes": [
    {
      "id": "676e6ec8-95fc-4fdf-a3c1-1400e42f364b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 980,
        "height": 1092,
        "content": "## WhatsApp Order Tracking Automation\n\nThis workflow automatically delivers real-time order updates to customers via WhatsApp \u2014 covering every stage from order confirmation through final delivery.\n\n### Who's it for\n\u2022 E-commerce stores processing 50+ orders/day\n\u2022 Logistics teams managing last-mile delivery\n\u2022 Businesses wanting to reduce support tickets about order status\n\n### How it works / What it does\n1. Receives order event via webhook (new order, shipped, out for delivery, delivered)\n2. Extracts and normalises order + customer data\n3. Detects the event type using JavaScript logic\n4. Selects the matching WhatsApp message template\n5. Sends the formatted message via WhatsApp Business API\n6. Logs delivery receipt and audit trail to Google Sheets\n\n### Message Templates\n\u2022 Order Confirmed: Order #ID confirmed with total amount\n\u2022 Shipped: Tracking number + expected delivery date\n\u2022 Out for Delivery: Same-day delivery alert\n\u2022 Delivered: Thank-you confirmation message\n\n### How to set up\n1. Import this workflow into n8n\n2. Configure credentials: WhatsApp Business API, Google Sheets OAuth2\n3. Replace YOUR_SHEET_ID with your actual Google Sheet ID\n4. Replace YOUR_WHATSAPP_PHONE_NUMBER_ID with your Meta phone number ID\n5. Set WHATSAPP_ACCESS_TOKEN in your n8n credentials\n6. Activate the workflow and point your order system to the webhook URL\n\n### Requirements\n\u2022 Meta WhatsApp Business API access (approved account)\n\u2022 Google Sheets (for audit logging)\n\u2022 Order management system capable of sending webhooks\n\u2022 n8n instance (cloud or self-hosted)\n\n### How to customise\n\u2022 Edit message text in the JS - Build Message Payload node\n\u2022 Add new event types in the JS - Detect Order Event node\n\u2022 Change sheet columns in Update Google Sheet Tracker\n\u2022 Add error notification (e.g. Slack/email) in the Error Handler node"
      },
      "typeVersion": 1
    },
    {
      "id": "25a569d1-0504-40cd-bd8b-e68497202377",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1082,
        276
      ],
      "parameters": {
        "color": 4,
        "width": 668,
        "height": 500,
        "content": "## 1. Trigger & Intake\n\nAccepts incoming order events from your OMS via webhook.\nBoth real-time webhooks and a scheduled polling fallback are supported."
      },
      "typeVersion": 1
    },
    {
      "id": "0c00263a-8843-449e-9320-1adaa434d693",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1856,
        176
      ],
      "parameters": {
        "color": 3,
        "width": 820,
        "height": 620,
        "content": "## 2. Event Detection & Template Selection\n\nIdentifies the order event type, validates required fields,\nand selects the correct WhatsApp message template."
      },
      "typeVersion": 1
    },
    {
      "id": "a736a442-2b67-4fcc-a0ae-94450763661d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2736,
        192
      ],
      "parameters": {
        "color": 6,
        "width": 1040,
        "height": 600,
        "content": "## 3. Send & Track\n\nDelivers the WhatsApp message, captures delivery receipt,\nand logs the full audit trail to Google Sheets.\nError handler fires on any node failure in this section."
      },
      "typeVersion": 1
    },
    {
      "id": "5abe6c3f-c6f5-4918-81dc-d9682d7ba5a5",
      "name": "Webhook - Order Event",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1200,
        424
      ],
      "parameters": {
        "path": "order-tracking-inbound",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "c6bd101c-1bca-427e-b0e2-ffecae11fa91",
      "name": "Poll Order System",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1200,
        616
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/15 * * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2df0cf68-2f16-4169-b3e8-154ff614a820",
      "name": "Prepare Order Context",
      "type": "n8n-nodes-base.set",
      "position": [
        1424,
        520
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "orderId",
              "type": "string",
              "value": "={{ $json.order_id || $json.body?.order_id || $json.id || 'UNKNOWN' }}"
            },
            {
              "name": "orderStatus",
              "type": "string",
              "value": "={{ ($json.status || $json.body?.status || $json.event_type || '').toLowerCase().trim() }}"
            },
            {
              "name": "customerName",
              "type": "string",
              "value": "={{ $json.customer_name || $json.body?.customer?.name || $json.customer?.name || 'Valued Customer' }}"
            },
            {
              "name": "customerPhone",
              "type": "string",
              "value": "={{ $json.customer_phone || $json.body?.customer?.phone || $json.phone || '' }}"
            },
            {
              "name": "orderTotal",
              "type": "string",
              "value": "={{ $json.total || $json.body?.total || $json.amount || '0.00' }}"
            },
            {
              "name": "currency",
              "type": "string",
              "value": "={{ $json.currency || $json.body?.currency || 'USD' }}"
            },
            {
              "name": "trackingNumber",
              "type": "string",
              "value": "={{ $json.tracking_number || $json.body?.tracking?.number || $json.tracking_id || '' }}"
            },
            {
              "name": "trackingUrl",
              "type": "string",
              "value": "={{ $json.tracking_url || $json.body?.tracking?.url || '' }}"
            },
            {
              "name": "estimatedDelivery",
              "type": "string",
              "value": "={{ $json.estimated_delivery || $json.body?.estimated_delivery || $json.eta || '' }}"
            },
            {
              "name": "ingestedAt",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7cedf235-232d-43f4-815c-15c0698e9073",
      "name": "JS - Detect Order Event",
      "type": "n8n-nodes-base.code",
      "position": [
        1648,
        520
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\n// Normalise status to a canonical event key\nconst rawStatus = (item.orderStatus || '').toLowerCase();\n\nconst STATUS_MAP = {\n  'confirmed':      'ORDER_CONFIRMED',\n  'order_confirmed':'ORDER_CONFIRMED',\n  'placed':         'ORDER_CONFIRMED',\n  'new':            'ORDER_CONFIRMED',\n  'shipped':        'ORDER_SHIPPED',\n  'dispatched':     'ORDER_SHIPPED',\n  'in_transit':     'ORDER_SHIPPED',\n  'out_for_delivery': 'OUT_FOR_DELIVERY',\n  'out for delivery': 'OUT_FOR_DELIVERY',\n  'on_the_way':     'OUT_FOR_DELIVERY',\n  'delivered':      'ORDER_DELIVERED',\n  'completed':      'ORDER_DELIVERED',\n};\n\nconst eventType = STATUS_MAP[rawStatus] || null;\n\n// Validate required fields\nconst missingPhone = !item.customerPhone || item.customerPhone.trim() === '';\nconst missingOrderId = !item.orderId || item.orderId === 'UNKNOWN';\n\nreturn {\n  json: {\n    ...item,\n    eventType,\n    isValidEvent: eventType !== null,\n    hasPhone: !missingPhone,\n    validationError: missingPhone ? 'Missing customer phone' : missingOrderId ? 'Missing order ID' : null\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "8630c436-ecc4-41bd-803c-1423658884fd",
      "name": "Filter Valid Order Events",
      "type": "n8n-nodes-base.filter",
      "position": [
        1872,
        520
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.isValidEvent }}"
            },
            {
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.hasPhone }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1a50903d-2bd2-4207-ac28-09a4ec5f12be",
      "name": "Wait - Rate Limit",
      "type": "n8n-nodes-base.wait",
      "position": [
        2096,
        520
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "753e0880-d252-4353-b2dd-7484309e4b07",
      "name": "JS - Build Message Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        520
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\n// \u2500\u2500\u2500 Message Templates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst templates = {\n  ORDER_CONFIRMED: [\n    `Hi ${item.customerName}! \ud83c\udf89`,\n    `Your order *#${item.orderId}* has been confirmed.`,\n    `\ud83d\udcb0 Total: ${item.currency} ${item.orderTotal}`,\n    `We'll notify you as soon as it ships. Thank you for shopping with us!`\n  ].join('\\n'),\n\n  ORDER_SHIPPED: [\n    `Hi ${item.customerName}! \ud83d\udce6`,\n    `Great news \u2014 your order *#${item.orderId}* has shipped!`,\n    item.trackingNumber ? `\ud83d\udd0d Tracking: ${item.trackingNumber}` : null,\n    item.trackingUrl    ? `\ud83d\udd17 Track here: ${item.trackingUrl}` : null,\n    item.estimatedDelivery ? `\ud83d\udcc5 Expected delivery: ${item.estimatedDelivery}` : null,\n    `We'll keep you posted every step of the way.`\n  ].filter(Boolean).join('\\n'),\n\n  OUT_FOR_DELIVERY: [\n    `Hi ${item.customerName}! \ud83d\ude9a`,\n    `Your order *#${item.orderId}* is out for delivery today!`,\n    `Please make sure someone is available to receive it.`,\n    `If you have any questions, reply to this message.`\n  ].join('\\n'),\n\n  ORDER_DELIVERED: [\n    `Hi ${item.customerName}! \u2705`,\n    `Your order *#${item.orderId}* has been delivered.`,\n    `We hope you love your purchase! \ud83d\udecd\ufe0f`,\n    `If anything is missing or damaged, please contact us within 48 hours.`,\n    `Thank you for choosing us!`\n  ].join('\\n')\n};\n\nconst messageText = templates[item.eventType] || `Hi ${item.customerName}, your order #${item.orderId} status has been updated to: ${item.orderStatus}.`;\n\n// Sanitise phone number \u2014 ensure it starts with country code, digits only\nconst sanitisedPhone = item.customerPhone\n  .replace(/\\D/g, '')\n  .replace(/^0+/, '');\n\nreturn {\n  json: {\n    ...item,\n    messageText,\n    recipientPhone: sanitisedPhone,\n    sentAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "664fbdf6-d224-43ad-b914-4936b68c9957",
      "name": "Send WhatsApp Message",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2544,
        520
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $credentials.whatsappPhoneNumberId }}/messages",
        "method": "POST",
        "options": {
          "timeout": 10000
        },
        "jsonBody": "={\n  \"messaging_product\": \"whatsapp\",\n  \"recipient_type\": \"individual\",\n  \"to\": \"{{ $json.recipientPhone }}\",\n  \"type\": \"text\",\n  \"text\": {\n    \"preview_url\": false,\n    \"body\": {{ JSON.stringify($json.messageText) }}\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d1fd224c-f942-471b-819d-fcb8c2b2030f",
      "name": "JS - Capture Delivery Receipt",
      "type": "n8n-nodes-base.code",
      "position": [
        2768,
        448
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\n// Capture WhatsApp API response\nconst apiResponse = item;\nconst waMessageId = apiResponse?.messages?.[0]?.id || 'N/A';\nconst deliveryStatus = apiResponse?.messages ? 'SENT' : 'FAILED';\n\nreturn {\n  json: {\n    ...($input.item.json),\n    waMessageId,\n    deliveryStatus,\n    loggedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "0b11c816-0cf4-4076-a058-d8945975eae6",
      "name": "Update Google Sheet Tracker",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2992,
        448
      ],
      "parameters": {
        "url": "=https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/OrderTracking!A1:append?valueInputOption=USER_ENTERED",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"values\": [[\n    \"{{ $json.loggedAt }}\",\n    \"{{ $json.orderId }}\",\n    \"{{ $json.customerName }}\",\n    \"{{ $json.recipientPhone }}\",\n    \"{{ $json.eventType }}\",\n    \"{{ $json.orderStatus }}\",\n    \"{{ $json.orderTotal }}\",\n    \"{{ $json.currency }}\",\n    \"{{ $json.trackingNumber }}\",\n    \"{{ $json.waMessageId }}\",\n    \"{{ $json.deliveryStatus }}\",\n    \"{{ $json.sentAt }}\"\n  ]]\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "oAuth2"
      },
      "typeVersion": 4.2
    },
    {
      "id": "afcec925-4967-4e0c-8d86-53637955e406",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3216,
        400
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={ \"success\": true, \"orderId\": \"{{ $json.orderId }}\", \"waMessageId\": \"{{ $json.waMessageId }}\", \"status\": \"{{ $json.deliveryStatus }}\" }"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d63d2ad1-6d1c-467e-96e5-a128189af6c9",
      "name": "Error Handler",
      "type": "n8n-nodes-base.code",
      "position": [
        3216,
        616
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Error handler \u2014 captures failures from Send or Sheet nodes\nconst errorDetails = {\n  failedAt: new Date().toISOString(),\n  orderId: $input.item.json?.orderId || 'N/A',\n  eventType: $input.item.json?.eventType || 'N/A',\n  recipientPhone: $input.item.json?.recipientPhone || 'N/A',\n  errorMessage: $input.item.json?.error?.message || 'Unknown error',\n  errorCode: $input.item.json?.error?.code || 'N/A'\n};\n\nconsole.error('[OrderTracking] ERROR:', JSON.stringify(errorDetails));\n\nreturn { json: errorDetails };"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "77de3cf1-6b92-4b53-bebb-aed42bb11b98",
  "connections": {
    "Poll Order System": {
      "main": [
        [
          {
            "node": "Prepare Order Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait - Rate Limit": {
      "main": [
        [
          {
            "node": "JS - Build Message Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Order Context": {
      "main": [
        [
          {
            "node": "JS - Detect Order Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send WhatsApp Message": {
      "main": [
        [
          {
            "node": "JS - Capture Delivery Receipt",
            "type": "main",
            "index": 0
          },
          {
            "node": "Error Handler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Order Event": {
      "main": [
        [
          {
            "node": "Prepare Order Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Detect Order Event": {
      "main": [
        [
          {
            "node": "Filter Valid Order Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Valid Order Events": {
      "main": [
        [
          {
            "node": "Wait - Rate Limit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Build Message Payload": {
      "main": [
        [
          {
            "node": "Send WhatsApp Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Google Sheet Tracker": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          },
          {
            "node": "Error Handler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Capture Delivery Receipt": {
      "main": [
        [
          {
            "node": "Update Google Sheet Tracker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}