{
  "nodes": [
    {
      "id": "f66c5895-886f-4141-9aa6-7db364100ea2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2288,
        272
      ],
      "parameters": {
        "color": 4,
        "height": 432,
        "content": "## Make Phone Call\n**\u2699\ufe0f Setup:**\n**Create a free VAPI account**  [VAPI \u2013 $10 FREE credits](https://vapi.ai/?aff=n8ntemplate) \n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "710a797d-5faa-4389-b59d-d60606b141f0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        272
      ],
      "parameters": {
        "color": 5,
        "width": 288,
        "height": 432,
        "content": "## When a lead arrives\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b91f048a-7497-4899-8ed2-d6103dbc06a7",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        272
      ],
      "parameters": {
        "color": 6,
        "width": 576,
        "height": 432,
        "content": "## Santise the phone number\nPhone numbers are messy; For the call to work, we need to strip away all spaces, dashes, plus signs, and brackets so all we have left are numbers. Next, we're filtering out any numbers we've already called for a bit of damage control.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2697cbc3-bf00-4f32-85c4-b3629eb45d6a",
      "name": "Make phone call",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2352,
        416
      ],
      "parameters": {
        "url": "https://api.vapi.ai/call",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n  \"assistantId\": \"REPLACE WITH YOUR ASSISTANT ID\",\n  \"phoneNumberId\": \"REPLACE WITH YOUR PHONE NUMBER ID\",\n  \"customer\": {\n    \"number\": \"REPLACE WITH THE NUMBER YOU WANT TO CALL e.g. +1234567890\"\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "6125bb93-c3a7-4a34-9c62-372b77390aa0",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2544,
        272
      ],
      "parameters": {
        "color": 4,
        "width": 720,
        "height": 432,
        "content": "## Wait for the call to finish\nThis poll's Vapi until the call has finished. \n"
      },
      "typeVersion": 1
    },
    {
      "id": "18243315-1488-46f7-999d-11f42b89225f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1552,
        272
      ],
      "parameters": {
        "color": 6,
        "width": 720,
        "height": 432,
        "content": "## Check it's daytime (in their timezone)\nWe don't want to call leads at 2am, so this step checks the lead's timezone (based on their phone number) and only calls within the 8am-5pm window."
      },
      "typeVersion": 1
    },
    {
      "id": "39607441-5fbf-4ba5-b1d1-1d0d482da888",
      "name": "Get lead's timezone",
      "type": "n8n-nodes-base.code",
      "position": [
        1632,
        432
      ],
      "parameters": {
        "jsCode": "const rawPhone = $input.first().json['Phone Number'];\nconst phoneNumber = String(rawPhone || '');\nconst cleaned = phoneNumber.replace(/\\D/g, '');\n\n// Country code to timezone mapping\nconst timezoneMap = {\n  '44': 'Europe/London',\n  '1': 'America/New_York',\n  '61': 'Australia/Sydney',\n  '33': 'Europe/Paris',\n  '49': 'Europe/Berlin',\n  '34': 'Europe/Madrid',\n  '39': 'Europe/Rome',\n  '31': 'Europe/Amsterdam',\n  '353': 'Europe/Dublin',\n  '91': 'Asia/Kolkata',\n  '971': 'Asia/Dubai',\n  '65': 'Asia/Singapore',\n  '81': 'Asia/Tokyo',\n};\n\n// Find matching country code (check longer codes first)\nlet timezone = 'Europe/London';\nlet countryCode = '44';\n\nconst sortedCodes = Object.keys(timezoneMap).sort((a, b) => b.length - a.length);\nfor (const code of sortedCodes) {\n  if (cleaned.startsWith(code)) {\n    timezone = timezoneMap[code];\n    countryCode = code;\n    break;\n  }\n}\n\n// Get current time in the lead's timezone using Intl API\nconst now = new Date();\n\nconst formatter = new Intl.DateTimeFormat('en-GB', {\n  timeZone: timezone,\n  hour: 'numeric',\n  minute: 'numeric',\n  weekday: 'long',\n  hour12: false\n});\n\nconst parts = formatter.formatToParts(now);\nconst localHour = parseInt(parts.find(p => p.type === 'hour').value, 10);\nconst localMinute = parseInt(parts.find(p => p.type === 'minute').value, 10);\nconst weekdayName = parts.find(p => p.type === 'weekday').value;\n\nconst weekdayMap = {\n  'Monday': 1,\n  'Tuesday': 2,\n  'Wednesday': 3,\n  'Thursday': 4,\n  'Friday': 5,\n  'Saturday': 6,\n  'Sunday': 7\n};\nconst dayOfWeek = weekdayMap[weekdayName];\n\nreturn {\n  json: {\n    ...$input.first().json,\n    timezone,\n    countryCode,\n    localHour,\n    localMinute,\n    dayOfWeek,\n    localTimeFormatted: `${weekdayName} ${String(localHour).padStart(2, '0')}:${String(localMinute).padStart(2, '0')}`\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f5b6758d-aa2f-42ae-b683-06ca05b683cf",
      "name": "Cleanse phone numbers",
      "type": "n8n-nodes-base.code",
      "position": [
        1056,
        432
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst updatedItems = items.map((item) => {\n  item.json[\"Phone Number\"] = String(item.json[\"Phone Number\"]).replace(/\\D/g, \"\");\n  return item;\n});\nreturn updatedItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "7c4d97e1-149c-43c7-95ae-4af6c63b6948",
      "name": "New lead",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        736,
        432
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "61b44e37-6a88-4074-a913-e7b064925f4c",
      "name": "Is it between 8am-5pm for them?",
      "type": "n8n-nodes-base.if",
      "position": [
        1888,
        432
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ba0908e3-7234-4e40-b232-37111a59ff11",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.localHour }}",
              "rightValue": 8
            },
            {
              "id": "5085c2ab-cf18-41d9-989e-1f25579a89b8",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.localHour }}",
              "rightValue": 17
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "2b04d05d-959a-4f81-a6b4-19a3d117cbaf",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        2096,
        512
      ],
      "parameters": {
        "unit": "hours",
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "73cc76a9-1e02-4bd4-af05-6e03de30ac1f",
      "name": "Wait 1 minute",
      "type": "n8n-nodes-base.wait",
      "position": [
        2640,
        416
      ],
      "parameters": {
        "unit": "minutes",
        "amount": 1
      },
      "typeVersion": 1.1
    },
    {
      "id": "012d72d0-987c-455a-a7c7-b4d663805ec2",
      "name": "Call has finished",
      "type": "n8n-nodes-base.if",
      "position": [
        3024,
        416
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{$json.body.status}}",
              "rightValue": "ended"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "d5f23e74-e449-40d9-907e-be47350ec21a",
      "name": "Get Call Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2832,
        416
      ],
      "parameters": {
        "url": "={{`https://api.vapi.ai/call/${$node['Make phone call'].json.id}`}}\n",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {}
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "366e35ae-5e8a-410f-8968-ec43a3bbf807",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3280,
        272
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 432,
        "content": "## Update Google Sheet\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "df93507d-9c71-4d0d-a94b-8834d2311237",
      "name": "Filter numbers we've already called",
      "type": "n8n-nodes-base.filter",
      "position": [
        1360,
        432
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "81d18bd8-2b72-4605-9503-45c6cd0de270",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json['Phone Number'] }}",
              "rightValue": 0
            },
            {
              "id": "2f60a482-7bac-441a-b8de-35e62238d874",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Not Called"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e5016eb1-b343-4110-ac1d-b1c91f2ebe88",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 592,
        "height": 1040,
        "content": "## \ud83d\udcde Automated Outbound Lead Caller\n\nAutomatically call new leads from a Google Sheet using [VAPI](https://vapi.ai/?aff=n8ntemplate), with built-in timezone awareness to ensure you're only calling during business hours.\n\n### What it does\n1. **Triggers** when a new row is added to your Google Sheet\n2. **Cleans** the phone number (removes spaces, dashes, brackets)\n3. **Checks timezone** based on country code\u2014only calls between 8am-5pm local time\n4. **Makes the call** via VAPI using your configured assistant\n5. **Polls for completion** then updates the sheet with \"Called\" status, a call summary, sentiment, transcript & more.\n\n### \u2699\ufe0f Setup\n\n**1. VAPI Account**\n[Create a free account and get $10 in credits](https://vapi.ai/?aff=n8ntemplate\n) (~70 mins of calls)\n\n\n**2. Add Your Credentials**\n- VAPI API key \u2192 Bearer Auth credential\n- Google Sheets OAuth\n\n\n**3. Configure the \"Make phone call\" Node**\nIn the JSON body change:\n\n- `phoneNumberId`: Your Vapi phone number ID\n- `number`: Change to `{{ $json['Phone Number'] }}`\n- `assistantId`: Your Vapi assistant ID\n\n\n**4. Connect Your Google Sheet**\nColumns needed: `Phone Number`, `Name`, `Status`\nSet new leads to Status = \"Not called\"\n\n### \ud83d\udca1 Customisation Ideas\n- Swap the trigger for a webhook, form, or CRM\n- Adjust calling hours in the IF node (currently 8am-5pm)\n- Add more country codes to the timezone mapping\n- Send results to Slack, CRM, or email\n\n---\nBuilt by Marcus Taylor\n\ud83d\udcfa @intellagents | \ud83c\udf10 [voiceai.guide](https://voiceai.guide)"
      },
      "typeVersion": 1
    },
    {
      "id": "cffc3e9d-263d-49bd-81cc-dce738cf446d",
      "name": "Update Google Sheets with call information",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3488,
        400
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "gid=0"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    }
  ],
  "connections": {
    "Wait": {
      "main": [
        [
          {
            "node": "Get lead's timezone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New lead": {
      "main": [
        [
          {
            "node": "Cleanse phone numbers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 1 minute": {
      "main": [
        [
          {
            "node": "Get Call Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Call Status": {
      "main": [
        [
          {
            "node": "Call has finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Make phone call": {
      "main": [
        [
          {
            "node": "Wait 1 minute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call has finished": {
      "main": [
        [
          {
            "node": "Update Google Sheets with call information",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 1 minute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get lead's timezone": {
      "main": [
        [
          {
            "node": "Is it between 8am-5pm for them?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cleanse phone numbers": {
      "main": [
        [
          {
            "node": "Filter numbers we've already called",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is it between 8am-5pm for them?": {
      "main": [
        [
          {
            "node": "Make phone call",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter numbers we've already called": {
      "main": [
        [
          {
            "node": "Get lead's timezone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}