How to Build a SaaS-Enabled SMS Platform with n8n Voice Agent Integration (Free Template)

How to Build a SaaS-Enabled SMS Platform with n8n Voice Agent Integration (Free Template)

You need an SMS platform that scales across multiple clients. You need voice AI integration. You need it to work with your existing automation stack. Building this from scratch takes months. This n8n workflow gives you the foundation in hours. You'll learn how to create a multi-tenant SMS system that connects voice agents, manages customer data, and automates communication workflows.

The Problem: SMS Platforms That Don't Scale for Agencies

Current challenges:

  • Manual SMS sending doesn't scale beyond 50-100 messages per day
  • No way to integrate voice AI agents with SMS follow-ups
  • Existing platforms charge per-user fees that kill agency margins
  • Can't customize workflows for different client needs
  • No central dashboard to manage multiple client accounts

Business impact:

  • Time spent: 15-20 hours/week on manual message coordination
  • Cost: $200-500/month per client on legacy SMS platforms
  • Lost revenue: Can't offer white-label SMS services to clients
  • Client churn: 23% higher when communication isn't automated

The real problem isn't sending SMS. It's building a system that lets you manage dozens of clients, integrate with voice AI, and customize workflows without rebuilding everything for each use case.

The Solution Overview

This n8n workflow creates a SaaS-enabled SMS platform with voice agent integration. The system uses webhook triggers to receive SMS requests, processes them through conditional logic, integrates with Twilio or similar SMS providers, and connects to voice AI platforms like Vapi or Retell. The architecture supports multi-tenant operations with client-specific configurations stored in Airtable or PostgreSQL. Each workflow execution handles authentication, message routing, delivery tracking, and callback management in under 3 seconds.

What You'll Build

Component Technology Purpose
API Gateway n8n Webhook Nodes Receive SMS requests from multiple sources
Authentication Function Nodes + JWT Validate client API keys and permissions
SMS Delivery HTTP Request Nodes Send messages via Twilio/Vonage/Plivo
Voice Integration Vapi/Retell API Trigger voice calls based on SMS responses
Client Database Airtable/Supabase Store client configs, message history, analytics
Message Queue n8n Queue Mode Handle high-volume sends without rate limits
Webhook Management n8n Webhook Reply Return delivery status to calling applications
Error Handling Error Workflow Retry failed sends, log issues, alert admins

Key capabilities:

  • Send SMS messages via API with client-specific credentials
  • Trigger voice AI calls when SMS receives specific keywords
  • Track delivery status and update client dashboards
  • Support multiple SMS providers (Twilio, Vonage, Plivo)
  • Rate limiting and queue management for high volumes
  • Multi-tenant architecture with isolated client data
  • Webhook callbacks for delivery receipts and replies

Prerequisites

Before starting, ensure you have:

  • n8n instance (cloud or self-hosted with queue mode enabled)
  • Twilio account with API credentials (or Vonage/Plivo alternative)
  • Vapi or Retell account for voice AI integration
  • Airtable or Supabase database for client configuration
  • Basic understanding of REST APIs and webhooks
  • SSL certificate for production webhook endpoints

Recommended setup:

  • n8n Cloud (easiest) or self-hosted with Redis for queue mode
  • Twilio trial account gives you $15 credit to test
  • Vapi free tier supports 10 minutes of voice calls
  • Airtable free tier handles up to 1,200 records

Step 1: Set Up the Webhook API Gateway

This phase creates the entry point for SMS requests. Your workflow needs to accept POST requests with client authentication and message parameters.

Configure the Webhook Trigger Node:

  1. Add a Webhook node as your workflow trigger
  2. Set HTTP Method to POST
  3. Set Path to /sms/send (or your preferred endpoint)
  4. Enable "Respond Immediately" to return status before processing
  5. Set Authentication to "Header Auth" with key name X-API-Key

Node configuration:

