AutomationFlowsData & Sheets › Confirm Restaurant Orders by Phone with Supabase, Claude and Twilio

Confirm Restaurant Orders by Phone with Supabase, Claude and Twilio

ByAumadi @aumaditech on n8n.io

This workflow triggers from a Supabase Database Webhook when a new order is inserted, generates a short order-confirmation script with Anthropic Claude, calls the customer via Twilio, and streams live voice audio generated by ElevenLabs back to Twilio without storing an audio…

Webhook trigger★★★★★ complexity31 nodesHTTP RequestError Trigger
Data & Sheets Trigger: Webhook Nodes: 31 Complexity: ★★★★★ Added:

This workflow corresponds to n8n.io template #16247 — we link there as the canonical source.

This workflow follows the Error Trigger → HTTP Request 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 →

Download .json
{
  "id": "NoUZwycbjYjV7qbZ",
  "name": "Aumadi \u2014 Order Confirmation Call",
  "tags": [],
  "nodes": [
    {
      "id": "demo-video-note-call",
      "name": "\ud83c\udfa5 Demo Video",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2464,
        180
      ],
      "parameters": {
        "width": 470,
        "height": 160,
        "content": "## \ud83c\udfa5 Watch the demo\nSee this workflow run end-to-end:\nhttps://youtu.be/lpWBQzEWjUE\n\nBuilt by **Aumadi** \u2014 pj@aumadi.com"
      },
      "typeVersion": 1
    },
    {
      "id": "551b84d6-147d-4585-b7b8-0ab014765e16",
      "name": "\ud83d\udcd8 Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2464,
        464
      ],
      "parameters": {
        "width": 470,
        "height": 340,
        "content": "## \ud83d\udcde Order Confirmation Call (no storage)\n\n**Trigger:** a new order. Point the Supabase Database Webhook at the `notifications` table (auto-created on each order) \u2014 or the `orders` table. This node accepts both.\n\n**What it does:** fetches the order, Claude writes a confirmation script (saved to `order_call_logs`), then Twilio calls the customer. When the call is **answered**, Twilio asks n8n for the audio and **ElevenLabs generates the voice live** and streams it back \u2014 *no file is stored anywhere*.\n\n**Reuses (read-only):** notifications, orders, order_items, restaurants.\n**Adds (new only):** `order_call_logs` table. (No storage bucket needed.)"
      },
      "typeVersion": 1
    },
    {
      "id": "301a9f68-279d-4602-9235-c7d5170d87a9",
      "name": "\ud83d\udd27 Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2464,
        816
      ],
      "parameters": {
        "width": 470,
        "height": 380,
        "content": "## \ud83d\udd27 Setup (no hardcoded keys)\n\n**Credentials (create in n8n):**\n1. **Supabase API** \u2014 Host = your project URL, Service Role Secret = your key. *(All 6 Supabase nodes use this one credential.)*\n2. **Anthropic API** \u2014 Header Auth, name `x-api-key`\n3. **ElevenLabs** \u2014 Header Auth, name `xi-api-key`\n4. **Twilio** \u2014 Basic Auth (user = Account SID, pass = Auth Token)\n\n**Fill in:**\n- `YOUR_ELEVENLABS_VOICE_ID` (ElevenLabs node URL)\n- `YOUR_TWILIO_ACCOUNT_SID`, `YOUR_TWILIO_PHONE_NUMBER` (Twilio node)\n- `YOUR_N8N_TUNNEL_URL` (Twilio node \u2014 your public URL)\n\n**Supabase Database Webhook:** Database \u2192 Webhooks \u2192 table `notifications` (or `orders`), event `Insert`, POST \u2192 `https://YOUR_N8N_TUNNEL_URL/webhook/order-confirmation-call`\n\n\ud83d\udd12 The Supabase **service_role key is NOT in the workflow** \u2014 it lives only in the Supabase credential."
      },
      "typeVersion": 1
    },
    {
      "id": "5dc6ed49-9f60-464f-9f8d-d43c9ad705e2",
      "name": "\ud83d\udce5 New Notification",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1952,
        704
      ],
      "parameters": {
        "path": "order-confirmation-call",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "76dddd80-74a3-4ebf-8982-06010fd3bdd5",
      "name": "Parse Notification",
      "type": "n8n-nodes-base.code",
      "position": [
        -1728,
        704
      ],
      "parameters": {
        "jsCode": "// Read the Supabase Database Webhook payload.\n// Works for EITHER trigger table:\n//   notifications -> the order id is record.order_id, with record.type   (your flow)\n//   orders        -> the order id is record.id, no record.type\nconst input = $input.first().json;\nconst body = input.body || input;\nconst rec = body.record || body;\n\nconst orderId = rec.order_id || rec.id || null;\n\n// FILTER: only act on new orders. Notifications carry rec.type \u2014 we require\n// 'new_order' so future notification types never trigger a call. The orders\n// table has no 'type' column (rec.type is undefined) -> also allowed.\nconst proceed = !!orderId && (rec.type === 'new_order' || rec.type == null);\n\nreturn [{ json: {\n  eventType: body.type || '',\n  notifType: rec.type || '',\n  orderId,\n  proceed\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4fee916e-3ba0-48f5-a141-26f4e60850ae",
      "name": "Is a new order?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1504,
        704
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c_proceed",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.proceed }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "bf41ec95-bb75-433d-96da-fefa63825b9c",
      "name": "Skip (not new_order)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1280,
        912
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ skipped: true, reason: 'not a new_order (or missing order id)' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "c4b18300-9048-433a-9fcc-7030d30785a8",
      "name": "Claim Order (insert log)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -1280,
        672
      ],
      "parameters": {
        "url": "https://YOUR_PROJECT.supabase.co/rest/v1/order_call_logs",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ order_id: $json.orderId, status: 'processing' }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=representation"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "5a6fd39e-ee4c-4c28-a848-b36313909fcf",
      "name": "Skip (already called)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1056,
        912
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ skipped: true, reason: 'order already has a call log (duplicate notification)' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "13bbd0cc-cf27-4c5c-9804-dca4a3c9d883",
      "name": "Fetch Order Details",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -1056,
        672
      ],
      "parameters": {
        "url": "=https://YOUR_PROJECT.supabase.co/rest/v1/orders?id=eq.{{ $('Parse Notification').item.json.orderId }}&select=id,order_number,customer_name,customer_phone,total_amount,restaurants(name,branch_name),order_items(item_name,variant_name,quantity),customers(language)",
        "options": {
          "timeout": 20000
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "514a81ec-6c2e-4a90-8edb-8078d9a26665",
      "name": "Build Call Context",
      "type": "n8n-nodes-base.code",
      "position": [
        -832,
        672
      ],
      "parameters": {
        "jsCode": "const raw = $('Fetch Order Details').first().json;\nconst order = Array.isArray(raw) ? raw[0] : (raw.body && Array.isArray(raw.body) ? raw.body[0] : raw);\nif (!order || !order.id) { throw new Error('Order not found for orderId ' + $('Parse Notification').item.json.orderId); }\n\nconst rest = order.restaurants || {};\nconst placeName = [rest.name, rest.branch_name].filter(Boolean).join(' \u2014 ') || 'our restaurant';\n\n// Normalize phone to E.164. Assumes India (+91) for 10-digit numbers \u2014 change if needed.\nlet phone = String(order.customer_phone || '').replace(/[^\\d+]/g, '');\nif (phone && !phone.startsWith('+')) {\n  phone = phone.replace(/^0+/, '');\n  phone = (phone.length === 10) ? '+91' + phone : '+' + phone;\n}\n\nconst items = order.order_items || [];\nconst itemsSummary = items.map(it => {\n  const name = it.variant_name ? (it.item_name + ' (' + it.variant_name + ')') : it.item_name;\n  return it.quantity + 'x ' + name;\n}).join(', ') || 'your items';\n\nconst claimRow = $('Claim Order (insert log)').first().json;\nconst logId = Array.isArray(claimRow) ? claimRow[0].id : claimRow.id;\n\nreturn [{ json: {\n  logId,\n  orderId:       order.id,\n  orderNumber:   order.order_number || '',\n  customerName:  order.customer_name || 'there',\n  customerPhone: phone,\n  total:         order.total_amount,\n  placeName,\n  itemsSummary,\nlanguage: ((order.customers && order.customers.language) || 'English')\n\n} }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ffd05d05-dfd9-441f-86cb-85ca42253007",
      "name": "\ud83e\udde0 Claude: Write Script",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        -624,
        672
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={{ JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 400, system: 'You write a short, warm outbound phone-call script for a restaurant confirming a customer order was placed successfully. Output ONLY the words to be spoken, in ' + $json.language + '. Under 80 words. Greet the customer by name, name the restaurant, briefly list what they ordered, state the total, and confirm the order is placed successfully. No markdown, no quotes, no stage directions.', messages: [ { role: 'user', content: 'Customer name: ' + $json.customerName + '\\nRestaurant: ' + $json.placeName + '\\nOrder number: ' + $json.orderNumber + '\\nItems: ' + $json.itemsSummary + '\\nTotal amount: \u20b9' + $json.total + '\\n\\nWrite the confirmation call script.' } ] }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "content-type",
              "value": "application/json"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "910d85a8-3074-4927-895e-821c779bc07a",
      "name": "Extract Script",
      "type": "n8n-nodes-base.code",
      "position": [
        -400,
        672
      ],
      "parameters": {
        "jsCode": "// Pull plain text out of Claude's response; fall back to a basic script.\nconst r = $input.first().json;\nlet text = '';\ntry { if (Array.isArray(r.content)) text = r.content.map(c => c.text || '').join(' ').trim(); } catch (e) {}\nconst ctx = $('Build Call Context').first().json;\nconst script = text || ('Hello ' + ctx.customerName + '. This is ' + ctx.placeName + '. Your order ' + ctx.orderNumber + ' for ' + ctx.itemsSummary + ' totalling ' + ctx.total + ' rupees has been placed successfully. Thank you!');\nreturn [{ json: { script } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "262a0ec3-da06-461d-8be4-e26e2f7b43a2",
      "name": "Log: status = calling (save script)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        -176,
        672
      ],
      "parameters": {
        "url": "=https://YOUR_PROJECT.supabase.co/rest/v1/order_call_logs?id=eq.{{ $('Build Call Context').item.json.logId }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ status: 'calling', phone: $('Build Call Context').item.json.customerPhone, language: $('Build Call Context').item.json.language, script: $json.script }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=minimal"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d5ce474a-bab7-4f8c-a5a8-ba79186b49ee",
      "name": "\u260e\ufe0f Twilio: Place Call",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        48,
        672
      ],
      "parameters": {
        "url": "https://api.twilio.com/2010-04-01/Accounts/YOUR_TWILIO_ACCOUNT_SID/Calls.json",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "To",
              "value": "={{ $('Build Call Context').item.json.customerPhone }}"
            },
            {
              "name": "From",
              "value": "YOUR_TWILIO_PHONE_NUMBER"
            },
            {
              "name": "Twiml",
              "value": "=<Response><Play>https://YOUR_N8N_TUNNEL_URL/webhook/init-call-audio?log_id={{ $('Build Call Context').item.json.logId }}</Play></Response>"
            },
            {
              "name": "StatusCallback",
              "value": "=https://YOUR_N8N_TUNNEL_URL/webhook/twilio-status?log_id={{ $('Build Call Context').item.json.logId }}"
            },
            {
              "name": "StatusCallbackMethod",
              "value": "POST"
            },
            {
              "name": "StatusCallbackEvent",
              "value": "completed"
            }
          ]
        },
        "genericAuthType": "httpBasicAuth"
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a5cbed40-e3f5-4553-b840-5ee764394125",
      "name": "200 Call Initiated",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        256,
        672
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'call_initiated', orderId: $('Build Call Context').item.json.orderId }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "c41584cb-8b1c-411c-94c8-ceb5e297dfd9",
      "name": "\ud83d\udcd7 Main flow notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        352
      ],
      "parameters": {
        "width": 560,
        "height": 280,
        "content": "## \u260e\ufe0f Main flow (left \u2192 right)\n1. **Parse** the new order \u2192 check it has an order id.\n2. **Claim Order** \u2014 insert into `order_call_logs`. UNIQUE(order_id) makes a duplicate fail \u2192 **Skip (already called)**. Idempotency, no double calls.\n3. **Fetch** order + items + restaurant in one query.\n4. **Claude** writes the script \u2192 **save it** to the log row.\n5. **Twilio** calls. Its `<Play>` points back to the **\ud83d\udd0a audio webhook** below \u2014 no file is stored.\n6. Call result returns to the **\ud83d\udcde status callback** below (`?log_id=`)."
      },
      "typeVersion": 1
    },
    {
      "id": "c055b1ef-9e75-4e38-b55f-05b80d472bda",
      "name": "\ud83d\udd0a Twilio Fetches Audio",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1728,
        1200
      ],
      "parameters": {
        "path": "init-call-audio",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "2420af2b-28ee-4f4b-92c0-49c10fcb9eda",
      "name": "Get Saved Script",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1504,
        1200
      ],
      "parameters": {
        "url": "=https://YOUR_PROJECT.supabase.co/rest/v1/order_call_logs?id=eq.{{ $json.query.log_id }}&select=script,language",
        "options": {
          "timeout": 15000
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "dc77f89a-4c82-4b0f-ac7c-bc9f9a3137aa",
      "name": "Prepare Text",
      "type": "n8n-nodes-base.code",
      "position": [
        -1280,
        1200
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json;\nconst arr = Array.isArray(raw) ? raw : (raw.body && Array.isArray(raw.body) ? raw.body : [raw]);\nconst row = arr[0] || {};\nreturn [{ json: { script: row.script || 'Your order has been placed successfully. Thank you.' } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "9ce46d9e-4655-4f6a-84b0-b4e9d7b2eead",
      "name": "\ud83d\udde3\ufe0f ElevenLabs: Text \u2192 Audio",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1056,
        1200
      ],
      "parameters": {
        "url": "https://api.elevenlabs.io/v1/text-to-speech/pNInz6obpgDQGcFmaJgB",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "jsonBody": "={{ JSON.stringify({ text: $json.script, model_id: 'eleven_multilingual_v2', voice_settings: { stability: 0.5, similarity_boost: 0.75 } }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "audio/mpeg"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "6cb15323-252c-4048-b0be-6ccbc0fef284",
      "name": "Stream MP3 to Twilio",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -848,
        1200
      ],
      "parameters": {
        "options": {},
        "respondWith": "binary"
      },
      "typeVersion": 1.1
    },
    {
      "id": "8937ee2f-7f63-45fe-83b8-4e882e00c7f7",
      "name": "\ud83d\udcd2 Audio notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1728,
        960
      ],
      "parameters": {
        "width": 480,
        "height": 220,
        "content": "## \ud83d\udd0a Live audio (no storage)\nTwilio's `<Play>` calls this webhook to GET the audio. We read the saved script, generate the voice with **ElevenLabs on the fly**, and stream the MP3 straight back. Nothing is stored."
      },
      "typeVersion": 1
    },
    {
      "id": "a64976f9-f428-4efd-8a5a-9efbbff108fc",
      "name": "\ud83d\udcde Twilio Status Callback",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1728,
        1552
      ],
      "parameters": {
        "path": "twilio-status",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "3d02a107-0703-411a-900e-167705615984",
      "name": "Map Call Outcome",
      "type": "n8n-nodes-base.code",
      "position": [
        -1504,
        1552
      ],
      "parameters": {
        "jsCode": "// Twilio reports the call result here; ?log_id was passed on StatusCallback URL.\nconst inp = $input.first().json;\nconst q = inp.query || {};\nconst b = inp.body || {};\nconst map = { 'completed': 'completed', 'no-answer': 'no_answer', 'busy': 'failed', 'failed': 'failed', 'canceled': 'canceled' };\nconst status = b.CallStatus || 'unknown';\nreturn [{ json: {\n  logId: q.log_id,\n  callSid: b.CallSid || '',\n  finalStatus: map[status] || status,\n  pickedUp: status === 'completed'\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "e02d8784-a60c-49d7-a455-a6f1c5564d34",
      "name": "Update Call Result",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1280,
        1552
      ],
      "parameters": {
        "url": "=https://YOUR_PROJECT.supabase.co/rest/v1/order_call_logs?id=eq.{{ $json.logId }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ status: $json.finalStatus, picked_up: $json.pickedUp, call_sid: $json.callSid }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=minimal"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "93dea254-2a0c-4de9-9fb6-380df680f84e",
      "name": "200 OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1056,
        1552
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: true }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "5fc86332-d557-410c-9490-a4ee22e436b9",
      "name": "\ud83d\udcd9 Callback notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1728,
        1344
      ],
      "parameters": {
        "width": 470,
        "height": 200,
        "content": "## \ud83d\udcde Call-result flow\nTwilio calls this when the call ends (picked up / no answer / failed). We map it and save it back to the matching `order_call_logs` row by `log_id`."
      },
      "typeVersion": 1
    },
    {
      "id": "0eaa8be2-04da-419b-9459-8dfeeaf7b497",
      "name": "\u26a0\ufe0f On Error",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -1728,
        1920
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "883d3a87-1444-4755-893c-a9d01d945155",
      "name": "Log Error \u2192 Supabase",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1504,
        1920
      ],
      "parameters": {
        "url": "https://YOUR_PROJECT.supabase.co/rest/v1/order_call_logs",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ status: 'error', error_message: ($json.execution && $json.execution.error ? $json.execution.error.message : 'unknown error') + ' @ ' + ($json.execution ? $json.execution.lastNodeExecuted : '') }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Prefer",
              "value": "return=minimal"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "aaed1cf8-d76e-42c9-aea1-905f0e1a61c6",
      "name": "\ud83d\udcd5 Error notes",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1728,
        1680
      ],
      "parameters": {
        "width": 470,
        "height": 200,
        "content": "## \ud83d\udedf Error logging\nAny node failure is caught and written to `order_call_logs` (status = `error`). Set this workflow as its own **Error Workflow** in Settings."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "fd618953-53f2-4e62-b8ce-77273ec5a2e6",
  "connections": {
    "Prepare Text": {
      "main": [
        [
          {
            "node": "\ud83d\udde3\ufe0f ElevenLabs: Text \u2192 Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Script": {
      "main": [
        [
          {
            "node": "Log: status = calling (save script)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is a new order?": {
      "main": [
        [
          {
            "node": "Claim Order (insert log)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip (not new_order)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u26a0\ufe0f On Error": {
      "main": [
        [
          {
            "node": "Log Error \u2192 Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Saved Script": {
      "main": [
        [
          {
            "node": "Prepare Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map Call Outcome": {
      "main": [
        [
          {
            "node": "Update Call Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Call Context": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Claude: Write Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Notification": {
      "main": [
        [
          {
            "node": "Is a new order?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Call Result": {
      "main": [
        [
          {
            "node": "200 OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Order Details": {
      "main": [
        [
          {
            "node": "Build Call Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udce5 New Notification": {
      "main": [
        [
          {
            "node": "Parse Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claim Order (insert log)": {
      "main": [
        [
          {
            "node": "Fetch Order Details",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip (already called)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u260e\ufe0f Twilio: Place Call": {
      "main": [
        [
          {
            "node": "200 Call Initiated",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd0a Twilio Fetches Audio": {
      "main": [
        [
          {
            "node": "Get Saved Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde0 Claude: Write Script": {
      "main": [
        [
          {
            "node": "Extract Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcde Twilio Status Callback": {
      "main": [
        [
          {
            "node": "Map Call Outcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udde3\ufe0f ElevenLabs: Text \u2192 Audio": {
      "main": [
        [
          {
            "node": "Stream MP3 to Twilio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log: status = calling (save script)": {
      "main": [
        [
          {
            "node": "\u260e\ufe0f Twilio: Place Call",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow triggers from a Supabase Database Webhook when a new order is inserted, generates a short order-confirmation script with Anthropic Claude, calls the customer via Twilio, and streams live voice audio generated by ElevenLabs back to Twilio without storing an audio…

Source: https://n8n.io/workflows/16247/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

Workflow B — AI Listing Engine. Uses httpRequest, postgres, errorTrigger. Webhook trigger; 47 nodes.

HTTP Request, Postgres, Error Trigger
Data & Sheets

BP_check. Uses googleSheets, @n-octo-n/n8n-nodes-json-database, httpRequest, itemLists. Webhook trigger; 99 nodes.

Google Sheets, @N Octo N/N8N Nodes Json Database, HTTP Request +2
Data & Sheets

v25.1.3. Uses httpRequest, mySql, n8n-nodes-zohozeptomail. Webhook trigger; 98 nodes.

HTTP Request, MySQL, N8N Nodes Zohozeptomail
Data & Sheets

This solution enables you to manage all your Notion and Todoist tasks from different workspaces as well as your calendar events in a single place. This is 2 way sync with partial support for recurring

Redis, Notion, Todoist +6
Data & Sheets

Notion to Clockify Sync Template. Uses scheduleTrigger, clockify, compareDatasets, stopAndError. Webhook trigger; 68 nodes.

Clockify, Stop And Error, Notion +1