Skip to content

Instantly share code, notes, and snippets.

@Rinshin-Jalal
Last active May 8, 2026 10:24
Show Gist options
  • Select an option

  • Save Rinshin-Jalal/ca6efa770162759977aa7db9e326015a to your computer and use it in GitHub Desktop.

Select an option

Save Rinshin-Jalal/ca6efa770162759977aa7db9e326015a to your computer and use it in GitHub Desktop.
Vapi + n8n workflow: Outbound voice announcements with auto-end. Send a short voice message (loyalty reminder, appointment alert, billing notification) and auto-terminate the call. Uses dynamic Vapi controlUrl per call session. Built by Rinshin Jalal.

Vapi Outbound Announcement + Auto Hangup

Simple outbound announcement flow using Vapi.

Perfect for:

  • loyalty/reward reminders
  • appointment reminders
  • billing notifications
  • delivery alerts
  • pharmacy pickup notifications

The customer does not need to speak.

The flow:

  1. Start outbound call
  2. Play announcement
  3. Automatically hang up after playback finishes

Flow Overview

Create outbound call
    ↓
Get monitor.controlUrl from response
    ↓
Send "say" control message
    ↓
endCallAfterSpoken: true
    ↓
Call automatically ends

Step 1 — Create the Outbound Call

curl 'https://api.vapi.ai/call' \
  -H 'authorization: Bearer YOUR_API_KEY' \
  -H 'content-type: application/json' \
  --data-raw '{
    "assistantId": "YOUR_ASSISTANT_ID",
    "customer": {
      "number": "+12345678913"
    },
    "phoneNumberId": "YOUR_PHONE_NUMBER_ID"
  }'

Example Response

{
  "id": "7420f27a-30fd-4f49-a995-5549ae7cc00d",
  "assistantId": "5b0a4a08-133c-4146-9315-0984f8c6be80",
  "phoneNumberId": "42b4b25d-031e-4786-857f-63b346c9580f",
  "type": "outboundPhoneCall",
  "createdAt": "2024-09-10T11:14:12.339Z",
  "updatedAt": "2024-09-10T11:14:12.339Z",
  "customer": {
    "number": "+12345678913"
  },
  "status": "queued",
  "monitor": {
    "listenUrl": "wss://aws-us-west-2-production1-phone-call-websocket.vapi.ai/7420f27a-30fd-4f49-a995-5549ae7cc00d/transport",
    "controlUrl": "https://aws-us-west-2-production1-phone-call-websocket.vapi.ai/7420f27a-30fd-4f49-a995-5549ae7cc00d/control"
  }
}

Use:

monitor.controlUrl

for the next step.


Step 2 — Send the Announcement

curl -X POST 'CONTROL_URL_FROM_PREVIOUS_RESPONSE' \
  -H 'content-type: application/json' \
  --data-raw '{
    "type": "say",
    "content": "Hello, you currently have 100 unused points within our supermarket rewards program. Please redeem them soon. Thank you, goodbye.",
    "endCallAfterSpoken": true
  }'

What Happens

The call will:

  • play the announcement
  • automatically hang up afterward
  • never wait for customer input

No conversational flow required.


Why This Approach?

This is much cleaner than building a full conversational assistant when you only need:

  • one-way announcements
  • reminders
  • notifications

Benefits:

  • lower latency
  • lower cost
  • no transcription needed
  • no interruption handling
  • CRM-friendly
  • automation-friendly

Works well with:

  • n8n
  • Make.com
  • Zapier
  • GoHighLevel
  • HubSpot
  • custom CRMs

Production Tips

Add a Small Delay Before Control

Sometimes the call is not fully connected immediately after creation.

Adding a small delay improves reliability:

2–5 second delay

before sending the control request.


Dynamic CRM Variables

Example dynamic message:

Hello John, you currently have 143 unused points. Please redeem them before expiration. Thank you, goodbye.

Perfect for:

  • loyalty systems
  • clinics
  • insurance reminders
  • payment alerts

Example n8n Flow

Webhook / CRM Trigger
    ↓
Create Vapi Call
    ↓
Wait 3 Seconds
    ↓
POST monitor.controlUrl
    ↓
Auto Hangup

Important Notes

  • Use monitor.controlUrl dynamically from each call response
  • Do NOT hardcode websocket/control URLs
  • endCallAfterSpoken: true automatically terminates the call

Useful Docs


Built after watching multiple people accidentally turn a 20-second supermarket announcement into a distributed AI orchestration platform.

An industry tradition.

{
"name": "Vapi Outbound Announcement Flow",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "vapi-announcement",
"options": {}
},
"id": "da34565f-8461-4144-ac91-2b802a285831",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
496,
-32
],
"webhookId": "vapi-announcement"
},
{
"parameters": {
"method": "POST",
"url": "https://api.vapi.ai/call",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "authorization",
"value": "Bearer YOUR_VAPI_API_KEY"
},
{
"name": "content-type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"assistantId\":\"5b0a4a08-133c-4146-9315-0984f8c6be80\",\"customer\":{\"number\":\"{{$json.phone}}\"},\"phoneNumberId\":\"42b4b25d-031e-4786-857f-63b346c9580f\"}",
"options": {}
},
"id": "e93dcbe6-1a6e-40f7-b258-dcc752549997",
"name": "Create Call",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
720,
-32
]
},
{
"parameters": {
"amount": 3,
"unit": "seconds"
},
"id": "b45f7188-a7f2-4982-a2c9-59e23f7abace",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
944,
-32
],
"webhookId": "0a079011-eb45-472a-941b-d5e92dc9ed08"
},
{
"parameters": {
"values": {
"string": [
{
"name": "callId",
"value": "={{$node[\"Create Call\"].json[\"id\"]}}"
},
{
"name": "name",
"value": "={{$node[\"Webhook\"].json[\"name\"]}}"
},
{
"name": "controlUrl",
"value": "={{$node[\"Create Call\"].json[\"monitor\"][\"controlUrl\"]}}"
}
],
"number": [
{
"name": "points",
"value": "={{$node[\"Webhook\"].json[\"points\"]}}"
}
]
},
"options": {}
},
"id": "abea84c7-f8af-487a-992f-affec169bd06",
"name": "Prepare Data",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
1168,
-32
]
},
{
"parameters": {
"method": "POST",
"url": "={{$json.controlUrl}}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "content-type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"type\":\"say\",\"content\":\"Hello {{$json.name}}, you currently have {{$json.points}} unused points within our supermarket rewards program. Please redeem them soon. Thank you, goodbye.\",\"endCallAfterSpoken\":true}",
"options": {}
},
"id": "c2efcc52-8984-4d71-bb65-d86397eccd20",
"name": "Send Announcement",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
1392,
-32
]
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Create Call",
"type": "main",
"index": 0
}
]
]
},
"Create Call": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "Prepare Data",
"type": "main",
"index": 0
}
]
]
},
"Prepare Data": {
"main": [
[
{
"node": "Send Announcement",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "d680721f-4df6-44fe-9224-09c79c1283c4",
"meta": {
"instanceId": "8ec742a1f8e0781268e15b6e52fb83c50be6e4338b730b889bf1af4d81431605"
},
"id": "3WV8r1xfkKNb7aLx",
"tags": []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment