← All Developer Tools Workflows
BillionVerifyAuthentica

Register users and authenticate with magic links using Google Sheets

Pull contacts, verify each address with BillionVerify, and continue to Authentica — only deliverable addresses get through.

Why verify before the send

Sending to invalid, risky, catch-all, or disposable addresses spikes your bounce rate and erodes sender reputation. A verification gate before the Authentica step removes that risk automatically — only deliverable addresses continue, the rest are flagged.

The workflow

BillionVerify — verification sits right before the send.

+13
n8n steps
+18
n8n steps

Node by node

  1. 1
    On form registerTrigger· n8n

    Starts the workflow — on a schedule, a webhook, or manually while you test.

  2. 2
    On form loginTrigger· n8n

    Starts the workflow — on a schedule, a webhook, or manually while you test.

  3. 3
    AuthSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  4. 4
    ProfileSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  5. 5
    Create password and tokenSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  6. 6
    Check username/passwordSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  7. 7
    If has tokenLogic· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  8. 8
    Check sid cookies headerSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  9. 9
    Append or update row in sheetSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  10. 10
    If login successLogic· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  11. 11
    Search by tokenSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  12. 12
    Invalid tokenSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  13. 13
    Search sidSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  14. 14
    Verify Email (BillionVerify)Verify· billionverify

    The BillionVerify node verifies the address — status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score — before anything is sent.

  15. 15
    If has token in DBLogic· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  16. 16
    Redirect to login formSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  17. 17
    If token validLogic· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  18. 18
    If has data with sidLogic· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  19. 19
    IF deliverableLogic· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  20. 20
    Redirect to auth urlSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  21. 21
    Generate tokenSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  22. 22
    Prepare sidSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  23. 23
    Redirect to login form1Source· n8n

    Provides or transforms the contact data flowing through the workflow.

  24. 24
    Prepare htmlSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  25. 25
    Redirect to loginSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  26. 26
    Send username/passwordSend· n8n

    Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.

  27. 27
    Add token to DBSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  28. 28
    Redirect to profile and set cookiesSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  29. 29
    Update sidSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  30. 30
    Show profile pageSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  31. 31
    Notice register successSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  32. 32
    Redirect to auth with new tokenSource· n8n

    Provides or transforms the contact data flowing through the workflow.

Workflow JSON

Copy or download this workflow, then import it in n8n (Workflows → Import from File / Paste). Install the BillionVerify community node first, then add your API key credential.