{
  "httpMethod": "POST",
  "path": "sms/send",
  "responseMode": "responseNode",
  "options": {
    "rawBody": false
  }
}

Expected request body:

{
  "client_id": "client_abc123",
  "to": "+15551234567",
  "message": "Your appointment is confirmed for tomorrow at 2pm",
  "callback_url": "https://client-app.com/sms-status",
  "metadata": {
    "campaign_id": "summer_promo",
    "user_id": "user_456"
  }
}

Why this works:
The webhook creates a REST API endpoint that any application can call. By using "Respond Immediately," you return a 200 OK status before processing the SMS. This prevents timeout errors when sending to large lists. The client_id in the request body lets you route messages to the correct Twilio account.

Step 2: Authenticate and Validate Client Requests

Every API request must verify the client has permission to send SMS. This prevents unauthorized usage and tracks which client sent which messages.

Configure Function Node for Authentication:

  1. Add a Function node after the webhook
  2. Name it "Validate API Key"
  3. Add code to check the API key against your client database
  4. Return error if authentication fails

Node configuration:

// Extract API key from headers
const apiKey = $('Webhook').item.headers['x-api-key'];
const clientId = $json.client_id;

// Query your database (example with Airtable)
// In production, this would be a separate HTTP Request node
const validClients = {
  'key_abc123': {
    client_id: 'client_abc123',
    twilio_sid: 'AC1234567890',
    twilio_token: 'your_token',
    rate_limit: 100
  }
};

// Validate
if (!validClients[apiKey]) {
  throw new Error('Invalid API key');
}

if (validClients[apiKey].client_id !== clientId) {
  throw new Error('Client ID mismatch');
}

// Return client config for next nodes
return {
  json: {
    ...validClients[apiKey],
    message_data: $json
  }
};

Why this approach:
Authentication happens before any external API calls. This saves costs by rejecting invalid requests early. Storing client credentials in your database (encrypted) lets you support multiple SMS providers per client. One client uses Twilio, another uses Vonage—same workflow handles both.

Variables to customize:

  • validClients: Replace with actual database lookup
  • rate_limit: Implement per-client sending limits
  • Error messages: Customize for your API documentation

Step 3: Route Messages to SMS Provider

Now send the actual SMS using the client's credentials. This node handles multiple providers and formats requests correctly.

Configure HTTP Request Node for Twilio:

  1. Add HTTP Request node
  2. Set Method to POST
  3. Set URL to https://api.twilio.com/2010-04-01/Accounts/{{$json.twilio_sid}}/Messages.json
  4. Set Authentication to "Generic Credential Type" with Basic Auth
  5. Add Body Parameters for To, From, and Body

Node configuration:

{
  "method": "POST",
  "url": "=https://api.twilio.com/2010-04-01/Accounts/{{$json.twilio_sid}}/Messages.json",
  "authentication": "genericCredentialType",
  "genericAuthType": "httpBasicAuth",
  "sendBody": true,
  "bodyParameters": {
    "parameters": [
      {
        "name": "To",
        "value": "={{$json.message_data.to}}"
      },
      {
        "name": "From",
        "value": "={{$json.twilio_phone}}"
      },
      {
        "name": "Body",
        "value": "={{$json.message_data.message}}"
      },
      {
        "name": "StatusCallback",
        "value": "={{$json.message_data.callback_url}}"
      }
    ]
  }
}

Why this works:
Twilio's API uses Basic Auth with Account SID as username and Auth Token as password. The StatusCallback parameter tells Twilio where to send delivery receipts. This lets your clients track message delivery in real-time. Using expressions like ={{$json.twilio_sid}} pulls data from previous nodes, making the workflow dynamic.

Common issues:

  • Phone numbers must be E.164 format (+15551234567) → Add validation in Function node
  • Twilio requires a verified "From" number → Store per-client in database
  • Rate limits vary by account type → Implement queue mode for high volumes

Step 4: Integrate Voice AI Triggers

