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 →
{
"name": "main-vapi-handler",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "vapi-server-url",
"responseMode": "responseNode"
},
"id": "webhook-vapi",
"name": "Webhook: VAPI Server URL",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
100,
300
],
"notes": "Receives all VAPI events. Verify x-vapi-secret header in production."
},
{
"parameters": {
"jsCode": "// Extract VAPI event type and validate\nconst body = $input.first().json.body;\nconst messageType = body.message?.type || 'unknown';\nconst call = body.message?.call || {};\nconst customerPhone = call.customer?.number || null;\nconst callId = call.id || null;\n\nreturn [{\n json: {\n messageType,\n customerPhone,\n callId,\n fullPayload: body,\n functionCall: body.message?.functionCall || null,\n transcript: body.message?.transcript || null,\n endOfCallReport: body.message?.endOfCallReport || null\n }\n}];"
},
"id": "parse-vapi-event",
"name": "Code: Parse VAPI Event",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
320,
300
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.messageType }}",
"rightValue": "assistant-request",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "Assistant Request"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.messageType }}",
"rightValue": "function-call",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "Function Call"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.messageType }}",
"rightValue": "end-of-call-report",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "End of Call"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.messageType }}",
"rightValue": "status-update",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "Status Update"
}
]
},
"mode": "rules",
"options": {
"fallbackOutputType": "extra"
}
},
"id": "switch-event-type",
"name": "Switch: VAPI Event Type",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [
540,
300
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_CUSTOMER_LOOKUP_ID }}"
},
"id": "ar-customer-lookup",
"name": "Assistant Request: Customer Lookup",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
760,
100
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_MEMORY_RETRIEVAL_ID }}"
},
"id": "ar-memory-retrieval",
"name": "Assistant Request: Memory Retrieval",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
980,
100
]
},
{
"parameters": {
"jsCode": "// Build dynamic VAPI assistant config with RAG context\nconst customer = $('Assistant Request: Customer Lookup').first().json.customer_profile;\nconst memory = $('Assistant Request: Memory Retrieval').first().json;\nconst isNew = $('Assistant Request: Customer Lookup').first().json.is_new_customer;\n\nconst firstName = customer?.name?.split(' ')[0] || 'love';\nconst ragContext = memory?.rag_context || 'No previous interaction history.';\nconst lastAppt = memory?.last_appointment;\n\nlet firstMessage = isNew\n ? `Hey love! Welcome to Sweet Hand Braids \u2014 I'm your AI stylist! Whether you're looking for a fresh new look or need to book an appointment, I've got you. What style are you thinking about?`\n : `Hey ${firstName}! Welcome back to Sweet Hand Braids! ${lastAppt ? `Last time you got ${lastAppt.service_type} and looked amazing!` : ''} What can I do for you today?`;\n\nconst systemPrompt = `You are SweetHand, the AI hair braiding stylist at Sweet Hand Braids salon.\n\nPERSONALITY:\n- Warm, confident, knowledgeable \u2014 like a best friend who is an expert braider\n- Use natural, friendly language. Keep responses SHORT (2-3 sentences) since this is voice\n- Be enthusiastic about hair \u2014 you LOVE what you do\n- Give honest advice with kindness\n\nCUSTOMER CONTEXT:\n${ragContext}\n\nCURRENT CUSTOMER: ${customer?.name || 'New Customer'}\nHair Profile: ${JSON.stringify(customer?.hair_profile || 'Unknown \u2014 ask about their hair')}\nVisit Count: ${customer?.lifetime_visits || 0}\n\nRULES:\n1. Keep responses to 2-3 sentences MAX \u2014 this is a phone call\n2. If they want to book, use the book_appointment function\n3. Always confirm details before booking: style, date, time\n4. If unsure about their hair type, ask them to describe it\n5. When discussing styles, mention estimated time and price range\n6. End interactions with a clear next step`;\n\nreturn [{\n json: {\n assistant: {\n firstMessage: firstMessage,\n model: {\n provider: 'openai',\n model: 'gpt-4o',\n temperature: 0.7,\n maxTokens: 250,\n systemMessage: systemPrompt,\n tools: [\n {\n type: 'function',\n function: {\n name: 'book_appointment',\n description: 'Book a hair braiding appointment for the customer',\n parameters: {\n type: 'object',\n properties: {\n service_type: { type: 'string', description: 'Type of braiding service (e.g., box_braids, goddess_locs, cornrows, passion_twists)' },\n preferred_date: { type: 'string', description: 'Preferred date in YYYY-MM-DD format' },\n preferred_time: { type: 'string', description: 'Preferred time in HH:MM format' },\n notes: { type: 'string', description: 'Style details: color, length, thickness preferences' }\n },\n required: ['service_type', 'preferred_date', 'preferred_time']\n }\n }\n },\n {\n type: 'function',\n function: {\n name: 'check_availability',\n description: 'Check available appointment slots for a given date',\n parameters: {\n type: 'object',\n properties: {\n start_date: { type: 'string', description: 'Start date YYYY-MM-DD' },\n end_date: { type: 'string', description: 'End date YYYY-MM-DD (optional, defaults to start_date)' },\n service_duration_minutes: { type: 'integer', description: 'Estimated service duration in minutes' }\n },\n required: ['start_date']\n }\n }\n },\n {\n type: 'function',\n function: {\n name: 'cancel_appointment',\n description: 'Cancel the customers most recent or specified appointment',\n parameters: {\n type: 'object',\n properties: {\n reason: { type: 'string', description: 'Reason for cancellation' }\n }\n }\n }\n },\n {\n type: 'function',\n function: {\n name: 'reschedule_appointment',\n description: 'Reschedule an existing appointment to a new date and time',\n parameters: {\n type: 'object',\n properties: {\n new_date: { type: 'string', description: 'New date YYYY-MM-DD' },\n new_time: { type: 'string', description: 'New time HH:MM' }\n },\n required: ['new_date', 'new_time']\n }\n }\n }\n ]\n },\n voice: {\n provider: '11labs',\n voiceId: 'EXAVITQu4vr4xnSDxMaL'\n },\n silenceTimeoutSeconds: 30,\n maxDurationSeconds: 600,\n endCallFunctionEnabled: true\n }\n }\n}];"
},
"id": "ar-build-config",
"name": "Code: Build Assistant Config",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1200,
100
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}"
},
"id": "ar-respond",
"name": "Respond: Assistant Config",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1420,
100
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.functionCall.name }}",
"rightValue": "book_appointment",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "Book"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.functionCall.name }}",
"rightValue": "check_availability",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "Check"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.functionCall.name }}",
"rightValue": "cancel_appointment",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "Cancel"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.functionCall.name }}",
"rightValue": "reschedule_appointment",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": "Reschedule"
}
]
},
"mode": "rules"
},
"id": "fc-switch-function",
"name": "Switch: Function Name",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [
760,
300
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_CUSTOMER_LOOKUP_ID }}"
},
"id": "fc-customer-lookup",
"name": "FC: Customer Lookup",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
980,
220
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_APPOINTMENT_MANAGER_ID }}"
},
"id": "fc-book",
"name": "FC: Book Appointment",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
1200,
220
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_APPOINTMENT_MANAGER_ID }}"
},
"id": "fc-check",
"name": "FC: Check Availability",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
1200,
340
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_APPOINTMENT_MANAGER_ID }}"
},
"id": "fc-cancel",
"name": "FC: Cancel Appointment",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
1200,
460
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_APPOINTMENT_MANAGER_ID }}"
},
"id": "fc-reschedule",
"name": "FC: Reschedule Appointment",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
1200,
580
]
},
{
"parameters": {
"jsCode": "// Format function result for VAPI\nconst result = $input.first().json;\nreturn [{ json: { result: JSON.stringify(result) } }];"
},
"id": "fc-format-result",
"name": "Code: Format Function Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1420,
380
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ result: $json.result }) }}"
},
"id": "fc-respond",
"name": "Respond: Function Result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1640,
380
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_CUSTOMER_LOOKUP_ID }}"
},
"id": "eoc-customer-lookup",
"name": "EOC: Customer Lookup",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
760,
560
]
},
{
"parameters": {
"workflowId": "={{ $vars.SUB_MEMORY_WRITER_ID }}"
},
"id": "eoc-memory-writer",
"name": "EOC: Store Transcript in Memory",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1,
"position": [
980,
560
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ status: 'ok' }) }}"
},
"id": "eoc-respond",
"name": "Respond: EOC Ack",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1200,
560
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ status: 'ok' }) }}"
},
"id": "status-respond",
"name": "Respond: Status Ack",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
760,
700
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ status: 'ok' }) }}"
},
"id": "fallback-respond",
"name": "Respond: Fallback Ack",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
760,
820
]
}
],
"connections": {
"Webhook: VAPI Server URL": {
"main": [
[
{
"node": "Code: Parse VAPI Event",
"type": "main",
"index": 0
}
]
]
},
"Code: Parse VAPI Event": {
"main": [
[
{
"node": "Switch: VAPI Event Type",
"type": "main",
"index": 0
}
]
]
},
"Switch: VAPI Event Type": {
"main": [
[
{
"node": "Assistant Request: Customer Lookup",
"type": "main",
"index": 0
}
],
[
{
"node": "Switch: Function Name",
"type": "main",
"index": 0
}
],
[
{
"node": "EOC: Customer Lookup",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond: Status Ack",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond: Fallback Ack",
"type": "main",
"index": 0
}
]
]
},
"Assistant Request: Customer Lookup": {
"main": [
[
{
"node": "Assistant Request: Memory Retrieval",
"type": "main",
"index": 0
}
]
]
},
"Assistant Request: Memory Retrieval": {
"main": [
[
{
"node": "Code: Build Assistant Config",
"type": "main",
"index": 0
}
]
]
},
"Code: Build Assistant Config": {
"main": [
[
{
"node": "Respond: Assistant Config",
"type": "main",
"index": 0
}
]
]
},
"Switch: Function Name": {
"main": [
[
{
"node": "FC: Customer Lookup",
"type": "main",
"index": 0
}
],
[
{
"node": "FC: Check Availability",
"type": "main",
"index": 0
}
],
[
{
"node": "FC: Customer Lookup",
"type": "main",
"index": 0
}
],
[
{
"node": "FC: Customer Lookup",
"type": "main",
"index": 0
}
]
]
},
"FC: Customer Lookup": {
"main": [
[
{
"node": "FC: Book Appointment",
"type": "main",
"index": 0
},
{
"node": "FC: Cancel Appointment",
"type": "main",
"index": 0
},
{
"node": "FC: Reschedule Appointment",
"type": "main",
"index": 0
}
]
]
},
"FC: Book Appointment": {
"main": [
[
{
"node": "Code: Format Function Result",
"type": "main",
"index": 0
}
]
]
},
"FC: Check Availability": {
"main": [
[
{
"node": "Code: Format Function Result",
"type": "main",
"index": 0
}
]
]
},
"FC: Cancel Appointment": {
"main": [
[
{
"node": "Code: Format Function Result",
"type": "main",
"index": 0
}
]
]
},
"FC: Reschedule Appointment": {
"main": [
[
{
"node": "Code: Format Function Result",
"type": "main",
"index": 0
}
]
]
},
"Code: Format Function Result": {
"main": [
[
{
"node": "Respond: Function Result",
"type": "main",
"index": 0
}
]
]
},
"EOC: Customer Lookup": {
"main": [
[
{
"node": "EOC: Store Transcript in Memory",
"type": "main",
"index": 0
}
]
]
},
"EOC: Store Transcript in Memory": {
"main": [
[
{
"node": "Respond: EOC Ack",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"errorWorkflow": "sub-error-handler"
},
"tags": [
{
"name": "sweethandbraids"
},
{
"name": "main-workflow"
},
{
"name": "vapi"
},
{
"name": "production"
}
]
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
main-vapi-handler. Webhook trigger; 20 nodes.
Source: https://github.com/rdmahpcengineer-gpu/sweethandbraidsMainProject/blob/9c327e7b2190848ae227fe277b0662173be78f38/n8n/main-vapi-handler.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.
A production-ready authentication workflow implementing secure user registration, login, token verification, and refresh token mechanisms. Perfect for adding authentication to any application without
Portfolio Orchestrator. Uses httpRequest. Webhook trigger; 59 nodes.
This n8n template demonstrates how a simple Multi-Layer Perceptron (MLP) neural network can predict housing prices. The prediction is based on four key features, processed through a three-layer model.
github code Try yourself
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.