AutomationFlows β€Ί AI & RAG β€Ί Build a Whatsapp AI Shopping Bot with Virtual Try-on Using Gemini and Gpt

Build a Whatsapp AI Shopping Bot with Virtual Try-on Using Gemini and Gpt

ByBytezTech @chintanβœ“ on n8n.io

πŸ“Œ Overview

Event triggerβ˜…β˜…β˜…β˜…β˜… complexityAI-powered83 nodesRedisWhatsAppOpenAI ChatMongo Db ToolAgentGoogle Sheets ToolVector Store Mongo DbatlasOpenAI Embeddings
AI & RAG Trigger: Event Nodes: 83 Complexity: β˜…β˜…β˜…β˜…β˜… AI nodes: yes Added:

This workflow corresponds to n8n.io template #13506 β€” we link there as the canonical source.

This workflow follows the Agent β†’ OpenAI Embeddings 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "4f63a844-6f93-4880-a74f-aa40787aace6",
      "name": "\ud83d\udccc Main overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4608,
        736
      ],
      "parameters": {
        "width": 516,
        "height": 620,
        "content": "## WhatsApp AI shopping bot for Bytez\n\n## How it works\n1. User sends a WhatsApp message (text, button tap, or image).\n2. GPT-5-nano classifies the intent and routes to the correct flow.\n3. Product search checks Redis cache first, then runs MongoDB Atlas vector search using OpenAI embeddings.\n4. Matched products are sent as interactive WhatsApp cards with Order Now and Virtual Try-On buttons.\n5. Order tap \u2192 AI agent fetches product, creates order in MongoDB, logs to Google Sheets, sends confirmation.\n6. Try-On tap \u2192 user sends selfie \u2192 Gemini validates one person \u2192 generates try-on image \u2192 sends result back.\n\n## Setup steps\n1. Add WhatsApp Business API credentials.\n2. Add OpenAI credentials (embeddings + GPT-5-nano).\n3. Add Google Gemini API credentials.\n4. Add MongoDB Atlas credentials and create a vector index named `ShopingBot` on the `product` collection.\n5. Add Redis credentials.\n6. Add Google Drive and Sheets credentials (service account).\n7. Import product catalog with embeddings into the MongoDB `product` collection."
      },
      "typeVersion": 1
    },
    {
      "id": "271c0200-b2e1-4830-abd0-22a1994bb404",
      "name": "\ud83d\udccc Group: Entry & Router",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5184,
        736
      ],
      "parameters": {
        "color": 7,
        "width": 580,
        "height": 460,
        "content": "## \ud83d\udce5 Entry & Message Router\nReceives all incoming WhatsApp messages. Routes button taps and image uploads to the interactive handler. Routes plain text messages to the validation pipeline.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2c5fe6ef-414d-4184-9593-d373815ac389",
      "name": "\ud83d\udccc Group: Validation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5856,
        2160
      ],
      "parameters": {
        "color": 7,
        "width": 760,
        "height": 476,
        "content": "## \u2705 Text Message Validation\nValidates incoming text for empty input, spam, XSS, and unsupported types. Invalid messages are rejected with a user-friendly error reply before entering the session pipeline."
      },
      "typeVersion": 1
    },
    {
      "id": "d6513e3e-c805-4987-afce-dd7db7a5f739",
      "name": "\ud83d\udccc Group: Session Management",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6656,
        2064
      ],
      "parameters": {
        "color": 7,
        "width": 492,
        "height": 396,
        "content": "## \ud83d\udcbe Session Management (Redis)\nLoads or creates a per-user Redis session (TTL: 1 hr). Appends user and assistant messages. Keeps the last 20 messages using a sliding window."
      },
      "typeVersion": 1
    },
    {
      "id": "44cad1fa-b280-4824-9f37-ced7823009c5",
      "name": "\ud83d\udccc Group: AI Classification",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7296,
        1856
      ],
      "parameters": {
        "color": 7,
        "width": 1496,
        "height": 828,
        "content": "## \ud83e\udde0 AI Classification & Routing\nBytezBot classifies intent using GPT-5-nano. Returns JSON for product_search or recommend, or plain text for all other queries. Routes to the product pipeline or a direct WhatsApp reply."
      },
      "typeVersion": 1
    },
    {
      "id": "590a85d0-ed0d-4afe-ac0a-e35761a58356",
      "name": "\ud83d\udccc Group: Cache & Vector Search",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8864,
        1600
      ],
      "parameters": {
        "color": 7,
        "width": 2508,
        "height": 732,
        "content": "## \u26a1 Product Search (Cache + Vector)\nChecks Redis cache first (TTL: 1 hr). On a miss, runs MongoDB Atlas vector search using OpenAI embeddings. \nDownloads product images from Drive and stores results in cache."
      },
      "typeVersion": 1
    },
    {
      "id": "ffbd2a6d-6185-4a9e-b665-be92ffa2731c",
      "name": "\ud83d\udccc Group: Product Sender",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11424,
        1664
      ],
      "parameters": {
        "color": 7,
        "width": 1148,
        "height": 460,
        "content": "## \ud83d\udce4 Product Message Sender\nLoops through each matched product. Uploads image to WhatsApp media API. \nSends an interactive message with Order Now and Virtual Try-On buttons."
      },
      "typeVersion": 1
    },
    {
      "id": "8b304693-991b-49d1-899c-b9a53c5a48c0",
      "name": "\ud83d\udccc Group: Order Flow",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6960,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 1148,
        "height": 588,
        "content": "## \ud83d\uded2 Order Flow\nTriggered when user taps Order Now. AI agent fetches product, creates an order document, saves to MongoDB and Google Sheets, then sends a confirmation message via WhatsApp."
      },
      "typeVersion": 1
    },
    {
      "id": "5b487497-fb35-46f4-8b51-b7e3677608e0",
      "name": "\ud83d\udccc Group: VTO Flow",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6752,
        -560
      ],
      "parameters": {
        "color": 7,
        "width": 872,
        "height": 360,
        "content": "## \ud83d\udc57 Virtual Try-On (VTO) Flow\nStores product ID in Redis (TTL: 10 min) and prompts the user for a selfie. Gemini validates exactly one person, generates the try-on image, sends it via WhatsApp, then clears Redis context."
      },
      "typeVersion": 1
    },
    {
      "id": "42252d26-19e1-4359-872b-94c2ce3a9ea2",
      "name": "Check Redis cache",
      "type": "n8n-nodes-base.redis",
      "position": [
        9104,
        1872
      ],
      "parameters": {
        "key": "={{ $json.cacheKey }}",
        "options": {},
        "operation": "get",
        "propertyName": "cachedProductsJson"
      },
      "typeVersion": 1
    },
    {
      "id": "493b1ea1-356f-461a-b6fa-bdfc0c39be03",
      "name": "Store products in cache",
      "type": "n8n-nodes-base.redis",
      "position": [
        11024,
        1984
      ],
      "parameters": {
        "key": "={{ $json.cacheKey }}",
        "ttl": 3600,
        "value": "={{ $json.productsJson }}",
        "expire": true,
        "operation": "set"
      },
      "typeVersion": 1
    },
    {
      "id": "cc9a90f4-db40-4498-960a-e590a43376fc",
      "name": "Save session to Redis",
      "type": "n8n-nodes-base.redis",
      "position": [
        8208,
        2176
      ],
      "parameters": {
        "key": "={{ $json.redisKey }}",
        "ttl": 3600,
        "value": "={{ $json.updatedSessionString }}",
        "expire": true,
        "operation": "set"
      },
      "typeVersion": 1
    },
    {
      "id": "dcc659e3-24d9-40ab-b06e-ea7412a0642e",
      "name": "Validate incoming message",
      "type": "n8n-nodes-base.code",
      "position": [
        5984,
        2304
      ],
      "parameters": {
        "jsCode": "// Ultra-Advanced Validation for WhatsApp Messages\nconst items = $input.all();\n\nfunction isEmptyOrInvalid(str) {\n  if (!str || typeof str !== 'string') return true;\n  const trimmed = str.trim();\n  if (trimmed.length === 0) return true;\n  const invalidOnlyPattern = /^[\\s\\u00A0\\u1680\\u2000-\\u200B\\u202F\\u205F\\u3000\\uFEFF\"'`<>,.!?;:\\-_=+(){}\\[\\]\\/\\\\|~@#$%^&*]*$/;\n  if (invalidOnlyPattern.test(trimmed)) return true;\n  return false;\n}\n\nfunction isMeaningless(str) {\n  if (!str || typeof str !== 'string') return true;\n  const trimmed = str.trim();\n  if (trimmed.length === 1) {\n    const char = trimmed;\n    if (/[a-zA-Z0-9]/.test(char)) return false;\n    if (['\u2713', '\u2714', '\u2705', '\u274c', '\ud83d\udc4d', '\ud83d\udc4e', '\u270b', '\ud83d\ude4f'].includes(char)) return false;\n    if (/[<>!?.,;:'\"=+\\-_*&^%$#@~`|\\\\\\/(){}\\[\\]]/.test(char)) return true;\n  }\n  if (trimmed.length <= 2) {\n    if (/^[^a-zA-Z0-9\\u{1F300}-\\u{1F9FF}]+$/u.test(trimmed)) return true;\n  }\n  if (/^(.)\\1{2,}$/.test(trimmed) && trimmed.length <= 10) {\n    if (!/^[a-zA-Z0-9]+$/.test(trimmed)) return false;\n    return true;\n  }\n  return false;\n}\n\nfunction sanitizeMessage(str) {\n  if (!str || typeof str !== 'string') return '';\n  let sanitized = str.trim();\n  sanitized = sanitized.replace(/\\s+/g, ' ');\n  sanitized = sanitized.replace(/[\\u200B-\\u200D\\uFEFF\\u00AD]/g, '');\n  sanitized = sanitized.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/g, '');\n  sanitized = sanitized.replace(/([!?.;,]){4,}/g, '$1$1$1');\n  sanitized = sanitized.replace(/^[<>{}()\\[\\]]+|[<>{}()\\[\\]]+$/g, '');\n  return sanitized.trim();\n}\n\nfunction containsSuspiciousPatterns(str) {\n  if (!str || typeof str !== 'string') return false;\n  const suspiciousPatterns = [\n    /<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi,\n    /javascript:/gi,\n    /on\\w+\\s*=/gi,\n    /<iframe/gi,\n    /eval\\s*\\(/gi,\n    /expression\\s*\\(/gi,\n    /<object/gi,\n    /<embed/gi,\n    /vbscript:/gi,\n    /data:text\\/html/gi,\n  ];\n  return suspiciousPatterns.some(pattern => pattern.test(str));\n}\n\nfunction isSpamPattern(str) {\n  if (!str || typeof str !== 'string') return false;\n  const capitals = (str.match(/[A-Z]/g) || []).length;\n  const letters = (str.match(/[a-zA-Z]/g) || []).length;\n  if (letters > 5 && (capitals / letters) > 0.7) return true;\n  const words = str.toLowerCase().split(/\\s+/);\n  const uniqueWords = new Set(words);\n  if (words.length > 10 && uniqueWords.size < words.length * 0.3) return true;\n  return false;\n}\n\nfor (const item of items) {\n  const message = item.json.messages?.[0];\n  let isValid = false;\n  let validationError = null;\n  let userFriendlyError = null;\n  let messageContent = null;\n  let messageType = null;\n\n  if (!message) {\n    validationError = \"Message data is missing in the incoming payload\";\n    userFriendlyError = \"Sorry, we couldn't process your message. Please try again.\";\n  } else if (message.type === 'text') {\n    const rawText = message.text?.body;\n    if (!rawText) {\n      validationError = \"Text message body is null or undefined\";\n      userFriendlyError = \"Please send a valid text message.\";\n    } else if (isEmptyOrInvalid(rawText)) {\n      validationError = `Text message is empty or contains only invalid characters: \"${rawText}\"`;\n      userFriendlyError = \"Your message appears to be empty or contains only special characters. Please send a valid text message.\";\n    } else {\n      const sanitized = sanitizeMessage(rawText);\n      if (sanitized.length === 0) {\n        validationError = `Text message contains no valid content after sanitization. Original: \"${rawText}\"`;\n        userFriendlyError = \"Your message doesn't contain any valid text. Please try sending your message again.\";\n      } else if (isMeaningless(sanitized)) {\n        validationError = `Message is too short or meaningless: \"${sanitized}\"`;\n        userFriendlyError = \"Your message is too short. Please send a complete message (at least 2-3 characters).\";\n      } else if (containsSuspiciousPatterns(sanitized)) {\n        validationError = `Text message contains potentially malicious content: \"${sanitized}\"`;\n        userFriendlyError = \"Your message contains invalid content. Please send a normal text message.\";\n      } else if (isSpamPattern(sanitized)) {\n        validationError = `Text message appears to be spam: \"${sanitized}\"`;\n        userFriendlyError = \"Your message looks unusual. Please send a normal message.\";\n      } else if (sanitized.length > 4096) {\n        validationError = `Text message exceeds maximum length: ${sanitized.length} characters`;\n        userFriendlyError = \"Your message is too long. Please keep it under 4000 characters.\";\n      } else {\n        isValid = true;\n        messageContent = sanitized;\n        messageType = 'text';\n      }\n    }\n  } else if (message.type === 'interactive') {\n    const interactive = message.interactive;\n    if (!interactive) {\n      validationError = \"Interactive message data is missing\";\n      userFriendlyError = \"Button/list selection failed. Please try clicking again.\";\n    } else if (interactive.type === 'button_reply' && interactive.button_reply) {\n      const buttonId = interactive.button_reply.id;\n      const buttonTitle = interactive.button_reply.title;\n      if (!buttonId || isEmptyOrInvalid(buttonId)) {\n        validationError = `Button reply ID is empty or invalid: \"${buttonId}\"`;\n        userFriendlyError = \"Button selection failed. Please try clicking the button again.\";\n      } else if (containsSuspiciousPatterns(buttonId)) {\n        validationError = `Button reply contains malicious content: \"${buttonId}\"`;\n        userFriendlyError = \"Invalid button selection. Please contact support.\";\n      } else {\n        const sanitizedId = sanitizeMessage(buttonId);\n        if (sanitizedId.length === 0) {\n          validationError = `Button ID empty after sanitization: \"${buttonId}\"`;\n          userFriendlyError = \"Button selection failed. Please try again.\";\n        } else {\n          isValid = true;\n          messageContent = sanitizedId;\n          messageType = 'button_reply';\n          item.json.buttonTitle = buttonTitle ? sanitizeMessage(buttonTitle) : null;\n        }\n      }\n    } else if (interactive.type === 'list_reply' && interactive.list_reply) {\n      const listId = interactive.list_reply.id;\n      const listTitle = interactive.list_reply.title;\n      const listDescription = interactive.list_reply.description;\n      if (!listId || isEmptyOrInvalid(listId)) {\n        validationError = `List reply ID is empty or invalid: \"${listId}\"`;\n        userFriendlyError = \"List selection failed. Please try selecting from the list again.\";\n      } else {\n        const sanitizedId = sanitizeMessage(listId);\n        if (sanitizedId.length === 0) {\n          validationError = `List ID empty after sanitization: \"${listId}\"`;\n          userFriendlyError = \"List selection failed. Please try again.\";\n        } else {\n          isValid = true;\n          messageContent = sanitizedId;\n          messageType = 'list_reply';\n          item.json.listTitle = listTitle ? sanitizeMessage(listTitle) : null;\n          item.json.listDescription = listDescription ? sanitizeMessage(listDescription) : null;\n        }\n      }\n    } else {\n      validationError = `Unsupported interactive type: '${interactive.type || 'unknown'}'`;\n      userFriendlyError = \"Unsupported selection type. Please use the provided buttons or lists.\";\n    }\n  } else if (message.type === 'image') {\n    validationError = \"Image message received\";\n    userFriendlyError = \"\ud83d\udcf8 Image messages are not supported here. Please send a text message instead.\";\n  } else if (message.type === 'video') {\n    validationError = \"Video message received\";\n    userFriendlyError = \"\ud83c\udfa5 Video messages are not supported. Please send a text message instead.\";\n  } else if (message.type === 'audio' || message.type === 'voice') {\n    validationError = \"Audio/voice message received\";\n    userFriendlyError = \"\ud83c\udfa4 Voice messages are not supported. Please send a text message instead.\";\n  } else if (message.type === 'document') {\n    validationError = \"Document message received\";\n    userFriendlyError = \"\ud83d\udcc4 Document messages are not supported. Please send a text message instead.\";\n  } else if (message.type === 'sticker') {\n    validationError = \"Sticker message received\";\n    userFriendlyError = \"\ud83d\ude0a Stickers are not supported. Please send a text message instead.\";\n  } else {\n    validationError = `Unknown message type: '${message.type}'`;\n    userFriendlyError = \"\u274c This message type is not supported. Please send a text message.\";\n  }\n\n  item.json.validation = {\n    isValid: isValid,\n    error: validationError,\n    userFriendlyError: userFriendlyError,\n    messageType: messageType,\n    timestamp: new Date().toISOString()\n  };\n\n  if (isValid) {\n    item.json.messageText = messageContent;\n    item.json.messageType = messageType;\n    item.json.senderId = item.json.contacts?.[0]?.wa_id || null;\n    item.json.userName = item.json.contacts?.[0]?.profile?.name\n      ? sanitizeMessage(item.json.contacts[0].profile.name)\n      : \"User\";\n    item.json.messageId = message.id || null;\n    item.json.timestamp = message.timestamp || null;\n    item.json.messageMetadata = {\n      originalLength: messageType === 'text' ? (message.text?.body?.length || 0) : messageContent.length,\n      sanitizedLength: messageContent.length,\n      wordCount: messageContent.split(/\\s+/).length,\n      containsEmojis: /[\\u{1F600}-\\u{1F64F}]|[\\u{1F300}-\\u{1F5FF}]|[\\u{1F680}-\\u{1F6FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/u.test(messageContent),\n      containsNumbers: /\\d/.test(messageContent),\n      containsUrls: /(https?:\\/\\/[^\\s]+)/gi.test(messageContent),\n      isQuestion: /[?\uff1f]/.test(messageContent),\n      language: /[\\u0900-\\u097F]/.test(messageContent) ? 'hindi' :\n                /[a-zA-Z]/.test(messageContent) ? 'english' : 'unknown'\n    };\n  } else {\n    item.json.senderId = item.json.contacts?.[0]?.wa_id || null;\n    item.json.userName = item.json.contacts?.[0]?.profile?.name || \"User\";\n    item.json.messageId = message?.id || null;\n  }\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "2ec6a4c2-06eb-4ab9-b0e5-09b779ecd7b8",
      "name": "Send validation error to user",
      "type": "n8n-nodes-base.whatsApp",
      "disabled": true,
      "position": [
        6432,
        2400
      ],
      "parameters": {
        "textBody": "=\u26a0\ufe0f Validation failed: {{ $json.validation.error }}",
        "operation": "send",
        "phoneNumberId": "804155476125570",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Check if message is button or image').item.json.statuses[0].recipient_id }}"
      },
      "typeVersion": 1
    },
    {
      "id": "05cfe114-d777-4c15-9e33-495b376860db",
      "name": "Get user session from Redis",
      "type": "n8n-nodes-base.redis",
      "position": [
        6704,
        2224
      ],
      "parameters": {
        "key": "={{ $json.senderId }}",
        "options": {},
        "operation": "get",
        "propertyName": "sessionValue"
      },
      "typeVersion": 1
    },
    {
      "id": "2497bfb1-6cc9-4243-996d-25f91f7e57de",
      "name": "GPT-5-nano (intent classifier)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        7312,
        2432
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-nano",
          "cachedResultName": "gpt-5-nano"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "1117d593-3437-4e96-9e31-448b090062c9",
      "name": "Set VTO context in Redis",
      "type": "n8n-nodes-base.redis",
      "position": [
        6816,
        -432
      ],
      "parameters": {
        "key": "=vto_context_{{ $json.waId }}",
        "ttl": 600,
        "value": "={{ $json.productId }}",
        "expire": true,
        "operation": "set"
      },
      "typeVersion": 1
    },
    {
      "id": "26f4dfd0-95c5-4af1-b0d1-213991fbf947",
      "name": "GPT-4o (order agent)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        7120,
        272
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "gpt-4o"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "4c10f728-35e4-44e0-93b4-356c389cc63b",
      "name": "Create order in MongoDB",
      "type": "n8n-nodes-base.mongoDbTool",
      "position": [
        7392,
        272
      ],
      "parameters": {
        "fields": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Fields', 'A JSON string representing the complete order document to be inserted.', 'string') }}",
        "options": {},
        "operation": "insert",
        "collection": "orders"
      },
      "typeVersion": 1.2
    },
    {
      "id": "3f3518a8-c956-43ba-9d06-f3c221b1ff23",
      "name": "Get product info from MongoDB",
      "type": "n8n-nodes-base.mongoDbTool",
      "position": [
        7520,
        272
      ],
      "parameters": {
        "query": "={ \"_id\": \"{{ $fromAI('productId', 'The Object ID of the product to find', 'string') }}\" }",
        "options": {},
        "collection": "product"
      },
      "typeVersion": 1.2
    },
    {
      "id": "0c5258dc-ab79-4e54-afa9-9ca3e104c42b",
      "name": "Order orchestration AI agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        7296,
        48
      ],
      "parameters": {
        "text": "=User with phone number {{ $json.waId }} (name: {{ $json.userName }}) has sent an order request for product ID {{ $json.productId }}. Please handle the entire order process.",
        "options": {
          "systemMessage": "You are an automated order processing agent for Bytez, a T-shirt store.\n\nYour job is to handle the complete order workflow end-to-end without asking the user for any additional information.\n\nYou have access to the following tools:\n- Get product info from MongoDB: fetch product details using the product ID\n- Create order in MongoDB: insert the completed order document\n- Log order to Google Sheets: append the order row\n- Session memory: maintain conversation context\n\nFollow this sequence for every order:\n\nStep 1 - Fetch product\nUse \"Get product info from MongoDB\" with the provided product ID to retrieve the product name and price.\n\nStep 2 - Create the order document\nBuild the order with these fields:\n- orderId: \"ORD\" followed by 12 random alphanumeric characters\n- productId: the provided product ID\n- productName: from the product lookup\n- userPhone: the provided waId\n- userName: the provided name\n- orderDate: current date and time in ISO 8601 format\n- status: \"pending\"\n- totalAmount: from the product lookup\n\nStep 3 - Save to MongoDB\nUse \"Create order in MongoDB\" to insert the order document.\n\nStep 4 - Log to Google Sheets\nUse \"Log order to Google Sheets\" with the same order fields.\n\nStep 5 - Send confirmation\nReply with this message:\nHello [userName]! Your order has been placed successfully. Order ID: [orderId]. Product: [productName]. We will update you once it ships!\n\nImportant rules:\n- Do not ask the user for any extra information\n- Always follow the sequence: Fetch, Create, Log, Confirm\n- If any step fails, reply with a clear, friendly error message"
        },
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "6528994a-08f3-415c-8c83-4f134a3505e8",
      "name": "Log order to Google Sheets",
      "type": "n8n-nodes-base.googleSheetsTool",
      "position": [
        7648,
        272
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('status', 'The current status of the order (e.g., pending).', 'string') }}",
            "orderId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('orderId', 'The unique ID for the order.', 'string') }}",
            "userName": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('userName', 'The name of the user who placed the order.', 'string') }}",
            "orderDate": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('orderDate', 'The ISO 8601 timestamp for when the order was placed.', 'string') }}",
            "productId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('productId', 'The ID of the product ordered.', 'string') }}",
            "userPhone": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('userPhone', 'The phone number of the user.', 'string') }}",
            "productName": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('productName', 'The name of the product ordered.', 'string') }}",
            "totalAmount": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('totalAmount', 'The total cost of the order.', 'string') }}"
          },
          "schema": [
            {
              "id": "orderId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "orderId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "productId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "productId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "productName",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "productName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "userName",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "userName",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "userPhone",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "userPhone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "orderDate",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "orderDate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "totalAmount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "totalAmount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Ps5p6Z4uXwBhvwEktv9ZNAdpLvA7Si80VjTtXRE77uo/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1Ps5p6Z4uXwBhvwEktv9ZNAdpLvA7Si80VjTtXRE77uo",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Ps5p6Z4uXwBhvwEktv9ZNAdpLvA7Si80VjTtXRE77uo/edit?usp=drivesdk",
          "cachedResultName": "Order details"
        },
        "authentication": "serviceAccount"
      },
      "typeVersion": 4.7
    },
    {
      "id": "62b212a7-9298-4135-9ec1-caf2a9fe1685",
      "name": "Send order confirmation",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        7920,
        144
      ],
      "parameters": {
        "textBody": "={{ $('Order orchestration AI agent').item.json.output }}",
        "operation": "send",
        "phoneNumberId": "804155476125570",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Parse button and user data').item.json.waId }}"
      },
      "typeVersion": 1
    },
    {
      "id": "f10c9a50-4a13-4697-91c7-fe2e71f94671",
      "name": "AI shopping agent (BytezBot)",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        7392,
        2176
      ],
      "parameters": {
        "text": "={{ $('Validate incoming message').item.json.messages[0].text.body }}",
        "options": {
          "systemMessage": "=You are BytezBot, the official AI assistant for Bytez, India's premium T-shirt store.\n\nYour only job is to help users find and buy T-shirts with high accuracy.\n\nOutput format:\n- For product_search or recommendation \u2192 pure JSON: {\"intent\":\"product_search\",\"details\":\"...\",\"confidence\":0.95,\"language\":\"english\"}\n- For everything else \u2192 plain conversational text\n\nIntents:\n1. product_search \u2014 user wants to browse or find T-shirts\n2. recommend \u2014 user wants T-shirt advice\n\nAll other queries \u2192 plain text responses (greetings, size guide, delivery, redirects, etc.)"
        },
        "promptType": "define"
      },
      "typeVersion": 2
    },
    {
      "id": "c0a06cba-4eb1-4d0d-9972-c48467fa6fc4",
      "name": "MongoDB Atlas vector search",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreMongoDBAtlas",
      "position": [
        9552,
        1984
      ],
      "parameters": {
        "mode": "load",
        "topK": 3,
        "prompt": "={{ $('Build Redis cache key from search query').item.json.intentData.details }}",
        "options": {},
        "embedding": "text_embedding",
        "mongoCollection": {
          "__rl": true,
          "mode": "list",
          "value": "product",
          "cachedResultName": "product"
        },
        "vectorIndexName": "ShopingBot"
      },
      "typeVersion": 1.3
    },
    {
      "id": "5b799fdc-3a3f-4a55-a29b-7cfb1f6a40c6",
      "name": "OpenAI embeddings",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
      "position": [
        9616,
        2208
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "17c1b430-eb95-4059-b658-7d572dac8838",
      "name": "Get product image from MongoDB",
      "type": "n8n-nodes-base.mongoDb",
      "position": [
        7968,
        720
      ],
      "parameters": {
        "query": "={\n  \"_id\" : \"{{ $('Extract image ID, waId, and product ID').item.json.productId }}\"\n}",
        "options": {},
        "collection": "product"
      },
      "typeVersion": 1.2
    },
    {
      "id": "71738df2-3d3d-489c-95c0-417f4dec9cbf",
      "name": "Resize product image to 1024px",
      "type": "n8n-nodes-base.editImage",
      "position": [
        8416,
        720
      ],
      "parameters": {
        "width": 1024,
        "height": 1024,
        "options": {},
        "operation": "resize"
      },
      "typeVersion": 1
    },
    {
      "id": "0c55a6ff-a1ca-4a97-b862-dd97acc9c974",
      "name": "Extract product image as base64",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        8640,
        720
      ],
      "parameters": {
        "options": {},
        "operation": "binaryToPropery",
        "destinationKey": "product_image"
      },
      "typeVersion": 1
    },
    {
      "id": "9f838541-b2ab-4627-bec1-9c9054c887ba",
      "name": "Resize user selfie to 1024px",
      "type": "n8n-nodes-base.editImage",
      "position": [
        8416,
        912
      ],
      "parameters": {
        "width": 1024,
        "height": 1024,
        "options": {},
        "operation": "resize",
        "dataPropertyName": "user_image"
      },
      "typeVersion": 1
    },
    {
      "id": "755658dd-37eb-492a-a2a7-c76b5dd20bd4",
      "name": "Extract user selfie as base64",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        8640,
        912
      ],
      "parameters": {
        "options": {},
        "operation": "binaryToPropery",
        "destinationKey": "user_image",
        "binaryPropertyName": "user_image"
      },
      "typeVersion": 1
    },
    {
      "id": "19574e9a-3276-405e-9bc2-7ad1a75c10cd",
      "name": "Generate VTO with Gemini API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        9536,
        1008
      ],
      "parameters": {
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent",
        "method": "POST",
        "options": {
          "timeout": 120000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={{ $json.api_payload }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "googlePalmApi"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "130fa21d-dd0e-4580-bad8-13a00956cfda",
      "name": "Delete VTO context from Redis",
      "type": "n8n-nodes-base.redis",
      "position": [
        10432,
        1008
      ],
      "parameters": {
        "key": "=vto_context_{{ $json.contacts[0].wa_id }}",
        "operation": "delete"
      },
      "typeVersion": 1
    },
    {
      "id": "3339b281-64a3-497b-9653-b84f73841e2d",
      "name": "Get VTO context from Redis",
      "type": "n8n-nodes-base.redis",
      "position": [
        7296,
        1104
      ],
      "parameters": {
        "key": "=vto_context_{{ $json.contacts[0].wa_id }}",
        "options": {},
        "operation": "get"
      },
      "typeVersion": 1
    },
    {
      "id": "8bbb9949-4661-4696-ade9-7cd702e8c0b4",
      "name": "Get WhatsApp media URL",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        7968,
        1104
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $('Extract image ID, waId, and product ID').item.json.imageId }}",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "whatsAppApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "8003ff1b-ac5d-4f23-8b39-d7a54c32bfcf",
      "name": "Download user selfie",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        8192,
        1104
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "user_image"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "whatsAppApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "0c89b60a-8bc7-4aed-b0c6-aed6ab7991cc",
      "name": "Download product image from Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        9904,
        2064
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.document.metadata.product_image_url }}"
        },
        "options": {},
        "operation": "download",
        "authentication": "serviceAccount"
      },
      "typeVersion": 3
    },
    {
      "id": "5a8a8d84-4c55-44fb-af66-234d0d10be22",
      "name": "Convert product image to base64",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        10128,
        2064
      ],
      "parameters": {
        "options": {},
        "operation": "binaryToPropery"
      },
      "typeVersion": 1
    },
    {
      "id": "937723b2-490b-426a-bb1b-fc38873546c8",
      "name": "Upload product image to WhatsApp",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        12144,
        1792
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $json.key }}/media",
        "method": "POST",
        "options": {
          "timeout": 15000
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "messaging_product",
              "value": "whatsapp"
            },
            {
              "name": "file",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            },
            {
              "name": "type",
              "value": "image/jpeg"
            }
          ]
        },
        "nodeCredentialType": "whatsAppApi"
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "c296c374-7c20-4346-9ec3-c12820a33ad0",
      "name": "Send product message with buttons",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        12368,
        1872
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $json.key}}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"messaging_product\": \"whatsapp\",\n  \"recipient_type\": \"individual\",\n  \"to\": \"{{ $('Convert product image base64 to binary').first().json.to_number }}\",\n  \"type\": \"interactive\",\n  \"interactive\": {\n    \"type\": \"button\",\n    \"header\": {\n      \"type\": \"image\",\n      \"image\": {\n        \"id\": \"{{ $json.id }}\"\n      }\n    },\n    \"body\": {\n      \"text\": {{ $('Convert product image base64 to binary').first().json.bodyTextEscaped }}\n    },\n    \"footer\": {\n      \"text\": \"Powered by Bytez | Reply for assistance\"\n    },\n    \"action\": {\n      \"buttons\": [\n        {\n          \"type\": \"reply\",\n          \"reply\": {\n            \"id\": \"ORDER_{{ $('Convert product image base64 to binary').first().json.productId }}\",\n            \"title\": \"\ud83d\uded2 Order Now\"\n          }\n        },\n        {\n          \"type\": \"reply\",\n          \"reply\": {\n            \"id\": \"VTO_{{ $('Convert product image base64 to binary').first().json.productId }}\",\n            \"title\": \"\ud83d\udc57 Virtual Try-On\"\n          }\n        }\n      ]\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "whatsAppApi"
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "6a7462e4-7038-4b3f-bf0f-11bb81403d8c",
      "name": "Loop through products",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        11472,
        1872
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3
    },
    {
      "id": "c044e979-26c4-43e2-b869-4260e384feec",
      "name": "Download product image from Drive (VTO)",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        8192,
        720
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.product_image_url }}"
        },
        "options": {},
        "operation": "download",
        "authentication": "serviceAccount"
      },
      "typeVersion": 3
    },
    {
      "id": "11937477-4aec-47c3-a613-e3f59d256722",
      "name": "Ask user to send correct photo",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        7744,
        1200
      ],
      "parameters": {
        "textBody": "Please send a clear, front-facing photo of yourself to use the virtual try-on feature.",
        "operation": "send",
        "phoneNumberId": "804155476125570",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Route interactive vs image message').item.json.contacts[0].wa_id }}"
      },
      "typeVersion": 1
    },
    {
      "id": "d436178b-d025-428e-8e88-35d1e0dd3b91",
      "name": "Prompt user to upload selfie",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        7360,
        -432
      ],
      "parameters": {
        "textBody": "\u2728 Virtual try-on activated!\n\nPlease upload a clear, front-facing photo of yourself to see how this T-shirt looks on you.",
        "operation": "send",
        "phoneNumberId": "804155476125570",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Parse button and user data').item.json.waId }}"
      },
      "typeVersion": 1
    },
    {
      "id": "5e8c9f77-39c7-409a-ac8a-4f17abd77112",
      "name": "Upload VTO result to WhatsApp",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        9984,
        1008
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $json.key}}/media",
        "method": "POST",
        "options": {
          "timeout": 15000
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "messaging_product",
              "value": "whatsapp"
            },
            {
              "name": "file",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "VTO"
            },
            {
              "name": "type",
              "value": "image/jpeg"
            }
          ]
        },
        "nodeCredentialType": "whatsAppApi"
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "cb15a9f3-766a-4cfd-87f7-e0f123b1eef8",
      "name": "Send VTO result to user",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        10208,
        1008
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{$json.key}}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"messaging_product\": \"whatsapp\",\n  \"recipient_type\": \"individual\",\n  \"to\": \"{{ $('Route interactive vs image message').item.json.contacts[0].wa_id }}\",\n  \"type\": \"image\",\n  \"image\": {\n    \"id\": \"{{ $json.id }}\",\n    \"caption\": \"\u2728 Your virtual try-on result!\"\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "whatsAppApi"
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "dd950826-591a-43f6-b56e-8d3fc1e33779",
      "name": "Convert Gemini response to image file",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        9760,
        1008
      ],
      "parameters": {
        "options": {},
        "operation": "toBinary",
        "sourceProperty": "candidates[0].content.parts[0].inlineData.data",
        "binaryPropertyName": "VTO"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d1c50b63-deee-47c0-ad1f-af1a413ac084",
      "name": "Analyze user selfie with Gemini",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        8416,
        1104
      ],
      "parameters": {
        "text": "You are an image analysis assistant.\n\nTask:\nAnalyze the image and determine whether EXACTLY ONE real human person is visible.\n\nRules:\n- Count only real human beings.\n- Do NOT count mannequins, statues, drawings, posters, images on screens, or reflections.\n- If there is exactly 1 person \u2192 respond \"YES\"\n- If there are 0 people \u2192 respond \"NO\"\n- If there are 2 or more people \u2192 respond \"NO\"\n\nOutput format:\nRespond with ONLY one word: YES or NO\nDo not include any extra text, explanation, punctuation, or formatting.\n",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-2.5-flash",
          "cachedResultName": "models/gemini-2.5-flash"
        },
        "options": {},
        "resource": "image",
        "inputType": "binary",
        "operation": "analyze",
        "binaryPropertyName": "user_image"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6dcebd94-f7a0-4455-8c2a-0470cbbac32a",
      "name": "Send text response to user",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        8656,
        2192
      ],
      "parameters": {
        "textBody": "={{ $json.responseText }}",
        "operation": "send",
        "phoneNumberId": "804155476125570",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('WhatsApp message trigger').item.json.contacts[0].wa_id }}"
      },
      "typeVersion": 1
    },
    {
      "id": "b8e70696-1f3e-49f2-82d1-6e0da5467d0b",
      "name": "Ask user to resend valid photo",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        9312,
        1200
      ],
      "parameters": {
        "textBody": "\u26a0\ufe0f We couldn't detect exactly one person in your photo.\n\nPlease make sure:\n\u2022 Only one person is in the frame\n\u2022 You have a clear front-facing view\n\u2022 The lighting is good\n\nPlease try again.",
        "operation": "send",
        "phoneNumberId": "804155476125570",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Route interactive vs image message').item.json.contacts[0].wa_id }}"
      },
      "typeVersion": 1
    },
    {
      "id": "50f03b72-dede-45a6-b110-d1c64f542dc6",
      "name": "Notify user VTO is processing",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        9312,
        816
      ],
      "parameters": {
        "textBody": "\u23f3 Creating your virtual try-on... This may take a moment. Please wait.",
        "operation": "send",
        "phoneNumberId": "804155476125570",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Route interactive vs image message').item.json.contacts[0].wa_id }}"
      },
      "typeVersion": 1
    },
    {
      "id": "c449500b-2303-498a-9d9d-d6c7115a78b6",
      "name": "Parse button and user data",
      "type": "n8n-nodes-base.code",
      "position": [
        6016,
        80
      ],
      "parameters": {
        "jsCode": "const message = $input.first().json.entry?.[0]?.changes?.[0]?.value?.messages?.[0];\nconst contact = $input.first().json.entry?.[0]?.changes?.[0]?.value?.contacts?.[0];\n\nconst buttonId = $input.first().json.messages[0].interactive.button_reply.id;\nconst waId = $input.first().json.contacts[0].wa_id;\nconst userName = $input.first().json.contacts[0].profile.name;\n\nlet prefix = null;\nlet productId = null;\n\nif (buttonId?.startsWith(\"VTO_\")) {\n    prefix = \"VTO_\";\n    productId = buttonId.substring(4);\n} else if (buttonId?.startsWith(\"ORDER_\")) {\n    prefix = \"ORDER_\";\n    productId = buttonId.substring(6);\n}\n\n$input.first().json.prefix = prefix;\n$input.first().json.productId = productId;\n$input.first().json.waId = waId;\n$input.first().json.userName = userName;\n\nreturn $input.first();"
      },
      "typeVersion": 2
    },
    {
      "id": "1de08af7-976a-482b-8ffc-bd121a557769",
      "name": "\ud83d\udccc Group: Entry & Router1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5632,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 956,
        "height": 608,
        "content": "## \ud83d\udce5 Entry & Message Router\nReceives all incoming WhatsApp messages.\nRoutes traffic by type:\n- Button/image \u2192 interactive handler\n- Text \u2192 validation pipeline"
      },
      "typeVersion": 1
    },
    {
      "id": "78e04d36-5352-4bc3-8419-5d82c93b7449",
      "name": "WhatsApp message trigger",
      "type": "n8n-nodes-base.whatsAppTrigger",
      "position": [
        5296,
        1024
      ],
      "parameters": {
        "options": {},
        "updates": [
          "messages"
        ]
      },
      "typeVersion": 1
    },
    {
      "id": "448fed6d-99a9-47fb-9073-fc14f23046f7",
      "name": "Group: VTO Context Check",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7072,
        880
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 492,
        "content": "## \ud83d\udd34 VTO Context Check\nGETs `vto_context_{waId}` from Redis. If a product ID is stored (user previously tapped Try-On), proceed to image processing. If not found, ask user to tap the Try-On button on a product first."
      },
      "typeVersion": 1
    },
    {
      "id": "0ec56bf9-33a5-44ef-bcc7-2d8154f09c85",
      "name": "Group: Image Preparation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7936,
        544
      ],
      "parameters": {
        "color": 7,
        "width": 1056,
        "height": 780,
        "content": "## \ud83d\uddbc\ufe0f Image Download & Preparation\nFetches product image from MongoDB \u2192 downloads from Google Drive \u2192 resizes to 1024\u00d71024. In parallel, fetches the user selfie from WhatsApp media API \u2192 downloads \u2192 resizes to 1024\u00d71024. Both images are converted to base64 for Gemini."
      },
      "typeVersion": 1
    },
    {
      "id": "d42de00f-749e-4c6d-8971-c64b66c60937",
      "name": "Group: Validate Generate Send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9024,
        688
      ],
      "parameters": {
        "color": 7,
        "width": 1600,
        "height": 716,
        "content": "## \ud83e\udd16 Validate, Generate & Send\n\nGemini checks if exactly one real person is in the selfie. If valid, both images are sent to Gemini 2.5 Flash to generate the try-on result. Output is uploaded to WhatsApp and sent to the user. Redis VTO context is cleared after delivery. Invalid photos prompt a retry."
      },
      "typeVersion": 1
    },
    {
      "id": "8a0ef9cf-6ebc-4d52-b25f-ac3f71364385",
      "name": "Check if message is button or image",
      "type": "n8n-nodes-base.if",
      "position": [
        5584,
        1024
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "f9b79970-779c-4668-9502-02e7a04a46fb",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.messages[0].type }}",
              "rightValue": "interactive"
            },
            {
              "id": "176886ef-31af-4331-8b1c-b77a8786f5e9",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.messages[0].type }}",
              "rightValue": "image"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8180e0b1-eb66-415f-a60c-20728897d6ed",
      "name": "Route interactive vs image message",
      "type": "n8n-nodes-base.switch",
      "position": [
        5824,
        368
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Button click",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "e6a2eb2d-bd6d-4952-b88d-71286c9f6a7d",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messages[0].type }}",
                    "rightValue": "interactive"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Image received",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "180b7e41-002f-4889-80ac-a52145b377f0",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messages[0].type }}",
                    "rightValue": "image"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "805b97d3-2874-43ff-b2d6-8a5a5b1b284e",
      "name": "Route VTO vs order button tap",
      "type": "n8n-nodes-base.switch",
      "position": [
        6240,
        80
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "VTO",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "87d43119-f8cf-4c44-a045-13fb5a8c586d",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.prefix }}",
                    "rightValue": "VTO_"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Order",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "8522fa38-d834-476b-83c8-0f22957831c3",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.prefix }}",
                    "rightValue": "ORDER_"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "13be1098-b721-40ba-8518-76250f54f965",
      "name": "Route JSON intent vs plain text reply",
      "type": "n8n-nodes-base.if",
      "position": [
        8432,
        2176
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "89f70c2d-204c-432a-9368-cf12cd5bb720",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.responseType }}",
              "rightValue": "json"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "eabef4a8-9a42-45a3-b4bd-e30b0311d358",
      "name": "Route product search vs recommend",
      "type": "n8n-nodes-base.switch",
      "position": [
        8656,
        1936
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "product_search",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "8b9ecbc4-30cb-4cf8-9e03-f21f6583f76a",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.intentData.intent }}",
                    "rightValue": "product_search"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "recommend",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "5ba5dc9c-1a53-42e1-af9e-860993f8b1c1",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.intentData.intent }}",
                    "rightValue": "recommend"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "c1bba81c-3a96-4a10-827b-bf9e27a2c7c2",
      "name": "Is product cached in Redis?",
      "type": "n8n-nodes-base.if",
      "position": [
        9328,
        1872
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "6b6fdef2-9976-4eb7-bb53-a74dd2afae94",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.cachedProductsJson }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "87239b9b-7596-4016-aab6-3d1d30007d63",
      "name": "Pass valid messages, block invalid",
      "type": "n8n-nodes-base.if",
      "position": [
        6208,
        2304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
         

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

How this works

This workflow lets shoppers browse and try on clothing through WhatsApp without leaving the chat, using AI to classify requests, search a product catalogue, and generate virtual try-on images with Gemini. Retail teams and e-commerce operators use it to cut response times and boost conversions by delivering personalised product suggestions and orders directly in the messaging app. The key step is the AI classification node that routes each incoming message to the right flow, whether it is a product query, size check, or new order.

Use it for high-volume WhatsApp sales channels that already have a product catalogue in Google Sheets or MongoDB; avoid it if your inventory changes every few hours or if you need complex payment gateways beyond basic order logging. Common variations include swapping Gemini for GPT-4o vision models or adding Redis-based session limits to control concurrent users.

About this workflow

πŸ“Œ Overview

Source: https://n8n.io/workflows/13506/ β€” original creator credit. Request a take-down β†’

More AI & RAG workflows β†’ Β· Browse all categories β†’

Related workflows

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

AI & RAG

Your AI workforce is ready. Are you?

Google Sheets Tool, Mcp Trigger, Google Drive +29
AI & RAG

The "WhatsApp Productivity Assistant with Memory and AI Imaging" is a comprehensive n8n workflow that transforms your WhatsApp into a powerful, multi-talented AI assistant. It's designed to handle a w

WhatsApp Trigger, Agent, HTTP Request +20
AI & RAG

Who is this for? This workflow is ideal for HR teams, startups, and enterprises that want to handle employee interactions through WhatsApp and automate responses using LLM (OpenAI) and intelligent rou

WhatsApp Trigger, OpenAI, OpenAI Chat +13
AI & RAG

Automate Outreach Prospect automates finding, enriching, and messaging potential partners (like restaurants, malls, and bars) using Apify Google Maps scraping, Perplexity enrichment, OpenAI LLMs, Goog

@Devlikeapro/N8N Nodes Waha, Google Drive Trigger, @Apify/N8N Nodes Apify +14
AI & RAG

Chat with docs - 5minAI New version. Uses httpRequest, documentDefaultDataLoader, textSplitterRecursiveCharacterTextSplitter, embeddingsOpenAi. Event-driven trigger; 62 nodes.

HTTP Request, Document Default Data Loader, Text Splitter Recursive Character Text Splitter +10