When an SMS receives a reply with specific keywords, trigger a voice call. This creates powerful automation like "Reply CALL for immediate assistance."

Configure IF Node for Keyword Detection:

  1. Add IF node after SMS send
  2. Check if message contains trigger keywords
  3. Route to Vapi/Retell integration if match found

Configure HTTP Request Node for Vapi:

{
  "method": "POST",
  "url": "https://api.vapi.ai/call/phone",
  "authentication": "headerAuth",
  "sendHeaders": true,
  "headerParameters": {
    "parameters": [
      {
        "name": "Authorization",
        "value": "Bearer {{$json.vapi_api_key}}"
      }
    ]
  },
  "sendBody": true,
  "bodyParameters": {
    "parameters": [
      {
        "name": "phoneNumberId",
        "value": "={{$json.vapi_phone_id}}"
      },
      {
        "name": "customer",
        "value": {
          "number": "={{$json.message_data.to}}"
        }
      },
      {
        "name": "assistantId",
        "value": "={{$json.vapi_assistant_id}}"
      }
    ]
  }
}

Why this approach:
Voice AI platforms like Vapi and Retell use assistant IDs to define conversation behavior. By storing these per-client, you let each client customize their voice agent's personality and script. The phone number from the SMS becomes the outbound call target. This creates seamless SMS-to-voice handoffs.

Integration possibilities:

Trigger Voice Action Use Case
"CALL ME" keyword Immediate voice call Sales follow-up, support escalation
No SMS response in 24h Automated check-in call Appointment reminders, payment collection
Delivery failure Voice call with same message Backup communication channel
Specific time window Scheduled voice broadcast Event reminders, time-sensitive offers

Workflow Architecture Overview

This workflow consists of 12 nodes organized into 4 main sections:

  1. Request handling (Nodes 1-3): Webhook receives request, validates API key, extracts client config
  2. SMS delivery (Nodes 4-6): Routes to correct provider, sends message, captures response
  3. Voice integration (Nodes 7-9): Checks for triggers, initiates voice calls, logs activity
  4. Response management (Nodes 10-12): Updates database, sends callbacks, handles errors

Execution flow:

  • Trigger: Webhook POST request with client credentials
  • Average run time: 2.8 seconds for SMS-only, 4.2 seconds with voice trigger
  • Key dependencies: Twilio/SMS provider, Vapi/Retell, Airtable/Supabase

Critical nodes:

  • Function Node (Validate API Key): Prevents unauthorized access, loads client config
  • HTTP Request (Twilio): Handles actual SMS delivery with client-specific credentials
  • IF Node (Keyword Detection): Routes to voice integration based on message content
  • HTTP Request (Vapi): Triggers voice AI calls with context from SMS conversation

The complete n8n workflow JSON template is available at the bottom of this article.

Key Configuration Details

Twilio Integration

Required fields:

  • Account SID: Found in Twilio Console dashboard
  • Auth Token: Also in Console, treat like a password
  • Phone Number: Must be Twilio-verified, E.164 format
  • StatusCallback URL: Your n8n webhook for delivery receipts

Common issues:

  • Using trial accounts → Can only send to verified numbers
  • Missing country code → Always use +1 for US numbers
  • Webhook URL not HTTPS → Twilio requires SSL in production

Vapi Voice Integration

Required fields:

  • API Key: From Vapi dashboard under API Keys
  • Phone Number ID: Create in Vapi, links to your Twilio number
  • Assistant ID: Pre-configured voice agent with your script

Why this approach:
Vapi acts as a layer on top of Twilio for voice. You need both services configured. The Assistant ID defines what the AI says and how it responds. Create different assistants for different use cases (sales, support, reminders).

Variables to customize:

  • keyword_triggers: Array of words that trigger voice calls
  • retry_attempts: How many times to retry failed SMS sends
  • rate_limit_per_minute: Prevent provider throttling

Testing & Validation

