Root cause
Why Your Form Data Isn’t Reaching Google Sheets
You have two separate forms that don’t know about each other. Here is exactly what is happening.
Form A — Custom HTML form built into your page HTML. This is the form drivers see and fill in. When they click submit, it tries to submit but has nowhere to go — there’s no action URL connected to anything real.
Form B — CF7 form is a completely separate WordPress form with its own fields and its own shortcode. It lives inside your HTML widget but it has different field names than your custom form.
Result: Driver fills in Form A. Data goes nowhere. CF7 (Form B) sits there doing nothing because nobody filled it in.
One unified CF7 form that replaces your custom HTML form completely. The CF7 form will look identical to your current design — dark theme, red accents, all the same fields.
Google Apps Script webhook — completely free, no Make.com, no Zapier. A script that lives inside Google Sheets and receives the form data directly every time someone submits.
Alex calls the driver within 2 minutes. Jordan calls the shop once Alex confirms. Both agents go live before you give out the pitch.
The core issue in one line: Your custom HTML form fields (id="phone", id="vehicle" etc.) are not CF7 fields. CF7 only captures data from its own shortcode fields. You need to replace the custom form with a properly styled CF7 form so all data flows through CF7’s submission system.
Step 1 of 3
Replace Your Form With This CF7 Code
Two parts: the CF7 form template (goes in the CF7 editor) and the CSS (goes in Additional CSS). Together they produce a form that looks exactly like your current design.
Where to go: WordPress → Contact → Contact Forms → Edit your main form → replace the Form tab content with the code below.
CF7 Mail Tab Settings
Click the Mail tab in CF7 and set these fields so you get emailed on every submission.
partners@localtirequote.ca
🔔 [urgency] request — [your-name] in [city]
NEW DRIVER REQUEST — LocalTireQuote.ca
Name: [your-name]
Phone: [phone-number]
City: [city]
Postal Code: [postal-code]
Vehicle: [vehicle]
Tire Size: [tire-size]
Service: [service]
Urgency: [urgency]
Source Page: [ltq_source_page]
Submitted: [ltq_timestamp]
Styling — goes in Additional CSS
Make the CF7 Form Look Like Your Design
Appearance → Customize → Additional CSS → paste at the bottom.
/* ── LTQ CF7 Form Styles ── */
.ltq-form-inner { display:flex; flex-direction:column; gap:10px; width:100%; box-sizing:border-box; }
.ltq-form-label { font-size:0.6rem; font-weight:700; letter-spacing:2px; text-transform:uppercase; color:#E8170A; margin-bottom:6px; display:block; }
.ltq-field { display:flex; flex-direction:column; gap:4px; }
.ltq-field label { font-size:0.6rem; font-weight:700; letter-spacing:1px; text-transform:uppercase; color:#888; }
/* All inputs and selects */
.ltq-form-inner input[type="text"],
.ltq-form-inner input[type="tel"],
.ltq-form-inner select {
width:100% !important;
max-width:100% !important;
box-sizing:border-box !important;
background:#242424 !important;
border:1px solid #2a2a2a !important;
border-radius:5px !important;
padding:11px 13px !important;
color:#f0f0f0 !important;
font-family:'Barlow',sans-serif !important;
font-size:0.86rem !important;
outline:none !important;
-webkit-appearance:none !important;
appearance:none !important;
}
.ltq-form-inner input:focus,
.ltq-form-inner select:focus {
border-color:#E8170A !important;
box-shadow:0 0 0 3px rgba(232,23,10,0.08) !important;
}
.ltq-form-inner input::placeholder { color:#555 !important; }
.ltq-form-inner select option { background:#181818; }
/* Urgency radio buttons */
.ltq-urgency-row { display:flex; gap:8px; margin-bottom:4px; }
.ltq-urgency-btn { flex:1; }
.ltq-urgency-btn .wpcf7-radio { display:flex; }
.ltq-urgency-btn label {
flex:1;
padding:10px 8px !important;
border-radius:4px !important;
font-size:0.74rem !important;
font-weight:700 !important;
text-align:center !important;
cursor:pointer !important;
border:1px solid #2a2a2a !important;
background:#242424 !important;
color:#888 !important;
transition:all 0.2s !important;
display:flex !important;
align-items:center !important;
justify-content:center !important;
}
.ltq-urgency-btn input[type="radio"] { display:none !important; }
.ltq-urgency-btn input[type="radio"]:checked + span,
.ltq-urgency-btn label:has(input:checked) {
background:#E8170A !important;
border-color:#E8170A !important;
color:#fff !important;
}
/* Submit button */
.ltq-submit, input.ltq-submit {
width:100% !important;
background:#E8170A !important;
color:#fff !important;
border:none !important;
border-radius:5px !important;
padding:14px !important;
font-family:'Bebas Neue',sans-serif !important;
font-size:1.1rem !important;
letter-spacing:2px !important;
cursor:pointer !important;
margin-top:4px !important;
transition:background 0.2s !important;
}
.ltq-submit:hover { background:#b81008 !important; }
/* Note text */
.ltq-note { font-size:0.62rem; color:#555; text-align:center; margin-top:6px; line-height:1.5; }
.ltq-note a { color:#E8170A; text-decoration:none; }
/* CF7 validation messages */
.wpcf7-not-valid-tip { font-size:0.7rem; color:#E8170A !important; }
.wpcf7-response-output { font-size:0.78rem !important; border-radius:5px !important; padding:10px 14px !important; }
/* Fix CF7 overflow on mobile */
.wpcf7 { width:100% !important; max-width:100% !important; overflow:hidden !important; }
.wpcf7-form { width:100% !important; box-sizing:border-box !important; }
Footer JS — WPCode
Auto-select city from postal code
WPCode → Add Snippet → HTML → Footer location → paste this.
<script>
document.addEventListener('DOMContentLoaded', function() {
// Phone formatter — CF7 field
var ph = document.getElementById('ltq-phone');
if(ph) {
ph.addEventListener('input', function() {
var d = this.value.replace(/[^0-9]/g, '').slice(0, 10);
if(d.length >= 7) this.value = '(' + d.slice(0,3) + ') ' + d.slice(3,6) + '-' + d.slice(6);
else if(d.length >= 4) this.value = '(' + d.slice(0,3) + ') ' + d.slice(3);
else this.value = d;
});
}
// Postal code → city auto-select
var postal = document.getElementById('ltq-postal');
var city = document.getElementById('ltq-city');
if(postal && city) {
postal.addEventListener('blur', function() {
var p = this.value.toUpperCase().replace(/\s/g, '');
var map = [
['M1', 'Scarborough'],
['M', 'Toronto'],
['L4', 'Mississauga'], ['L5', 'Mississauga'],
['L6', 'Brampton'], ['L7', 'Brampton'],
['T3N','Calgary NW'],['T3K','Calgary NW'],['T3L','Calgary NW'],
['T2K','Calgary NW'],['T2L','Calgary NW'],['T2M','Calgary NW'],
['T2W','Calgary SW'],['T3H','Calgary SW'],['T3E','Calgary SW'],
['T2V','Calgary SW'],['T2T','Calgary SW'],['T2S','Calgary SW'],
['T2Z','Calgary SE'],['T2X','Calgary SE'],['T2Y','Calgary SE'],
['T3M','Calgary SE'],['T3J','Calgary SE'],['T2J','Calgary SE'],
['T3P','Calgary NE'],['T1Y','Calgary NE'],['T2A','Calgary NE'],
['T2B','Calgary NE'],['T2E','Calgary NE'],
['T2P','Calgary Downtown'],['T2R','Calgary Downtown'],
['T2G','Calgary Downtown'],['T2C','Calgary Downtown'],
];
for(var i=0; i<map.length; i++) {
if(p.startsWith(map[i][0])) {
city.value = map[i][1];
break;
}
}
});
}
});
</script>
Step 2 of 3 — 100% Free
Google Apps Script Webhook
This replaces Make.com and Zapier entirely. A script that lives inside your Google Sheet and receives CF7 data directly. Free forever, no account limits, no expiry.
Why Google Apps Script instead of Make.com? Make.com free tier limits you to 1,000 operations per month. Google Apps Script runs inside Google itself — unlimited free executions, no third-party account, no expiry, no rate limits for your volume.
Open your Google Sheet and create the Apps Script
Open LocalTireQuote — Master in Google Sheets
→ Click Extensions → Apps Script
→ Delete everything in the editor
→ Paste the entire script below
→ Click Save (disk icon)
// LocalTireQuote — Google Apps Script Webhook
// Receives CF7 form submissions and writes to Google Sheets + triggers VAPI
// Deploy as web app — anyone can access, execute as me
var SHEET_NAME = 'Driver Requests';
var NOTIFY_EMAIL = 'partners@localtirequote.ca';
var VAPI_API_KEY = 'YOUR_VAPI_API_KEY_HERE';
var VAPI_ASSISTANT_ID = 'YOUR_VAPI_ASSISTANT_ID_HERE';
var VAPI_PHONE_NUMBER_ID = 'YOUR_VAPI_PHONE_NUMBER_ID_HERE';
// This runs when the form POSTs to this script URL
function doPost(e) {
try {
var data = JSON.parse(e.postData.contents);
writeToSheet(data);
sendEmailAlert(data);
triggerVapiCall(data);
return ContentService
.createTextOutput(JSON.stringify({status:'ok'}))
.setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService
.createTextOutput(JSON.stringify({status:'error', message:err.message}))
.setMimeType(ContentService.MimeType.JSON);
}
}
// Also handle GET requests (for testing in browser)
function doGet(e) {
return ContentService
.createTextOutput('LocalTireQuote webhook is live ✓')
.setMimeType(ContentService.MimeType.TEXT);
}
function writeToSheet(data) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(SHEET_NAME);
if(!sheet) sheet = ss.insertSheet(SHEET_NAME);
// Auto-create headers if sheet is empty
if(sheet.getLastRow() === 0) {
sheet.appendRow([
'Timestamp','Full Name','Phone','Email',
'Postal Code','City','Vehicle','Tire Size',
'Service','Urgency','Status','Source Page',
'Confirmed Vehicle','Confirmed Service',
'Call Outcome','Shop Assigned','Notes'
]);
}
// Append the new row
sheet.appendRow([
new Date().toLocaleString('en-CA', {timeZone:'America/Toronto'}),
data['your-name'] || data['full-name'] || '',
data['phone-number'] || data['tel'] || '',
data['your-email'] || data['email'] || '',
data['postal-code'] || '',
data['city'] || '',
data['vehicle'] || '',
data['tire-size'] || '',
data['service'] || '',
data['urgency'] || '',
'New',
data['ltq_source_page'] || '',
'','','','',''
]);
}
function sendEmailAlert(data) {
var name = data['your-name'] || 'Unknown';
var phone = data['phone-number'] || 'Not provided';
var city = data['city'] || 'Unknown';
var service = data['service'] || 'Unknown';
var urgency = data['urgency'] || 'Unknown';
var subject = '🔔 ' + urgency + ' request — ' + name + ' in ' + city;
var body = [
'NEW DRIVER REQUEST — LocalTireQuote.ca\n',
'Name: ' + name,
'Phone: ' + phone,
'City: ' + city,
'Vehicle: ' + (data['vehicle'] || 'Not provided'),
'Service: ' + service,
'Urgency: ' + urgency,
'Postal: ' + (data['postal-code'] || ''),
'\nAlex (VAPI) will call the driver within 2 minutes.',
].join('\n');
MailApp.sendEmail(NOTIFY_EMAIL, subject, body);
}
function triggerVapiCall(data) {
var phone = data['phone-number'] || '';
if(!phone) return; // skip if no phone
// Format phone to E.164 (+1XXXXXXXXXX)
var digits = phone.replace(/\D/g, '');
if(digits.length === 10) digits = '1' + digits;
var e164 = '+' + digits;
var payload = {
assistantId: VAPI_ASSISTANT_ID,
phoneNumberId: VAPI_PHONE_NUMBER_ID,
customer: {
number: e164,
name: data['your-name'] || 'Driver'
},
assistantOverrides: {
variableValues: {
driver_name: data['your-name'] || 'there',
vehicle: data['vehicle'] || 'your vehicle',
service: data['service'] || 'tire service',
city: data['city'] || 'your area',
urgency: data['urgency'] || 'Today',
tire_size: data['tire-size'] || 'not provided',
postal_code: data['postal-code'] || ''
}
}
};
var options = {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + VAPI_API_KEY },
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
var resp = UrlFetchApp.fetch('https://api.vapi.ai/call/phone', options);
Logger.log('VAPI response: ' + resp.getContentText());
}
// ── CF7 WEBHOOK HANDLER ──
// CF7 sends data as form-encoded, not JSON.
// This handles both formats automatically.
function parseBody(e) {
if(e.postData.type === 'application/json') {
return JSON.parse(e.postData.contents);
}
// form-urlencoded
var params = e.parameter;
return params;
}
Deploy the script as a Web App
In Apps Script → click Deploy → New Deployment
→ Select type: Web App
→ Description: LTQ Webhook v1
→ Execute as: Me
→ Who has access: Anyone
→ Click Deploy
→ Authorise the permissions when prompted (click Advanced → Go to LTQ Webhook → Allow)
→ Copy the Web App URL — it looks like:
https://script.google.com/macros/s/AKfyc.../exec
Save that URL. This is your webhook endpoint. You will paste it into CF7 and into your site’s custom form action.
Add your VAPI credentials to the script
At the top of the script, replace the three placeholder values:
VAPI_API_KEY → from VAPI Dashboard → Account → API Keys
VAPI_ASSISTANT_ID → from VAPI → Assistants → your assistant → copy ID from URL
VAPI_PHONE_NUMBER_ID → from VAPI → Phone Numbers → click your number → copy ID
Then click Save and Deploy → Manage Deployments → Edit → New Version → Deploy to update.
Connect CF7 to your Apps Script webhook
Install the free plugin: Contact Form 7 – To – Webhook
→ WordPress → Plugins → Add New → search it → Install → Activate
→ Contact → Contact Forms → Edit your form → Webhooks tab
→ Paste your Apps Script URL
→ Method: POST
→ Save
Test it — submit the form and check the Sheet
Go to your site → fill in the form → submit.
Check Google Sheets — a new row should appear within 10 seconds.
Check your email — you should receive the alert.
Check your phone — VAPI should call within 2 minutes.
Agent 1 — Driver Confirmation
Alex calls every driver within 2 minutes of form submission. Confirms their details, sets the 15-minute shop callback expectation, and ends warmly. Alex never sells. Alex solves.
Voice11Labs — Sarah (warm, clear)
Avg call length90–120 seconds
Triggers whenForm submitted → Apps Script fires
Voice11Labs — Ash (professional)
Avg call length45–60 seconds
Triggers whenAlex call outcome = confirmed
You are Alex, a friendly and efficient booking assistant for LocalTireQuote.ca — Canada's tire service matching platform based in Toronto.
Your ONLY job is to call a driver who just submitted a tire service request, confirm their details in under 2 minutes, and let them know a local shop will call them shortly.
You never sell. You never upsell. You solve their problem fast.
## YOUR IDENTITY
- Name: Alex
- Company: LocalTireQuote.ca
- Tone: Warm, calm, human, and quick. Like a helpful friend who works in customer service.
- Never robotic. Never read from a script — have a conversation.
## WHAT YOU KNOW ABOUT THIS DRIVER
You will receive these variables before the call:
- {{driver_name}} — their name
- {{vehicle}} — their vehicle (year make model)
- {{service}} — the service they need
- {{city}} — their city
- {{urgency}} — Emergency, Today, or Scheduled
- {{tire_size}} — tire size if provided
- {{postal_code}} — their postal code
## CALL STRUCTURE — FOLLOW THIS EXACTLY
### OPENING — First 15 seconds
Say: "Hi, is this {{driver_name}}? Great — this is Alex from LocalTireQuote.ca. You just submitted a tire request and I am calling to quickly confirm your details. Takes about 90 seconds — is now okay?"
If no or bad time: "No problem at all — we will follow up shortly. Have a great day." Then end the call.
### STEP 1 — CONFIRM VEHICLE
"I have got {{vehicle}} on file — is that right?"
- Yes → continue
- No → "What is the correct vehicle?" → note it
### STEP 2 — CONFIRM SERVICE
"And you need {{service}} — still correct?"
- Yes → continue
- Changed → "Got it — what service do you need?" → note it
### STEP 3 — URGENCY HANDLING
IF urgency is EMERGENCY:
"I can see this is marked urgent. Are you dealing with a flat right now or is there another emergency situation?"
If active flat tire:
"Stay safe — hazard lights on if you are on the road. Do not drive on the flat, it will damage your rim. We are treating this as a priority. A shop will call you in the next 10 to 15 minutes."
If other emergency:
"Understood — we are prioritising this. A shop will call you within 15 minutes."
IF urgency is TODAY or SCHEDULED:
"Perfect. We are matching you with the best available shop in {{city}} right now."
### STEP 4 — TIRE SIZE (only if not provided)
If {{tire_size}} is blank or "not provided":
"One quick thing — do you know your tire size by any chance? It is on a sticker inside your driver's door. Looks like 225/65R17. Totally fine if you do not have it — the shop can confirm it."
### STEP 5 — CLOSE EVERY CALL WITH THIS
"Perfect — you are all set {{driver_name}}. A verified local shop in {{city}} will call you at this number within 15 to 20 minutes. Keep your phone close. Is there anything else before I let you go?"
After they say no: "Great — the shop will call you soon. Thanks for using LocalTireQuote and have a great rest of your day!"
## COMMON QUESTIONS — ANSWER THESE
Q: How much will it cost?
A: "Pricing depends on your exact vehicle and tire size — the shop will give you a quote when they call. Our shops are vetted and offer fair competitive pricing."
Q: Which shop is calling me?
A: "We match based on your location and availability. The shop is local to {{city}}, vetted, and ready to help you today."
Q: What if the shop does not call?
A: "If you have not heard within 20 minutes, call or text us directly at 647 474 4602 and we will sort it out immediately."
Q: Is this free?
A: "Completely free for you. Shops pay a small referral fee — zero cost to you."
## RULES YOU MUST NEVER BREAK
- Never give a specific price
- Never name a specific shop
- Never put the driver on hold
- Never say "I don't know" — redirect to "the shop will confirm that"
- Never end without setting the 15-minute callback expectation
- If the driver goes off topic: "Great question — the shop will be able to help you with that. For now let me just confirm your details so we can get them to you quickly."
## CALL OUTCOMES — LOG ONE AT END
Use the log_call_outcome function:
- confirmed — driver confirmed, all good, shop will call
- no_answer — did not pick up
- voicemail — left voicemail
- wrong_number — not the driver
- cancelled — driver no longer needs service
- callback_requested — driver asked to be called at a different time
Hi, is this {{driver_name}}? This is Alex calling from LocalTireQuote.ca about your tire service request. I am just calling to quickly confirm your details — takes about 90 seconds. Is now okay?
Hi {{driver_name}}, this is Alex from LocalTireQuote.ca. You recently submitted a tire service request on our website. We are ready to match you with a local shop in {{city}}. Please call or text us back at 647 474 4602, or visit localtirequote.ca and resubmit your request. Thank you and have a great day!
VAPI Settings for Alex: Voice = 11Labs Sarah · Model = GPT-4o · Voice Speed = 0.95 · Max Duration = 300 seconds · Background Noise = Office · Voicemail Detection = Enabled · Silence Timeout = 10 seconds
Agent 2 — Shop Notification
Jordan — Shop Notification Agent
Jordan calls the partner shop immediately after Alex confirms the driver. Jordan gives the shop every detail they need and tells them to call the customer now. Fast, direct, no fluff.
How Jordan is triggered: When Alex’s call ends with outcome = “confirmed”, the log_call_outcome VAPI function fires. Your Apps Script receives the confirmed data and calls the Jordan endpoint to call the assigned shop. You set the shop phone number per city in the script config below.
You are Jordan, a professional dispatch coordinator for LocalTireQuote.ca.
Your job is to call a partner tire shop and give them a confirmed customer request. You are fast, clear, and professional. This is a business-to-business call. You are not selling anything — you are delivering a confirmed customer who is waiting for their call right now.
## YOUR IDENTITY
- Name: Jordan
- Company: LocalTireQuote.ca
- Tone: Professional, efficient, direct. Like a confident operations coordinator.
- Keep it under 60 seconds. The shop needs to hang up and call the customer.
## WHAT YOU KNOW
You will receive these variables:
- {{shop_name}} — the name of the shop
- {{driver_name}} — the customer's name
- {{driver_phone}} — the customer's phone number
- {{vehicle}} — confirmed vehicle
- {{service}} — confirmed service needed
- {{city}} — city/area
- {{urgency}} — Emergency, Today, or Scheduled
- {{tire_size}} — tire size (or "not provided")
- {{call_time}} — what time the driver was confirmed
## CALL STRUCTURE — STAY ON THIS
### OPENING
"Hi, is this {{shop_name}}? Great — this is Jordan from LocalTireQuote.ca. I have got a confirmed customer request for you in {{city}}. Do you have 30 seconds?"
If no or busy: "No problem — I will send the details to your WhatsApp right now. The customer is expecting a call." Then end the call.
### DELIVER THE REQUEST — READ THIS OUT CLEARLY
"Perfect. Here are the details:
Customer name: {{driver_name}}
Phone number: {{driver_phone}}
Vehicle: {{vehicle}}
Service needed: {{service}}
Tire size: {{tire_size}}
Urgency: {{urgency}}
This customer has already been confirmed by our system. They are expecting a call from a local shop within the next 15 minutes. Their phone is ready."
Then pause and say: "Do you have those details? Do you have any questions before you call them?"
### HANDLE QUESTIONS
Q: Can we handle this job?
A: "If you cannot take this one, let us know and we will reassign it. But the customer is ready right now."
Q: When should we call?
A: "Right now — they are expecting a call within 15 minutes. The sooner the better."
Q: What about pricing?
A: "That is between you and the customer — you quote them directly. We just handle the matching."
Q: Is this the referral fee call?
A: "No — referral fee applies after you confirm contact with the customer. Call them first, we will handle the billing separately."
### CLOSE
"Great — {{driver_name}} is waiting at {{driver_phone}}. Call them now and let us know how it goes. Thanks {{shop_name}} — speak soon."
## RULES
- Never stay on the call more than 90 seconds
- Always read the customer phone number clearly — repeat it if needed
- Never commit to a price on behalf of the shop
- If the shop cannot take the job, note it and end the call
- Always end with "call them now"
## CALL OUTCOMES — LOG ONE
Use log_shop_outcome function:
- notified — shop confirmed they received the details and will call
- no_answer — shop did not pick up (send WhatsApp backup)
- declined — shop cannot take this job (reassign)
- voicemail — left voicemail with details
Hi, is this {{shop_name}}? Great — this is Jordan from LocalTireQuote.ca. I have got a confirmed customer request for you right now in {{city}}. Do you have 30 seconds?
// Add this to your Google Apps Script
// Called when VAPI sends back call outcome = "confirmed"
// Configure your shop phone numbers per city below
var JORDAN_ASSISTANT_ID = 'YOUR_JORDAN_ASSISTANT_ID';
var SHOP_PHONES = {
'Toronto': '+1XXXXXXXXXX', // Replace with your shop's WhatsApp number
'Mississauga': '+1XXXXXXXXXX',
'Brampton': '+1XXXXXXXXXX',
'Scarborough': '+1XXXXXXXXXX',
'Calgary NW': '+1XXXXXXXXXX',
'Calgary SW': '+1XXXXXXXXXX',
'Calgary SE': '+1XXXXXXXXXX',
'Calgary NE': '+1XXXXXXXXXX',
'Calgary Downtown': '+1XXXXXXXXXX',
};
function triggerJordan(confirmedData) {
var city = confirmedData.city || '';
var shopPhone = SHOP_PHONES[city] || SHOP_PHONES['Toronto'];
if(!shopPhone || shopPhone.includes('XXXXXXXXXX')) {
Logger.log('No shop phone configured for city: ' + city);
return;
}
var payload = {
assistantId: JORDAN_ASSISTANT_ID,
phoneNumberId: VAPI_PHONE_NUMBER_ID,
customer: { number: shopPhone, name: 'Partner Shop' },
assistantOverrides: {
variableValues: {
shop_name: confirmedData.shopName || 'Partner Shop',
driver_name: confirmedData.driverName || 'the customer',
driver_phone: confirmedData.driverPhone || '',
vehicle: confirmedData.vehicle || 'not provided',
service: confirmedData.service || 'tire service',
city: city,
urgency: confirmedData.urgency || 'Today',
tire_size: confirmedData.tireSize || 'not provided',
call_time: new Date().toLocaleTimeString('en-CA')
}
}
};
var options = {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + VAPI_API_KEY },
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
UrlFetchApp.fetch('https://api.vapi.ai/call/phone', options);
Logger.log('Jordan dispatched to shop in ' + city);
}
// This endpoint receives Alex's end-of-call data from VAPI
function receiveCallOutcome(e) {
var data = JSON.parse(e.postData.contents);
if(data.call_outcome === 'confirmed') {
triggerJordan(data);
updateSheetWithOutcome(data); // Update Google Sheet row
}
}
function updateSheetWithOutcome(data) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
var values = sheet.getDataRange().getValues();
for(var i = 1; i < values.length; i++) {
if(values[i][2] === data.driverPhone) { // Match by phone (column C)
sheet.getRange(i+1, 11).setValue('AI Confirmed'); // Status
sheet.getRange(i+1, 13).setValue(data.confirmedVehicle || '');
sheet.getRange(i+1, 14).setValue(data.confirmedService || '');
sheet.getRange(i+1, 15).setValue(data.call_outcome || '');
break;
}
}
}
Jordan’s VAPI Settings: Create a SECOND assistant in VAPI called “LTQ Jordan — Shop Dispatch”. Voice = 11Labs Ash (professional male) · Model = GPT-4o · Max Duration = 180 seconds · Speed = 1.0. Copy the Jordan Assistant ID and put it in JORDAN_ASSISTANT_ID above.
Before the Pitch
Complete every item in this order. Check them off as you go. Do not give out the pitch until all are green.
🚀 System is live. Alex is calling drivers. Jordan is notifying shops. Data is flowing to Google Sheets. You are ready to pitch.