verify-emails-in-authentica.json
{
  "name": "Register users and authenticate with magic links using Google Sheets + BillionVerify",
  "nodes": [
    {
      "id": "dcaa8a9d-abdd-41a8-a359-868318d7f661",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 1904,
        "height": 384,
        "content": "## 1. Login\nValidate username and password against Google Sheets, then reuse or issue a new token and redirect to the auth endpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "44324518-59de-4068-b49d-da5b9cabcf66",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 1232,
        "height": 288,
        "content": "## 0. Register\nCollect an email address via form, generate credentials and a one-time token, save the user to Google Sheets, and send an onboarding email with the login link."
      },
      "typeVersion": 1
    },
    {
      "id": "2472b7c4-cd6e-4194-8d12-59c4249ea7f3",
      "name": "Append or update row in sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        688,
        320
      ],
      "parameters": {
        "columns": {
          "value": {
            "role": "Property Manager",
            "token": "YOUR_CREDENTIAL_HERE",
            "password": "YOUR_CREDENTIAL_HERE",
            "username": "={{ $json.email }}"
          },
          "schema": [
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "password",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "password",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "token",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "token",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "username"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "d7f38863-74ee-4bb9-bb4d-2fd445594eea",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        1072
      ],
      "parameters": {
        "color": 7,
        "width": 1696,
        "height": 400,
        "content": "## 2. Auth\nValidate the one-time token via Google Sheets, generate a session id (sid), set an HttpOnly cookie, clear the token, and redirect to the profile page."
      },
      "typeVersion": 1
    },
    {
      "id": "891d65a6-0b6c-474d-b029-d4702c8e4174",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        1552
      ],
      "parameters": {
        "color": 7,
        "width": 1424,
        "height": 416,
        "content": "## 3. Profile\nRead the sid cookie, look up the active session in Google Sheets, and render a profile page — or redirect to login if the session is invalid."
      },
      "typeVersion": 1
    },
    {
      "id": "fee598fe-64d8-469e-be7d-745bd2be40e4",
      "name": "Prepare sid",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        1088
      ],
      "parameters": {
        "jsCode": "function makeSid(len = 48) {\n  const alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n  let out = \"\";\n  // Mix time + random to reduce collisions\n  let seed = Date.now().toString(36) + \"-\" + Math.random().toString(36).slice(2);\n\n  out += seed.replace(/[^a-z0-9-]/gi, \"\").slice(0, 12);\n\n  while (out.length < len) {\n    out += alphabet[Math.floor(Math.random() * alphabet.length)];\n  }\n  return out.slice(0, len);\n}\n\nconst sid = makeSid(64);\n\nreturn { json: { sid } };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f8747e29-893d-4e5d-916c-bb56975eb881",
      "name": "Generate token",
      "type": "n8n-nodes-base.code",
      "position": [
        1328,
        816
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function makeToken(len = 32) {\n  let out = '';\n  while (out.length < len) {\n    out += Math.random().toString(36).slice(2); // Strip the leading \"0.\"\n  }\n  return out.slice(0, len);\n}\nconst token = makeToken()\nreturn {token};"
      },
      "typeVersion": 2
    },
    {
      "id": "fd48786d-6e97-4361-bce0-40dc80596ba2",
      "name": "If has token in DB",
      "type": "n8n-nodes-base.if",
      "position": [
        976,
        640
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "df0d0deb-4db8-4ef7-9bd0-c79ab6fb4f35",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('Check username/password').item.json.token }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "43b3e0a0-c749-4b08-8cc1-0e84fe357bed",
      "name": "If has token",
      "type": "n8n-nodes-base.if",
      "position": [
        464,
        1200
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "51cff10a-dac2-4480-a177-3b6166e4fbdf",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('Auth').item.json.query.token }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "18b1b20d-06cb-44c5-b295-2966ff15c84f",
      "name": "Search by token",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        736,
        1184
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Auth').item.json.query.token || '' }}",
              "lookupColumn": "token"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "dc27c8ff-75d6-4448-afb2-512d21cb08bf",
      "name": "On form register",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        224,
        320
      ],
      "webhookId": "eff757ba-8869-4106-b386-8c3b21baa912",
      "parameters": {
        "options": {
          "path": "register"
        },
        "formTitle": "Register with your email",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Email",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Password will be sent to your email"
      },
      "typeVersion": 2.2
    },
    {
      "id": "135c6108-45ec-45e6-b1e2-317e40a9004f",
      "name": "Create password and token",
      "type": "n8n-nodes-base.code",
      "position": [
        448,
        320
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function generatePassword(length = 12) {\n  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n  let out = '';\n  for (let i = 0; i < length; i++) {\n    const idx = Math.floor(Math.random() * chars.length);\n    out += chars[idx];\n  }\n  return out;\n}\nfunction makeToken(len = 32) {\n  let out = '';\n  while (out.length < len) {\n    out += Math.random().toString(36).slice(2); // Strip the leading \"0.\"\n  }\n  return out.slice(0, len);\n}\nconst email = $json.Email\nconst password = generatePassword()\nconst token = makeToken()\nreturn {email, password, token};"
      },
      "typeVersion": 2
    },
    {
      "id": "fec2646b-dd15-4e8c-ad07-9fa7de065c9a",
      "name": "Send username/password",
      "type": "n8n-nodes-base.gmail",
      "position": [
        928,
        320
      ],
      "webhookId": "f66d080e-d94a-4244-99aa-fc2ebcbbece3",
      "parameters": {
        "sendTo": "={{ $('Create password and token').item.json.email }}",
        "message": "=Link to login: https://YOUR_DOMAIN/webhook/auth?token={{ $('Create password and token').item.json.token }}\n<br/>\nUsername: {{ $('Create password and token').item.json.email }}\n<br/>\nPassword: {{ $('Create password and token').item.json.password }}",
        "options": {},
        "subject": "=Your login credentials"
      },
      "typeVersion": 2.1
    },
    {
      "id": "f3241140-939d-4871-8bf3-25dc9c69b653",
      "name": "Notice register success",
      "type": "n8n-nodes-base.form",
      "position": [
        1184,
        320
      ],
      "webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "Registration successful",
        "completionMessage": "=<p><strong>✅ Please check email {{ $('Create password and token').item.json.email }} to get your login credentials</strong></p>\n<p>\n  After that, you can login here:<br>\n  <a href=\"https://YOUR_DOMAIN/form/login\" target=\"_blank\" rel=\"noopener noreferrer\">\n    https://YOUR_DOMAIN/form/login\n  </a>\n</p>"
      },
      "typeVersion": 1
    },
    {
      "id": "3da0b92c-edad-43e2-bcf1-e7bca316d7e9",
      "name": "On form login",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        224,
        736
      ],
      "webhookId": "f738ecda-fe2c-4892-8fd6-382ec06f45fd",
      "parameters": {
        "options": {
          "path": "login"
        },
        "formTitle": "Login",
        "formFields": {
          "values": [
            {
              "fieldLabel": "username",
              "requiredField": true
            },
            {
              "fieldType": "password",
              "fieldLabel": "password",
              "requiredField": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "caceffd3-862e-4ffc-817e-ed1300546caa",
      "name": "Check username/password",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        480,
        736
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.username }}",
              "lookupColumn": "username"
            },
            {
              "lookupValue": "={{ $json.password }}",
              "lookupColumn": "password"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "91c227fd-ecbb-4d9f-a608-4606453a0e93",
      "name": "If login success",
      "type": "n8n-nodes-base.if",
      "position": [
        720,
        736
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "455881ad-941f-4257-9427-1e48e7bc2974",
              "operator": {
                "type": "object",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "cedd4efe-c0e3-447b-a368-f4da51b0e004",
      "name": "Redirect to login form",
      "type": "n8n-nodes-base.form",
      "position": [
        992,
        816
      ],
      "webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
      "parameters": {
        "options": {},
        "operation": "completion",
        "redirectUrl": "=https://YOUR_DOMAIN/form/login",
        "respondWith": "redirect"
      },
      "typeVersion": 1
    },
    {
      "id": "6c2905c0-67d1-4c29-8ee5-3676d6ab1255",
      "name": "Redirect to auth url",
      "type": "n8n-nodes-base.form",
      "position": [
        1312,
        624
      ],
      "webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
      "parameters": {
        "options": {},
        "operation": "completion",
        "redirectUrl": "=https://YOUR_DOMAIN/webhook/auth?token={{ $json.token }}",
        "respondWith": "redirect"
      },
      "typeVersion": 1
    },
    {
      "id": "52426a88-2b8f-4839-b608-8c28e8cef5b1",
      "name": "Add token to DB",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1568,
        816
      ],
      "parameters": {
        "columns": {
          "value": {
            "token": "YOUR_CREDENTIAL_HERE",
            "username": "={{ $('Check username/password').item.json.username }}"
          },
          "schema": [
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "password",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "password",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "token",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "token",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "username"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "1a3c065f-b882-4088-9c34-5ebbb4fb787a",
      "name": "Redirect to auth with new token",
      "type": "n8n-nodes-base.form",
      "position": [
        1840,
        816
      ],
      "webhookId": "9a192624-7617-44d3-a3c2-80e2c1839422",
      "parameters": {
        "options": {},
        "operation": "completion",
        "redirectUrl": "=https://YOUR_DOMAIN/webhook/auth?token={{ $('Generate token').item.json.token }}",
        "respondWith": "redirect"
      },
      "typeVersion": 1
    },
    {
      "id": "b4e2aebf-3f52-4234-ae86-f7c29f2a226d",
      "name": "Auth",
      "type": "n8n-nodes-base.webhook",
      "position": [
        208,
        1200
      ],
      "webhookId": "6f7a23ce-b823-4dcb-af08-c7e91114292b",
      "parameters": {
        "path": "auth",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "340dcc3d-797c-44ca-a5ab-830687fbfffa",
      "name": "Invalid token",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        736,
        1344
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "Invalid token"
      },
      "typeVersion": 1.4
    },
    {
      "id": "6119d68b-76d0-40ce-8ac1-df2253ca1a81",
      "name": "If token valid",
      "type": "n8n-nodes-base.if",
      "position": [
        976,
        1184
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a29fd91f-24e3-4302-9735-25419166511b",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.username }}",
              "rightValue": ""
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "d3588bf0-be76-4e85-9ac4-ce11de129b25",
      "name": "Redirect to login form1",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1248,
        1280
      ],
      "parameters": {
        "options": {},
        "redirectURL": "={{ 'https://' + $('Auth').item.json.headers.host }}/form/login",
        "respondWith": "redirect"
      },
      "typeVersion": 1.4
    },
    {
      "id": "4d02d506-b001-45b8-afab-32ac76d29896",
      "name": "Update sid",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1600,
        1088
      ],
      "parameters": {
        "columns": {
          "value": {
            "sid": "={{ $json.sid }}",
            "token": "",
            "username": "={{ $('Search by token').item.json.username }}"
          },
          "schema": [
            {
              "id": "username",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "password",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "password",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "token",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "token",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sid",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "sid",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "username"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "0960c383-12b3-4176-8501-3b85d9453256",
      "name": "Redirect to profile and set cookies",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1600,
        1280
      ],
      "parameters": {
        "options": {
          "responseCode": 302,
          "responseHeaders": {
            "entries": [
              {
                "name": "Set-Cookie",
                "value": "=sid={{$json.sid}}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=604800"
              },
              {
                "name": "Location",
                "value": "/webhook/profile"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "={{ $json.sid }}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "ed4908f3-50f0-4150-adbc-ca5b706a94e3",
      "name": "Profile",
      "type": "n8n-nodes-base.webhook",
      "position": [
        224,
        1712
      ],
      "webhookId": "6f7a23ce-b823-4dcb-af08-c7e91114292b",
      "parameters": {
        "path": "profile",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2d514f17-01f8-4410-a759-50289488502f",
      "name": "Check sid cookies header",
      "type": "n8n-nodes-base.code",
      "position": [
        448,
        1712
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "function getCookie(headers, name) {\n  const raw = (headers.cookie || headers.Cookie || \"\");\n  if (!raw) return null;\n\n  const parts = raw.split(\";\").map(s => s.trim());\n  for (const p of parts) {\n    const eq = p.indexOf(\"=\");\n    if (eq === -1) continue;\n    const k = p.slice(0, eq).trim();\n    const v = p.slice(eq + 1).trim();\n    if (k === name) return decodeURIComponent(v);\n  }\n  return null;\n}\n\nconst headers = $json.headers || {};\nconst sid = getCookie(headers, \"sid\");\n\nreturn {\n  json: {\n    sid,\n    hasCookieHeader: !!(headers.cookie || headers.Cookie),\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9d062264-f2c0-4659-b68f-714b03389ac4",
      "name": "Search sid",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        672,
        1712
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.sid || '' }}",
              "lookupColumn": "sid"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sheet2"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "d7dd2d42-a6bf-4adb-9571-1736cdb11703",
      "name": "If has data with sid",
      "type": "n8n-nodes-base.if",
      "position": [
        880,
        1712
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a29fd91f-24e3-4302-9735-25419166511b",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.username }}",
              "rightValue": ""
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "89d72c2d-0f9a-4e63-a1e0-7245cdafad39",
      "name": "Redirect to login",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1168,
        1808
      ],
      "parameters": {
        "options": {},
        "redirectURL": "={{ 'https://' + $('Profile').item.json.headers.host }}/form/login",
        "respondWith": "redirect"
      },
      "typeVersion": 1.4
    },
    {
      "id": "e13952a5-dc41-4429-a0ba-bb918e18e41f",
      "name": "Prepare html",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        1568
      ],
      "parameters": {
        "jsCode": "const html = `Hello ${$input.first().json.username}`;\n\nreturn { json: { html } };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "bbb5e14a-e5f1-4eff-acb0-e02203922a70",
      "name": "Show profile page",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1392,
        1568
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "={{ $json.html }}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "9496073d-aaa6-4d65-a2d5-a4ee7cd3c85c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -896,
        576
      ],
      "parameters": {
        "width": 880,
        "height": 720,
        "content": "## Overview\nThis workflow provides a lightweight authentication flow for n8n web portals using Google Sheets as a simple user/session store and email delivery for onboarding. It supports register, username/password login, one-click magic link auth, and a cookie-based profile page.\n\n### How it works\n1. User registers with an email address via a form\n2. n8n generates a username, password, and a one-time auth token\n3. User data is stored in Google Sheets\n4. n8n sends an email containing credentials + a one-click login link (`/auth?token=...`)\n5. User can log in with username/password or click the magic link\n6. `/auth` validates the token, creates a session id (sid), sets it as an HttpOnly cookie, and invalidates the token\n7. `/profile` reads the sid cookie, validates the session in Google Sheets, and displays user info to confirm login\n\n### Setup\n- Configure Gmail credentials in the Send email node\n- Create a Google Sheet with columns: username, password, role, token, sid\n- Add your Google Sheets URL (replace YOUR_SPREADSHEET_ID)\n- Replace YOUR_DOMAIN with your n8n instance domain in all nodes\n- Ensure your n8n instance is served over HTTPS so Secure cookies work\n\n### Customization\n- Adjust token expiry (e.g., 10-15 minutes) and one-time use behavior\n- Change session cookie settings (name, Max-Age, SameSite)\n- Customize the registration email content\n- Replace the Profile HTML with your own portal UI\n- Add a /logout endpoint to revoke sessions\n- Add rate-limiting at reverse proxy (Nginx/Traefik/Cloudflare)\n"
      },
      "typeVersion": 1
    },
    {
      "parameters": {
        "operation": "verify",
        "email": "={{ $('Create password and token').item.json.email }}",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-billionverify.billionVerify",
      "typeVersion": 1,
      "position": [
        568,
        320
      ],
      "name": "Verify Email (BillionVerify)",
      "credentials": {
        "billionVerifyApi": {
          "id": "",
          "name": "BillionVerify account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-deliverable",
              "leftValue": "={{ $json.is_deliverable }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        748,
        320
      ],
      "name": "IF deliverable"
    }
  ],
  "connections": {
    "Auth": {
      "main": [
        [
          {
            "node": "If has token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Profile": {
      "main": [
        [
          {
            "node": "Check sid cookies header",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search sid": {
      "main": [
        [
          {
            "node": "If has data with sid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare sid": {
      "main": [
        [
          {
            "node": "Redirect to profile and set cookies",
            "type": "main",
            "index": 0
          },
          {
            "node": "Update sid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If has token": {
      "main": [
        [
          {
            "node": "Search by token",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Invalid token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare html": {
      "main": [
        [
          {
            "node": "Show profile page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form login": {
      "main": [
        [
          {
            "node": "Check username/password",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate token": {
      "main": [
        [
          {
            "node": "Add token to DB",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If token valid": {
      "main": [
        [
          {
            "node": "Prepare sid",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redirect to login form1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add token to DB": {
      "main": [
        [
          {
            "node": "Redirect to auth with new token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search by token": {
      "main": [
        [
          {
            "node": "If token valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If login success": {
      "main": [
        [
          {
            "node": "If has token in DB",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redirect to login form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form register": {
      "main": [
        [
          {
            "node": "Create password and token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If has token in DB": {
      "main": [
        [
          {
            "node": "Redirect to auth url",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generate token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If has data with sid": {
      "main": [
        [
          {
            "node": "Prepare html",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Redirect to login",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send username/password": {
      "main": [
        [
          {
            "node": "Notice register success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check username/password": {
      "main": [
        [
          {
            "node": "If login success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check sid cookies header": {
      "main": [
        [
          {
            "node": "Search sid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create password and token": {
      "main": [
        [
          {
            "node": "Append or update row in sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append or update row in sheet": {
      "main": [
        [
          {
            "node": "Verify Email (BillionVerify)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Email (BillionVerify)": {
      "main": [
        [
          {
            "node": "IF deliverable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF deliverable": {
      "main": [
        [
          {
            "node": "Send username/password",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

When to use this

  • Cleaning a list before a Authentica send or sync.
  • Protecting Authentica deliverability and sender reputation.
  • Keeping bounce rates low so your sending is never throttled.

FAQ

Why verify before sending in Authentica?

Verifying first keeps your bounce rate low, which protects your sender reputation and your results.

How do I import this workflow?

Download the JSON, then in n8n go to Workflows → Import from File (or paste it). Install the BillionVerify community node and add your API key credential.

What happens to risky or catch-all addresses?

They are routed to the false branch and excluded from the send. You decide whether to retry, review, or drop them.

Add verification to your workflow

Create a free account, grab your API key, and stop bounces before they happen.

Get started free