Test each component:

  1. Webhook endpoint: Use Postman or curl to send test requests
curl -X POST https://your-n8n.app/webhook/sms/send \
  -H "X-API-Key: your_test_key" \
  -H "Content-Type: application/json" \
  -d '{"client_id":"test","to":"+15551234567","message":"Test message"}'
  1. Authentication: Try invalid API keys, expect 401 errors
  2. SMS delivery: Send to your own phone, verify receipt within 5 seconds
  3. Voice trigger: Send SMS with keyword, confirm voice call initiates
  4. Error handling: Send to invalid number, check error logs

Common troubleshooting:

Issue Cause Fix
Webhook returns 404 Workflow not activated Click "Activate" in n8n
SMS not delivered Invalid phone format Add validation for E.164 format
Voice call fails Wrong Assistant ID Verify ID in Vapi dashboard
Rate limit errors Too many requests Enable queue mode in n8n settings

Production Deployment Checklist

Area Requirement Why It Matters
Error Handling Retry logic with exponential backoff Prevents message loss during provider outages
Monitoring Webhook health checks every 5 min Detect failures within 5 minutes vs discovering hours later
Security Encrypt API keys in database Protects client credentials from data breaches
Scaling Enable n8n queue mode Handles 1000+ messages/min without blocking
Logging Store all requests in database Audit trail for compliance and debugging
Rate Limiting Per-client throttling Prevents abuse and manages provider costs

Production setup:

  • Use environment variables for sensitive credentials
  • Set up separate workflows for dev/staging/production
  • Implement webhook signature verification (HMAC)
  • Add Slack/email alerts for critical errors
  • Configure automatic retries for transient failures

Real-World Use Cases

Use Case 1: Marketing Agency SMS Campaigns

  • Industry: Digital marketing agencies
  • Scale: 50-200 clients, 10,000-50,000 messages/day
  • Modifications needed: Add campaign tracking, A/B testing nodes, unsubscribe handling

Use Case 2: Healthcare Appointment Reminders

  • Industry: Medical practices, telehealth platforms
  • Scale: 5-20 providers, 500-2,000 appointments/day
  • Modifications needed: HIPAA-compliant logging, two-way confirmation, voice call escalation for no-shows

Use Case 3: E-commerce Order Updates

  • Industry: Online retail, dropshipping
  • Scale: Single store to marketplace, 100-10,000 orders/day
  • Modifications needed: Integration with Shopify/WooCommerce webhooks, tracking number insertion, delivery status updates

Use Case 4: Real Estate Lead Follow-up

  • Industry: Real estate agencies, property management
  • Scale: 10-100 agents, 200-1,000 leads/week
  • Modifications needed: CRM integration (HubSpot/Salesforce), automated voice calls for hot leads, drip campaign logic

Customizing This Workflow

Alternative SMS Providers:

Instead of Twilio:

  • Vonage (Nexmo): Better international rates - requires 3 node changes (auth method, endpoint URL, response parsing)
  • Plivo: Lower cost for high volumes - swap HTTP Request node configuration, same workflow structure
  • Telnyx: Best for carrier-grade reliability - different API structure, add 2 nodes for number lookup

Workflow Extensions:

Add automated reporting:

  • Add Schedule node to run daily at 8am
  • Query message database for previous 24 hours
  • Generate CSV report with delivery rates, costs, errors
  • Send via email or post to Slack
  • Nodes needed: +6 (Schedule, Database Query, Function for CSV, Email)

Scale to handle more data:

  • Replace Airtable with PostgreSQL or Supabase for 100,000+ records
  • Implement Redis caching for client configs (reduces database queries by 90%)
  • Add batch processing for bulk sends (1,000 messages per execution)
  • Performance improvement: 50x faster for high-volume campaigns

Multi-channel communication:

