Sharing a workflow I built for service-based businesses. Missed calls are expensive when callers just move on to the next result.
**What it does:**
A missed-call webhook fires when the phone system registers no answer. The flow checks business hours, then texts the caller within 60 seconds – during hours with a “calling you back” message, after hours with the next-morning callback promise. Every missed call logs to a Google Sheet for the follow-up list.
Compatible with any phone provider that fires webhooks on missed calls: Twilio, Telnyx, OpenPhone, JustCall, RingCentral, Grasshopper.
**Nodes (7 in this version):**
- Webhook trigger (path: `/missed-call`, POST)
- IF: Has caller number? (guard against empty payloads)
- Set: Normalize Fields (extract caller_number across provider formats)
- IF: Business hours? (compares current hour to configurable 8am-5pm window)
- Twilio SMS – business hours message
- Twilio SMS – after-hours message
- Google Sheets – log caller, timestamp, hours flag, SMS sent
**Setup time: about 20 minutes** (fill in Twilio credentials, sheet ID, business name, hours range)
```json
{
“name”: “Missed Call Auto-Response”,
“nodes”: [
{
“id”: “1”,
“name”: “Call Missed Webhook”,
“type”: “n8n-nodes-base.webhook”,
“typeVersion”: 1,
“position”: [240, 300],
“parameters”: {
“httpMethod”: “POST”,
“path”: “missed-call”,
“responseMode”: “onReceived”,
“responseData”: “noData”
}
},
{
“id”: “2”,
“name”: “Has Caller Number?”,
“type”: “n8n-nodes-base.if”,
“typeVersion”: 1,
“position”: [460, 300],
“parameters”: {
“conditions”: {
“string”: [
{
“value1”: “={{ $json.caller_number || $json.From || $json.CallerNumber || ‘’ }}”,
“operation”: “isNotEmpty”
}
]
}
}
},
{
“id”: “3”,
“name”: “Normalize Fields”,
“type”: “n8n-nodes-base.set”,
“typeVersion”: 1,
“position”: [680, 260],
“parameters”: {
“values”: {
“string”: [
{
“name”: “caller_number”,
“value”: “={{ $json.caller_number || $json.From || $json.CallerNumber }}”
},
{
“name”: “call_id”,
“value”: “={{ $json.call_id || $json.CallSid || $json.call_uuid || ‘’ }}”
}
]
},
“options”: {}
}
},
{
“id”: “4”,
“name”: “Is Business Hours?”,
“type”: “n8n-nodes-base.if”,
“typeVersion”: 1,
“position”: [900, 260],
“parameters”: {
“conditions”: {
“number”: [
{
“value1”: “={{ new Date().getHours() }}”,
“operation”: “largerEqual”,
“value2”: 8
},
{
“value1”: “={{ new Date().getHours() }}”,
“operation”: “smaller”,
“value2”: 17
}
]
}
}
},
{
“id”: “5”,
“name”: “SMS During Hours”,
“type”: “n8n-nodes-base.twilio”,
“typeVersion”: 1,
“position”: [1120, 180],
“parameters”: {
“from”: “YOUR_TWILIO_NUMBER”,
“to”: “={{ $(‘Normalize Fields’).item.json.caller_number }}”,
“message”: “Hey, we missed your call just now. Giving you a ring back in a few minutes. - YOUR_BUSINESS_NAME”
},
“credentials”: {
“twilioApi”: { “id”: “1”, “name”: “Twilio account” }
}
},
{
“id”: “6”,
“name”: “SMS After Hours”,
“type”: “n8n-nodes-base.twilio”,
“typeVersion”: 1,
“position”: [1120, 360],
“parameters”: {
“from”: “YOUR_TWILIO_NUMBER”,
“to”: “={{ $(‘Normalize Fields’).item.json.caller_number }}”,
“message”: “Thanks for calling YOUR_BUSINESS_NAME. We are closed right now but will reach you first thing tomorrow morning.”
},
“credentials”: {
“twilioApi”: { “id”: “1”, “name”: “Twilio account” }
}
},
{
“id”: “7”,
“name”: “Log to Sheet”,
“type”: “n8n-nodes-base.googleSheets”,
“typeVersion”: 2,
“position”: [1340, 260],
“parameters”: {
“operation”: “append”,
“documentId”: “YOUR_GOOGLE_SHEET_ID”,
“sheetName”: “Missed Calls”,
“columns”: {
“mappingMode”: “defineBelow”,
“value”: {
“Timestamp”: “={{ $now.toISO() }}”,
“Caller Number”: “={{ $(‘Normalize Fields’).item.json.caller_number }}”,
“Call ID”: “={{ $(‘Normalize Fields’).item.json.call_id }}”,
“Business Hours”: “={{ $(‘Is Business Hours?’).item.json[‘Is Business Hours?’] }}”,
“SMS Sent”: “=true”
}
}
},
“credentials”: {
“googleSheetsOAuth2Api”: { “id”: “2”, “name”: “Google Sheets account” }
}
}
],
“connections”: {
“Call Missed Webhook”: { “main”: [[{ “node”: “Has Caller Number?”, “type”: “main”, “index”: 0 }]] },
“Has Caller Number?”: {
“main”: [
[{ “node”: “Normalize Fields”, “type”: “main”, “index”: 0 }],
[]
]
},
“Normalize Fields”: { “main”: [[{ “node”: “Is Business Hours?”, “type”: “main”, “index”: 0 }]] },
“Is Business Hours?”: {
“main”: [
[{ “node”: “SMS During Hours”, “type”: “main”, “index”: 0 }],
[{ “node”: “SMS After Hours”, “type”: “main”, “index”: 0 }]
]
},
“SMS During Hours”: { “main”: [[{ “node”: “Log to Sheet”, “type”: “main”, “index”: 0 }]] },
“SMS After Hours”: { “main”: [[{ “node”: “Log to Sheet”, “type”: “main”, “index”: 0 }]] }
},
“settings”: {
“executionOrder”: “v1”
}
}
```
**To adapt for other providers:** swap the Twilio node for the SMS provider of your choice (MessageBird, Vonage, AWS SNS) and adjust the payload field normalization in the Normalize Fields node. The webhook payload fields differ by provider – the normalization step handles `caller_number`, `From`, and `CallerNumber` which covers most of them.
If you want the full version with all 11 nodes, setup instructions for each phone provider’s webhook format, and a callback-list tracker tab pre-formatted, it is packaged up at: Whop: Start a Business, Learn a New Skill, & Build a Network | Whop
Happy to answer questions here about adapting the flow.