This workflow follows the HTTP Request → Slack 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 →
{
"nodes": [
{
"id": "sticky-title",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1160,
-300
],
"parameters": {
"content": "## Missed Call Text Back\n- Every missed call gets a text back within seconds, so the caller is not left hanging\n- Calls that land late at night wait politely and get their text at 8am",
"height": 200,
"width": 480
}
},
{
"id": "sticky-quiet-hours",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-260,
-260
],
"parameters": {
"content": "## Quiet hours\n- A call between 8pm and 8am holds here and the text goes out at 8am",
"height": 160,
"width": 400,
"color": 4
}
},
{
"id": "webhook-missed-call",
"name": "Phone system reports a missed call",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-1100,
0
],
"parameters": {
"httpMethod": "POST",
"path": "demo/missed-call",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "code-read-call",
"name": "Read the call details",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-880,
0
],
"parameters": {
"jsCode": "// Missed-call events look different depending on the phone system.\n// Check the common shapes one after another: Quo/OpenPhone style,\n// Twilio style (a call that ended with CallStatus no-answer or busy),\n// CallRail style, and a plain flat shape. Whichever one arrives, we\n// pull out the same four facts: who called, their name if we have it,\n// which line they dialed, and when.\nconst raw = $json.body && typeof $json.body === 'object' ? $json.body : $json;\n\nconst pick = (...keys) => {\n for (const k of keys) {\n const v = raw[k];\n if (v !== undefined && v !== null && String(v).trim() !== '') return String(v).trim();\n }\n return '';\n};\n\nconst digitsWithPlus = (p) => {\n let out = String(p).replace(/[^\\d+]/g, '');\n if (out && !out.startsWith('+')) {\n out = out.length === 10 ? `+1${out}` : `+${out}`;\n }\n return out;\n};\n\nconst phone = digitsWithPlus(pick('caller', 'from', 'From', 'callerNumber', 'caller_number', 'customer_phone_number', 'phone'));\nconst callerName = pick('callerName', 'caller_name', 'CallerName', 'customer_name', 'name');\nconst lineCalled = digitsWithPlus(pick('called', 'to', 'To', 'calledNumber', 'called_number', 'tracking_phone_number', 'business_phone_number', 'line'));\nconst calledAt = pick('callTime', 'call_time', 'timestamp', 'Timestamp', 'createdAt', 'start_time', 'time') || new Date().toISOString();\n\nconst TZ = 'America/Denver';\nconst parsed = new Date(calledAt);\nconst calledAtLocal = (isNaN(parsed.getTime()) ? new Date() : parsed).toLocaleString('en-US', { timeZone: TZ });\n\n// Quiet hours: no texting between 8pm and 8am Denver time.\n// A late-night call gets its text held until 8am instead.\nconst nowLocal = new Date(new Date().toLocaleString('en-US', { timeZone: TZ }));\nconst hour = nowLocal.getHours();\nconst minute = nowLocal.getMinutes();\nconst canTextNow = hour >= 8 && hour < 20;\n\nlet waitMinutes = 0;\nif (!canTextNow) {\n waitMinutes = hour < 8 ? (8 - hour) * 60 - minute : (24 - hour + 8) * 60 - minute;\n}\nconst sendAt = new Date(Date.now() + waitMinutes * 60000).toISOString();\n\nreturn { phone, callerName, lineCalled, calledAt, calledAtLocal, canTextNow, sendAt };"
}
},
{
"id": "respond-ok",
"name": "Tell the phone system we got it",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
-660,
0
],
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"status\": \"received\"\n}",
"options": {}
}
},
{
"id": "ghl-save-caller",
"name": "Save the caller in GoHighLevel",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-440,
0
],
"parameters": {
"method": "POST",
"url": "https://services.leadconnectorhq.com/contacts/upsert",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ locationId: 'bsHomeSvcDemo01', phone: $('Read the call details').item.json.phone, name: $('Read the call details').item.json.callerName || undefined, tags: ['missed-call', 'needs-text-back'] }) }}",
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "if-quiet-hours",
"name": "Okay to text right now?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-220,
0
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "in-texting-window",
"leftValue": "={{ $('Read the call details').item.json.canTextNow }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "wait-until-8am",
"name": "Hold the text until 8am",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
0,
180
],
"parameters": {
"resume": "specificTime",
"dateTime": "={{ $('Read the call details').item.json.sendAt }}"
}
},
{
"id": "sms-text-back",
"name": "Text the caller back",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
220,
0
],
"parameters": {
"method": "POST",
"url": "https://services.leadconnectorhq.com/conversations/messages",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ type: 'SMS', contactId: $('Save the caller in GoHighLevel').item.json.contact?.id || $('Save the caller in GoHighLevel').item.json.id, message: 'Hey, this is Blue Summit Home Services. We just missed your call. What can we help with?' }) }}",
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "slack-missed-calls",
"name": "Post a note in #missed-calls",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.3,
"position": [
440,
0
],
"parameters": {
"resource": "message",
"operation": "post",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "missed-calls"
},
"text": "=Missed call from {{ $('Read the call details').item.json.phone }}{{ $('Read the call details').item.json.callerName ? ' (' + $('Read the call details').item.json.callerName + ')' : '' }} at {{ $('Read the call details').item.json.calledAtLocal }}. {{ $('Read the call details').item.json.canTextNow ? 'Text back sent right away.' : 'Call came in overnight, so the text was held and sent at 8am.' }}",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Phone system reports a missed call": {
"main": [
[
{
"node": "Read the call details",
"type": "main",
"index": 0
}
]
]
},
"Read the call details": {
"main": [
[
{
"node": "Tell the phone system we got it",
"type": "main",
"index": 0
}
]
]
},
"Tell the phone system we got it": {
"main": [
[
{
"node": "Save the caller in GoHighLevel",
"type": "main",
"index": 0
}
]
]
},
"Save the caller in GoHighLevel": {
"main": [
[
{
"node": "Okay to text right now?",
"type": "main",
"index": 0
}
]
]
},
"Okay to text right now?": {
"main": [
[
{
"node": "Text the caller back",
"type": "main",
"index": 0
}
],
[
{
"node": "Hold the text until 8am",
"type": "main",
"index": 0
}
]
]
},
"Hold the text until 8am": {
"main": [
[
{
"node": "Text the caller back",
"type": "main",
"index": 0
}
]
]
},
"Text the caller back": {
"main": [
[
{
"node": "Post a note in #missed-calls",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
httpHeaderAuthslackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Missed-Call-Text-Back. Uses httpRequest, slack. Webhook trigger; 10 nodes.
Source: https://github.com/mcruz1799/automation-examples/blob/main/claude-code/02-automation-file-factory/skills/n8n/section-templates/missed-call-text-back.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
HR teams, IT Operations, and System Administrators managing employee onboarding at scale. It’s perfect if you use Odoo 18 to trigger account requests and need Redmine + GitLab accounts created instant
This workflow is a complete, production-ready solution for recovering abandoned carts in Shopify stores using a multi-channel, multi-touch approach. It automates personalized follow-ups via Email, SMS
Backbrief: transcripts (Zoom webhook -> Slack + vault). Uses httpRequest, slack. Webhook trigger; 52 nodes.
This workflow automates end-to-end research analysis by coordinating multiple AI models—including NVIDIA NIM (Llama), OpenAI GPT-4, and Claude to analyze uploaded documents, extract insights, and gene
Are you tired of the repetitive dance between git push, creating a pull request in GitHub, updating the corresponding task in JIRA, and then manually notifying your team in Slack, or Notion?