Add This To Get This Complexity
Email integration SMS + Email campaigns Easy (3 nodes - SendGrid/Mailgun)
WhatsApp Business API Reach users on WhatsApp Medium (5 nodes - Twilio WhatsApp)
Push notifications Mobile app alerts Medium (4 nodes - Firebase/OneSignal)
Slack/Discord Team communication automation Easy (2 nodes - native integrations)

Advanced features:

  • Natural language processing for SMS replies (add OpenAI node)
  • Sentiment analysis to route angry customers to voice calls
  • Dynamic voice agent selection based on customer profile
  • A/B testing for message content and timing

Get Started Today

Ready to automate your SMS and voice communication?

  1. Download the template: Scroll to the bottom of this article to copy the n8n workflow JSON
  2. Import to n8n: Go to Workflows → Import from File, paste the JSON
  3. Configure your services: Add API credentials for Twilio, Vapi, and your database
  4. Test with sample data: Send test messages to your own phone before going live
  5. Deploy to production: Activate the workflow and start accepting API requests

This workflow gives you the foundation for a multi-tenant SMS platform. Customize it for your specific use case, add your branding, and start offering SMS automation to your clients.

Need help customizing this workflow for your specific needs? Schedule an intro call with Atherial.

Complete N8N Workflow Template

Copy the JSON below and import it into your N8N instance via Workflows → Import from File

