{
  "name": "Build an omnichannel OTP verification flow",
  "nodes": [
    {
      "id": "870cf631-17bf-45bf-a865-afcb775b48b6",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        736,
        1360
      ]
    },
    {
      "id": "32ac0aaf-aefd-4259-83af-c4730711c3f7",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        1360,
        1360
      ]
    },
    {
      "id": "318e9acf-08ff-4087-b899-f9046bc55e32",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        896
      ],
      "parameters": {
        "width": null,
        "height": null,
        "content": "## the user exists\n"
      }
    },
    {
      "id": "4f624126-8d43-4161-a36b-eaf0a6a16b83",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        1856
      ],
      "parameters": {
        "width": null,
        "height": null,
        "content": "## the user does not exist"
      }
    },
    {
      "id": "f124e921-285b-4933-b89c-9aad8f6dcfa8",
      "name": "Execute a SQL query",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1664,
        1632
      ]
    },
    {
      "id": "b3bb2f0a-a796-4a3a-9b7a-8de5e6638165",
      "name": "If1",
      "type": "n8n-nodes-base.if",
      "position": [
        2256,
        1552
      ]
    },
    {
      "id": "df899e6e-2829-4824-897a-b38ce6cd1341",
      "name": "waiting_for_otp",
      "type": "n8n-nodes-base.postgres",
      "position": [
        4016,
        288
      ]
    },
    {
      "id": "ba17533b-03ac-4216-a042-9fd10c113a77",
      "name": "If2- extracted_email",
      "type": "n8n-nodes-base.if",
      "position": [
        2928,
        304
      ]
    },
    {
      "id": "0b532844-bc77-429d-80c0-4d2b42656f87",
      "name": "Send email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3792,
        288
      ]
    },
    {
      "id": "8f1b6e70-2b32-4ea7-aa0c-801e17f54c3f",
      "name": "If2",
      "type": "n8n-nodes-base.if",
      "position": [
        4240,
        288
      ]
    },
    {
      "id": "450bbb83-2d89-4236-8a5f-c122fcbd16b6",
      "name": "Execute a SQL query1",
      "type": "n8n-nodes-base.postgres",
      "position": [
        2928,
        2080
      ]
    },
    {
      "id": "9ac99506-f4f4-4913-9692-38392c909e26",
      "name": "Execute a SQL query2",
      "type": "n8n-nodes-base.postgres",
      "position": [
        3920,
        2064
      ]
    },
    {
      "id": "57cc8561-a7ad-4b68-bb16-2f9d640e61c5",
      "name": "IF_resend_otp",
      "type": "n8n-nodes-base.if",
      "position": [
        2720,
        1856
      ]
    },
    {
      "id": "5b96d50b-4395-4243-ab57-6a40cd2d3267",
      "name": "Execute a SQL query3",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1728,
        1248
      ]
    },
    {
      "id": "a3eda303-0fd6-4507-b9a9-55846e0c0691",
      "name": "Execute a SQL query5",
      "type": "n8n-nodes-base.postgres",
      "position": [
        3216,
        288
      ]
    },
    {
      "id": "d5bfed53-4155-4210-8498-166d3de79e71",
      "name": "If4",
      "type": "n8n-nodes-base.if",
      "position": [
        2688,
        832
      ]
    },
    {
      "id": "b1465b3c-179e-4d80-b811-9de62413e731",
      "name": "IF_step- waiting_otp",
      "type": "n8n-nodes-base.if",
      "position": [
        2512,
        2144
      ]
    },
    {
      "id": "924f50e9-8d46-4dd8-aeec-78a2fa213710",
      "name": "If email en session",
      "type": "n8n-nodes-base.if",
      "position": [
        4336,
        2064
      ]
    },
    {
      "id": "bf02cbaf-d67f-4038-a262-c0184606b8e5",
      "name": "Upsert user_channel",
      "type": "n8n-nodes-base.postgres",
      "position": [
        4848,
        2048
      ]
    },
    {
      "id": "a1e122d2-e5c9-4298-9bc3-9858f5f7030e",
      "name": "Universal normalization",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        1360
      ]
    },
    {
      "id": "4cd10c7d-62f7-4b15-9480-f6e7c6577c31",
      "name": "Check if the channel already exists",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1152,
        1360
      ]
    },
    {
      "id": "2c85064f-863e-407c-827c-14922c94bbf9",
      "name": "Channel already verified",
      "type": "n8n-nodes-base.set",
      "position": [
        2000,
        1248
      ]
    },
    {
      "id": "5211c7a7-472c-4da3-8cd1-06b766a18968",
      "name": "Search onboarding_session",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1984,
        1632
      ]
    },
    {
      "id": "fbb4941f-3b12-4871-8ab4-57db409d046a",
      "name": "Extract email",
      "type": "n8n-nodes-base.code",
      "position": [
        2480,
        832
      ]
    },
    {
      "id": "aba11586-756c-455d-a229-30ce2870366b",
      "name": "Greeting 1 - send your email",
      "type": "n8n-nodes-base.set",
      "position": [
        2800,
        1008
      ]
    },
    {
      "id": "c8913fe3-9a74-49af-8bdf-94ba27ffb10d",
      "name": "Generate OTP",
      "type": "n8n-nodes-base.code",
      "position": [
        3440,
        288
      ]
    },
    {
      "id": "647aa9aa-aecc-4836-9e83-c3d5d8c968ea",
      "name": "Save OTP",
      "type": "n8n-nodes-base.postgres",
      "position": [
        3616,
        288
      ]
    },
    {
      "id": "2cf8c2a4-0f5c-438c-9aeb-fb4d061506f7",
      "name": "response_message1 - verification code sent",
      "type": "n8n-nodes-base.set",
      "position": [
        4512,
        416
      ]
    },
    {
      "id": "b1f87e35-0a19-4331-8d65-eb6d8eab28cd",
      "name": "IF_code_exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        3264,
        2080
      ]
    },
    {
      "id": "f9f76032-3329-4e84-9abd-cc7a09da59a2",
      "name": "code does not exist",
      "type": "n8n-nodes-base.set",
      "position": [
        3328,
        2464
      ]
    },
    {
      "id": "bcc811a6-d8ab-481e-9c1c-8fc92cb00d90",
      "name": "valid OTP",
      "type": "n8n-nodes-base.postgres",
      "position": [
        3696,
        2064
      ]
    },
    {
      "id": "b9ef2797-fd43-4faf-bf9a-24eb24fd5c49",
      "name": "Get verified session email",
      "type": "n8n-nodes-base.postgres",
      "position": [
        4112,
        2064
      ]
    },
    {
      "id": "514fc91c-ad13-461a-b77e-07eb259bdb91",
      "name": "Missing email response",
      "type": "n8n-nodes-base.set",
      "position": [
        4592,
        2400
      ]
    },
    {
      "id": "fb6eb345-fe1b-4898-8b51-4b7a2ad43cf4",
      "name": "Upsert user by email",
      "type": "n8n-nodes-base.postgres",
      "position": [
        4576,
        2048
      ]
    },
    {
      "id": "8d44f896-f774-4853-a82d-ad5be67b13b0",
      "name": "Verification completed response",
      "type": "n8n-nodes-base.set",
      "position": [
        5152,
        2048
      ]
    },
    {
      "id": "922a8ec9-3581-421e-973d-1209bfa0f4fb",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        1136
      ],
      "parameters": {
        "width": 544,
        "height": 608,
        "content": "## Omnichannel OTP Verification & User Onboarding\n\n### How it works\nThis workflow centralizes user verification across WhatsApp, Telegram, and email using OTP validation.\n\nIt detects the channel, mana"
      }
    },
    {
      "id": "6c92564b-02cf-47a8-9881-4ce41dd45f2c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        1152
      ],
      "parameters": {
        "width": 752,
        "height": 512,
        "content": "## Section 1 — Channel Detection & Normalization\n\nReceives incoming requests from multiple platforms and identifies the communication channel.\n\nNormalizes user data into a unified structure before che"
      }
    },
    {
      "id": "55666d14-9851-4b68-b504-34c708dcc145",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        1024
      ],
      "parameters": {
        "width": 672,
        "height": 384,
        "content": "## Section 2 — Existing Channel Validation\n\n- Checks if the incoming channel is already associated with a verified user.\n\n- If found, skips the verification process and continues with the existing pro"
      }
    },
    {
      "id": "0c7ce79c-bc41-447b-a17c-f8e2434796a8",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        1472
      ],
      "parameters": {
        "width": 624,
        "height": 384,
        "content": "## Section 3 — Session Initialization\n\n- Creates or retrieves the onboarding session for the current channel.\n\n- Tracks the current step of the verification process."
      }
    },
    {
      "id": "0c099410-84a3-46c3-95a2-5671b2bc88f1",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2304,
        576
      ],
      "parameters": {
        "width": 704,
        "height": 608,
        "content": "## Section 4 — Email Extraction & Validation\n\n- Extracts a valid email from the user message using regex detection.\n\n- If no email is found, prompts the user to provide one before continuing."
      }
    },
    {
      "id": "b8f1b027-e7b3-401d-86c9-45a26752a4cd",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3152,
        64
      ],
      "parameters": {
        "width": 1568,
        "height": 576,
        "content": "## Section 5 — OTP Generation & Delivery\n\n- Generates a secure one-time password and stores it with an expiration timestamp.\n\n- Sends the OTP via email and updates the session to waiting_for_otp.\n\n- A"
      }
    },
    {
      "id": "37e80802-135a-419c-968c-23ed5c222b4e",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2480,
        1648
      ],
      "parameters": {
        "width": 1088,
        "height": 1072,
        "content": "## Section 4 — Email Extraction & Validation\n\n- Extracts a valid email from the user message using regex detection.\n\n- If no email is found, prompts the user to provide one before continuing.\n\n- A mes"
      }
    },
    {
      "id": "3b112758-2051-4a1d-85b7-cd5ebf9210a8",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3664,
        1728
      ],
      "parameters": {
        "width": 1760,
        "height": 976,
        "content": "## Section 7 — Identity Consolidation\n\n- Marks the OTP as used and updates the session as verified in the Supabase database.\n\n- Links the verified channel to a single global user ID to ensure omnichan"
      }
    },
    {
      "id": "fac105f0-454d-4905-8d2d-5322c67b897e",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3792,
        2880
      ],
      "parameters": {
        "width": 544,
        "height": 272,
        "content": "## DATABASE REQUIRED\n\nThis workflow depends on predefined Postgres tables.\n\nYou must create users, user_channels, user_otps, and onboarding_sessions before running the flow."
      }
    },
    {
      "id": "5c4adad1-c940-427d-a7d5-1d3b6eba74e3",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3408,
        -272
      ],
      "parameters": {
        "width": 304,
        "height": 208,
        "content": "## Security Notice\n- Do not log OTP codes in production environments.\n\n- Always define an expiration time and mark codes as used after validation."
      }
    }
  ],
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Execute a SQL query3",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Execute a SQL query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If1": {
      "main": [
        [
          {
            "node": "Extract email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "IF_step- waiting_otp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If2": {
      "main": [
        [],
        [
          {
            "node": "response_message1 - verification code sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If4": {
      "main": [
        [
          {
            "node": "If2- extracted_email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Greeting 1 - send your email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Universal normalization",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save OTP": {
      "main": [
        [
          {
            "node": "Send email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "valid OTP": {
      "main": [
        [
          {
            "node": "Execute a SQL query2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send email": {
      "main": [
        [
          {
            "node": "waiting_for_otp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate OTP": {
      "main": [
        [
          {
            "node": "Save OTP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract email": {
      "main": [
        [
          {
            "node": "If4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF_resend_otp": {
      "main": [
        [],
        [
          {
            "node": "Execute a SQL query1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF_code_exists?": {
      "main": [
        [
          {
            "node": "valid OTP",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "code does not exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "waiting_for_otp": {
      "main": [
        [
          {
            "node": "If2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute a SQL query": {
      "main": [
        [
          {
            "node": "Search onboarding_session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If email en session": {
      "main": [
        [
          {
            "node": "Upsert user by email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Missing email response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert user_channel": {
      "main": [
        [
          {
            "node": "Verification completed response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute a SQL query1": {
      "main": [
        [
          {
            "node": "IF_code_exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute a SQL query2": {
      "main": [
        [
          {
            "node": "Get verified session email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute a SQL query3": {
      "main": [
        [
          {
            "node": "Channel already verified",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute a SQL query5": {
      "main": [
        [
          {
            "node": "Generate OTP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF_step- waiting_otp": {
      "main": [
        [
          {
            "node": "IF_resend_otp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If2- extracted_email": {
      "main": [
        [
          {
            "node": "Execute a SQL query5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert user by email": {
      "main": [
        [
          {
            "node": "Upsert user_channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Universal normalization": {
      "main": [
        [
          {
            "node": "Check if the channel already exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search onboarding_session": {
      "main": [
        [
          {
            "node": "If1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get verified session email": {
      "main": [
        [
          {
            "node": "If email en session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if the channel already exists": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}