{
  "name": "SMS Platform Voice Agent Integration",
  "nodes": [
    {
      "id": "1",
      "name": "Webhook - SMS Incoming",
      "type": "n8n-nodes-base.webhook",
      "position": [
        250,
        300
      ],
      "parameters": {
        "path": "sms-webhook",
        "httpMethod": "POST",
        "responseMode": "onReceived"
      },
      "typeVersion": 2
    },
    {
      "id": "2",
      "name": "Set - Parse SMS Data",
      "type": "n8n-nodes-base.set",
      "position": [
        450,
        300
      ],
      "parameters": {
        "mode": "enrichOrReplace",
        "assignments": {
          "assignments": [
            {
              "name": "phoneNumber",
              "type": "string",
              "value": "={{ $json.from }}"
            },
            {
              "name": "messageContent",
              "type": "string",
              "value": "={{ $json.body }}"
            },
            {
              "name": "tenantId",
              "type": "string",
              "value": "={{ $json.tenantId }}"
            },
            {
              "name": "smsId",
              "type": "string",
              "value": "={{ $json.messageId }}"
            },
            {
              "name": "timestamp",
              "type": "string",
              "value": "={{ now() }}"
            },
            {
              "name": "channel",
              "type": "string",
              "value": "sms"
            }
          ]
        }
      },
      "typeVersion": 3
    },
    {
      "id": "3",
      "name": "If - Validate Tenant",
      "type": "n8n-nodes-base.if",
      "position": [
        650,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "operator": {
              "name": "filter.operator.notEmpty",
              "type": "string",
              "value": "notEmpty"
            },
            "leftValue": "={{ $json.tenantId }}",
            "caseSensitive": true
          }
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4",
      "name": "HTTP Request - Get Tenant Config",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        850,
        200
      ],
      "parameters": {
        "url": "=https://{{ $env.TENANTDB_URL }}/tenant/{{ $json.tenantId }}",
        "method": "GET",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.TENANTDB_API_KEY }}"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "5",
      "name": "Set - Prepare Voice Agent Payload",
      "type": "n8n-nodes-base.set",
      "position": [
        1050,
        200
      ],
      "parameters": {
        "mode": "enrichOrReplace",
        "assignments": {
          "assignments": [
            {
              "name": "vapiPayload",
              "type": "object",
              "value": "={\n  \"phoneNumber\": $json.phoneNumber,\n  \"message\": $json.messageContent,\n  \"voiceId\": $json.voiceId,\n  \"systemPrompt\": $json.systemPrompt,\n  \"metadata\": {\n    \"tenantId\": $json.tenantId,\n    \"originalSmsId\": $json.smsId,\n    \"channel\": \"sms_to_voice\",\n    \"timestamp\": $json.timestamp\n  }\n}"
            }
          ]
        }
      },
      "typeVersion": 3
    },
    {
      "id": "6",
      "name": "HTTP Request - Vapi Voice Agent",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1250,
        200
      ],
      "parameters": {
        "url": "https://api.vapi.ai/call",
        "body": "={{ $json.vapiPayload }}",
        "method": "POST",
        "sendBody": true,
        "sendHeaders": true,
        "bodyContentType": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.VAPI_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "7",
      "name": "Set - Process Voice Response",
      "type": "n8n-nodes-base.set",
      "position": [
        1450,
        200
      ],
      "parameters": {
        "mode": "enrichOrReplace",
        "assignments": {
          "assignments": [
            {
              "name": "voiceCallId",
              "type": "string",
              "value": "={{ $json.callId }}"
            },
            {
              "name": "voiceStatus",
              "type": "string",
              "value": "={{ $json.status }}"
            },
            {
              "name": "voiceTranscript",
              "type": "string",
              "value": "={{ $json.transcript }}"
            }
          ]
        }
      },
      "typeVersion": 3
    },
    {
      "id": "8",
      "name": "Function - Store Conversation Log",
      "type": "n8n-nodes-base.function",
      "position": [
        1050,
        400
      ],
      "parameters": {
        "functionCode": "const conversationLog = {\n  id: $json.tenantId + '_' + $json.smsId + '_' + Date.now(),\n  tenantId: $json.tenantId,\n  phoneNumber: $json.phoneNumber,\n  originalSmsContent: $json.messageContent,\n  originalChannel: $json.channel,\n  voiceCallId: $json.voiceCallId || null,\n  voiceStatus: $json.voiceStatus || null,\n  voiceTranscript: $json.voiceTranscript || null,\n  conversationStartTime: $json.timestamp,\n  conversationEndTime: new Date().toISOString(),\n  status: 'initiated',\n  metadata: {\n    originalSmsId: $json.smsId,\n    apiVersion: 'v1'\n  }\n};\n$json.conversationLog = conversationLog;\nreturn $json;"
      },
      "typeVersion": 1
    },
    {
      "id": "9",
      "name": "HTTP Request - Store in Database",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1250,
        400
      ],
      "parameters": {
        "url": "=https://{{ $env.DB_API_URL }}/conversation-logs",
        "body": "={{ $json.conversationLog }}",
        "method": "POST",
        "sendBody": true,
        "sendHeaders": true,
        "bodyContentType": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.DB_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "10",
      "name": "HTTP Request - Sync to Back-Office Dashboard",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1450,
        400
      ],
      "parameters": {
        "url": "=https://{{ $env.BACKOFFICE_API_URL }}/dashboard/sync",
        "body": "={\n  \"tenantId\": $json.tenantId,\n  \"event\": \"voice_agent_call\",\n  \"data\": {\n    \"phoneNumber\": $json.phoneNumber,\n    \"smsContent\": $json.messageContent,\n    \"voiceCallId\": $json.voiceCallId,\n    \"voiceStatus\": $json.voiceStatus,\n    \"timestamp\": $json.timestamp,\n    \"channel\": \"sms_to_voice\"\n  }\n}",
        "method": "POST",
        "sendBody": true,
        "sendHeaders": true,
        "bodyContentType": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.BACKOFFICE_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "11",
      "name": "If - Check Voice Response",
      "type": "n8n-nodes-base.if",
      "position": [
        1650,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "operator": {
              "name": "filter.operator.notEmpty",
              "type": "string",
              "value": "notEmpty"
            },
            "leftValue": "={{ $json.voiceTranscript }}",
            "caseSensitive": true
          }
        }
      },
      "typeVersion": 2
    },
    {
      "id": "12",
      "name": "Function - Convert Voice Response to SMS",
      "type": "n8n-nodes-base.function",
      "position": [
        1850,
        200
      ],
      "parameters": {
        "functionCode": "const smsResponse = {\n  to: $json.phoneNumber,\n  body: $json.voiceTranscript.substring(0, 160),\n  tenantId: $json.tenantId,\n  originalVoiceCallId: $json.voiceCallId,\n  channel: 'voice_to_sms',\n  timestamp: new Date().toISOString(),\n  metadata: {\n    originalSmsId: $json.smsId,\n    voiceToSmsFallback: true\n  }\n};\n$json.smsResponse = smsResponse;\nreturn $json;"
      },
      "typeVersion": 1
    },
    {
      "id": "13",
      "name": "HTTP Request - Send SMS Response",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2050,
        200
      ],
      "parameters": {
        "url": "=https://{{ $env.SMS_PLATFORM_API_URL }}/send",
        "body": "={{ $json.smsResponse }}",
        "method": "POST",
        "sendBody": true,
        "sendHeaders": true,
        "bodyContentType": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.SMS_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "14",
      "name": "Webhook - Voice Agent Callback",
      "type": "n8n-nodes-base.webhook",
      "position": [
        250,
        600
      ],
      "parameters": {
        "path": "voice-callback",
        "httpMethod": "POST",
        "responseMode": "onReceived"
      },
      "typeVersion": 2
    },
    {
      "id": "15",
      "name": "Set - Parse Voice Callback",
      "type": "n8n-nodes-base.set",
      "position": [
        450,
        600
      ],
      "parameters": {
        "mode": "enrichOrReplace",
        "assignments": {
          "assignments": [
            {
              "name": "callbackVoiceCallId",
              "type": "string",
              "value": "={{ $json.callId }}"
            },
            {
              "name": "callbackStatus",
              "type": "string",
              "value": "={{ $json.status }}"
            },
            {
              "name": "callbackTranscript",
              "type": "string",
              "value": "={{ $json.transcript }}"
            },
            {
              "name": "callbackDuration",
              "type": "number",
              "value": "={{ $json.duration }}"
            },
            {
              "name": "callbackTenantId",
              "type": "string",
              "value": "={{ $json.metadata.tenantId }}"
            }
          ]
        }
      },
      "typeVersion": 3
    },
    {
      "id": "16",
      "name": "HTTP Request - Store Callback Data",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        650,
        600
      ],
      "parameters": {
        "url": "=https://{{ $env.DB_API_URL }}/conversation-logs/{{ $json.callbackVoiceCallId }}/update",
        "body": "={\n  \"voiceCallStatus\": $json.callbackStatus,\n  \"voiceTranscript\": $json.callbackTranscript,\n  \"duration\": $json.callbackDuration,\n  \"completedAt\": new Date().toISOString(),\n  \"status\": \"completed\"\n}",
        "method": "PUT",
        "sendBody": true,
        "sendHeaders": true,
        "bodyContentType": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.DB_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "17",
      "name": "HTTP Request - Update Dashboard with Completion",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        850,
        600
      ],
      "parameters": {
        "url": "=https://{{ $env.BACKOFFICE_API_URL }}/dashboard/update",
        "body": "={\n  \"tenantId\": $json.callbackTenantId,\n  \"event\": \"voice_call_completed\",\n  \"data\": {\n    \"voiceCallId\": $json.callbackVoiceCallId,\n    \"status\": $json.callbackStatus,\n    \"duration\": $json.callbackDuration,\n    \"transcript\": $json.callbackTranscript,\n    \"completedAt\": new Date().toISOString()\n  }\n}",
        "method": "POST",
        "sendBody": true,
        "sendHeaders": true,
        "bodyContentType": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.BACKOFFICE_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "18",
      "name": "Function - Generate Audit Log",
      "type": "n8n-nodes-base.function",
      "position": [
        1050,
        600
      ],
      "parameters": {
        "functionCode": "const auditLog = {\n  timestamp: new Date().toISOString(),\n  tenantId: $json.callbackTenantId,\n  eventType: 'voice_call_completed',\n  action: 'sms_to_voice_to_sms_conversion',\n  phoneNumber: $json.phoneNumber,\n  voiceCallId: $json.callbackVoiceCallId,\n  duration: $json.callbackDuration,\n  status: $json.callbackStatus,\n  metadata: {\n    originalSmsId: $json.smsId,\n    transcriptionAccuracy: 'auto',\n    apiVersion: 'v1'\n  }\n};\n$json.auditLog = auditLog;\nreturn $json;"
      },
      "typeVersion": 1
    },
    {
      "id": "19",
      "name": "HTTP Request - Archive Audit Log",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1250,
        600
      ],
      "parameters": {
        "url": "=https://{{ $env.AUDIT_LOG_URL }}/logs",
        "body": "={{ $json.auditLog }}",
        "method": "POST",
        "sendBody": true,
        "sendHeaders": true,
        "bodyContentType": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.AUDIT_LOG_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4
    },
    {
      "id": "20",
      "name": "Respond to Webhook - Success Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2250,
        200
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\n  \"success\": true,\n  \"message\": \"SMS-to-Voice conversion initiated\",\n  \"voiceCallId\": $json.voiceCallId,\n  \"status\": \"processing\"\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "21",
      "name": "Respond to Webhook - Callback Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1450,
        600
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\n  \"success\": true,\n  \"message\": \"Voice callback processed\",\n  \"callId\": $json.callbackVoiceCallId,\n  \"status\": \"logged\"\n}"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "timezone": "UTC",
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveExecutionProgress": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "connections": {
    "If - Validate Tenant": {
      "main": [
        [
          {
            "node": "HTTP Request - Get Tenant Config",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Set - Parse SMS Data": {
      "main": [
        [
          {
            "node": "If - Validate Tenant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - SMS Incoming": {
      "main": [
        [
          {
            "node": "Set - Parse SMS Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If - Check Voice Response": {
      "main": [
        [
          {
            "node": "Function - Convert Voice Response to SMS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond to Webhook - Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Parse Voice Callback": {
      "main": [
        [
          {
            "node": "HTTP Request - Store Callback Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Process Voice Response": {
      "main": [
        [
          {
            "node": "Function - Store Conversation Log",
            "type": "main",
            "index": 0
          },
          {
            "node": "If - Check Voice Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function - Generate Audit Log": {
      "main": [
        [
          {
            "node": "HTTP Request - Archive Audit Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Voice Agent Callback": {
      "main": [
        [
          {
            "node": "Set - Parse Voice Callback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Vapi Voice Agent": {
      "main": [
        [
          {
            "node": "Set - Process Voice Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Archive Audit Log": {
      "main": [
        [
          {
            "node": "Respond to Webhook - Callback Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Get Tenant Config": {
      "main": [
        [
          {
            "node": "Set - Prepare Voice Agent Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Send SMS Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook - Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Store in Database": {
      "main": [
        [
          {
            "node": "HTTP Request - Sync to Back-Office Dashboard",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function - Store Conversation Log": {
      "main": [
        [
          {
            "node": "HTTP Request - Store in Database",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Prepare Voice Agent Payload": {
      "main": [
        [
          {
            "node": "HTTP Request - Vapi Voice Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Store Callback Data": {
      "main": [
        [
          {
            "node": "HTTP Request - Update Dashboard with Completion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Function - Convert Voice Response to SMS": {
      "main": [
        [
          {
            "node": "HTTP Request - Send SMS Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Sync to Back-Office Dashboard": {
      "main": [
        [
          {
            "node": "Respond to Webhook - Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Update Dashboard with Completion": {
      "main": [
        [
          {
            "node": "Function - Generate Audit Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "description": "Comprehensive SMS-to-Voice and Voice-to-SMS SaaS integration workflow with multi-tenant support and enterprise back-office synchronization"
}