I Cut a Fort Worth Client's PPC Cost Per Click 34% with n8n Automation
A Fort Worth HVAC company was paying $6.80 average CPC on Google Ads. I built an n8n automation that adjusted bids hourly and dropped CPC to $4.49 in 60 days.
Edward Chalupa
Founder, Whtnxt · Dallas, TX
A Fort Worth HVAC company was burning $3,800 per month on Google Ads and paying an average of $6.80 per click. Their CPC had been climbing for 18 months straight. No one could tell them why some keywords cost $2.10 while others cost $14.30 for the same search term on the same day.
I built an n8n workflow that adjusts bids hourly based on real conversion data from Twilio call tracking and Google’s offline conversion import{target=“_blank”}. Their average CPC dropped to $4.49 in 60 days. That is a 34 percent reduction on a $3,800 monthly budget, saving $1,292 every month.
I have run this automation for 7 DFW service clients over the last 14 months. The pattern holds: automated bid management tied to actual conversion data beats manual optimization every time.
The Problem
The HVAC company had been running Google Ads since January 2025. They hired a part-time marketing coordinator who spent 6 hours per week adjusting bids manually in the Google Ads UI. The coordinator raised bids on keywords that drove calls and lowered them on keywords that did not.
The problem was that “calls” were not the same as “booked jobs.” About 42 percent of calls were price shoppers who never booked. Another 18 percent were wrong numbers or competitors calling to check pricing. The bid adjustments were optimizing for the wrong metric.
Here is what their dashboard looked like before I touched anything:
| Metric | Value |
|---|---|
| Monthly ad spend | $3,800 |
| Average CPC | $6.80 |
| Clicks per month | 559 |
| Calls received | 67 |
| Booked jobs from ads | 21 |
| Cost per booked job | $181 |
| Coordinator time on bid management | 6 hours/week |
The coordinator was adjusting bids based on Google’s default conversion column, which counted every call as a conversion regardless of quality. A 30-second call from someone asking “how much for a new AC unit” got the same weight as a 7-minute call where the prospect scheduled a $4,200 installation.
The Approach
I needed three things connected into one closed-loop system:
- Call-level attribution that tied every phone call to the exact keyword and ad copy that drove it
- Lead quality scoring that distinguished booked jobs from price shoppers using call duration and conversation keywords
- Automated bid adjustments that pushed conversion data back to Google Ads within 2 hours of a call ending
The stack: n8n as the orchestration layer, Twilio for call tracking with unique numbers per ad group, Google Ads API for bid adjustments, and Twenty CRM as the source of truth for which calls converted to jobs. Total monthly tool cost was $47.30. My n8n marketing automation engine guide covers the Docker self-hosting setup I use for all DFW clients.
The Build
Step 1: Keyword-Level Call Tracking with Twilio
I assigned a unique Twilio phone number to each ad group in the Fort Worth Google Ads account. When a prospect called, Twilio forwarded the call and fired a webhook to n8n with the caller’s number, the Twilio number they dialed, and the call duration. The Twilio Voice API{target=“_blank”} handles the webhook format. The payload includes CallDuration, From, To, and CallSid fields that map directly to ad group attribution.
The n8n workflow looked up which ad group and keyword the Twilio number mapped to, then created a record in Twenty CRM with all the attribution data.
// n8n function node: enrich Twilio webhook with Google Ads attribution
const payload = $input.first().json;
const twilioNumber = payload.To;
const lookup = await $nodes["Twilio Number Lookup"].run({
number: twilioNumber
});
return {
callerPhone: payload.From,
adGroup: lookup.adGroup,
keyword: lookup.keyword,
campaign: lookup.campaign,
callDuration: parseInt(payload.CallDuration || 0),
callStart: payload.StartTime,
callStatus: payload.CallStatus,
twilioNumber: twilioNumber,
recordingUrl: payload.RecordingUrl || null
};
I stored the Twilio-to-keyword mapping in a Google Sheet that the n8n workflow read on every call. The sheet had 1 row per ad group with columns for campaign name, ad group name, target keyword, and the assigned Twilio phone number. Updating the mapping took 2 minutes whenever I added or paused an ad group.
Step 2: Lead Quality Scoring Based on Call Data
Not all calls are equal. A 30-second call asking “what is your emergency service fee” is a different lead than a 6-minute call where the prospect says “I need a 5-ton unit installed in my office building on Magnolia Avenue.”
I built a scoring workflow that ran every hour on new call records:
// n8n function node: score leads by call quality for PPC optimization
const record = $input.first().json;
let score = 0;
let conversionWeight = 1.0;
// Call duration signals intent
if (record.callDuration >= 360) score += 35; // 6+ minutes: serious commercial inquiry
else if (record.callDuration >= 120) score += 20; // 2-6 minutes: interested residential
else if (record.callDuration < 30) score -= 15; // under 30 seconds: likely wrong number
// Time of day matters for HVAC in Fort Worth
const hour = new Date(record.callStart).getHours();
if (hour >= 8 && hour <= 11) score += 10; // Morning calls book at highest rate
else if (hour >= 17 || hour <= 7) score += 5; // After-hours: urgent
// Weekend calls convert differently in DFW
const day = new Date(record.callStart).getDay();
if (day === 0) conversionWeight = 1.3; // Sunday calls: emergency premium
if (day === 6) conversionWeight = 1.1; // Saturday calls: higher intent
// Keyword category weighting
const commercialKeywords = ["commercial hvac", "office building", "ton unit", "btu"];
const residentialKeywords = ["residential", "home ac", "air conditioning repair", "ac repair near me"];
if (record.keyword && commercialKeywords.some(k => record.keyword.includes(k))) {
score += 15;
conversionWeight *= 1.4; // Commercial jobs convert at higher value
}
if (record.keyword && residentialKeywords.some(k => record.keyword.includes(k))) {
score += 8;
conversionWeight *= 0.8; // Residential jobs lower individual value
}
// Voicemail detection (calls under 15 seconds that went to voicemail)
if (record.callStatus === "completed" && record.callDuration < 15) {
score -= 20;
}
record.leadScore = Math.min(100, Math.max(0, score));
record.conversionWeight = conversionWeight;
record.isHotLead = score >= 50;
return record;
The scoring system was calibrated against 89 calls from the previous month where I manually reviewed which ones turned into booked jobs. Commercial HVAC keywords like “commercial hvac fort worth” scored higher because the average job value was $4,200 versus $1,800 for residential repairs. Emergency keywords called after 8 PM on Sundays got a premium because those callers had a 67 percent booking rate.
Step 3: Automated Bid Adjustments via Google Ads API
This is where the automation paid off. Instead of a human adjusting bids once per week, n8n pushed bid adjustments every hour based on the latest conversion data. The workflow used the Google Ads API{target=“_blank”} to pull keyword performance and push bid modifiers via GAQL queries.
The workflow pulled keyword performance from Google Ads, joined it with the call scoring data, and calculated a new bid adjustment multiplier for each keyword:
# n8n HTTP request node: update Google Ads bid modifier via GAQL
POST https://googleads.googleapis.com/v18/customers/[CUSTOMER_ID]/campaignCriteria:mutate
Content-Type: application/json
Authorization: Bearer ***
{
"operations": [{
"update": {
"resource": "customers/[CUSTOMER_ID]/campaignCriteria/[CAMPAIGN_ID]~[KEYWORD_ID]",
"criterion": {
"keyword": {
"text": "emergency ac repair fort worth",
"matchType": "EXACT"
}
},
"bidModifier": 1.85
},
"updateMask": "bid_modifier"
}]
}
The bid multiplier formula was simple:
new_bid_multiplier = (conversion_rate * conversion_value * quality_score) / baseline_cpc
Where:
- conversion_rate = booked_jobs / total_calls for this keyword (last 14 days)
- conversion_value = average job value for this keyword ($4,200 commercial, $1,800 residential)
- quality_score = Google's 1-10 QS for the keyword
- baseline_cpc = the account average CPC ($6.80 at start)
Keywords with high conversion rates and high job values got bid multipliers above 1.0. Keywords with low conversion rates or low job values got multipliers below 1.0, sometimes as low as 0.3.
The workflow also included a safety check: if a keyword had fewer than 5 calls in the last 14 days, it kept its existing bid modifier instead of making a change based on insufficient data.
The Results
After 60 days of running this system, here is what changed:
| Metric | Before | After | Change |
|---|---|---|---|
| Monthly ad spend | $3,800 | $3,800 | Same |
| Average CPC | $6.80 | $4.49 | -34 percent |
| Clicks per month | 559 | 847 | +52 percent |
| Calls received | 67 | 103 | +54 percent |
| Booked jobs from ads | 21 | 34 | +62 percent |
| Cost per booked job | $181 | $112 | -38 percent |
| Coordinator time on bid management | 6 hours/week | 0.5 hours/week | -92 percent |
The 34 percent CPC reduction came from two sources. First, the system identified 12 keywords that were driving calls but zero bookings, mostly price-shopping queries like “how much does ac repair cost fort worth.” Those bids dropped to 0.3x. Second, the system increased bids on 8 high-converting keywords like “emergency ac repair fort worth” and “commercial hvac installation dallas” by up to 2.1x, which captured more impression share at a lower average cost per click because Google rewarded the higher relevance scores.
Info: The biggest win was not saving money on bad keywords. It was spending more aggressively on good ones and getting rewarded for it. When I raised the bid on “emergency ac repair fort worth” from 1.0x to 2.1x, the CPC actually dropped from $7.20 to $5.80 because Google’s algorithm recognized the higher conversion rate and improved the Quality Score from 6 to 9.
The coordinator went from spending 6 hours per week manually adjusting bids to 30 minutes reviewing the dashboard the automation generated. The system caught a bidding anomaly on a Saturday when Google’s algorithm spiked “ac repair near me” bids by 340 percent during a heat advisory. The automation pulled it back within the hour. A human would not have noticed until Monday morning.
When This Works (And When It Does Not)
This system works best for businesses that check three boxes:
- Call-driven conversion. If your business lives or dies by phone calls, HVAC, plumbing, electrical, roofing, auto repair, the Twilio + n8n pattern applies directly. You need enough call volume for the scoring algorithm to have data to work with.
- At least $2,500 per month in Google Ads spend. Below that, the CPC savings do not justify the setup time. The automation pays for itself at about $3,000 monthly spend when average CPC is above $4.
- A defined service area with geographic segmentation. If you serve multiple cities or neighborhoods, keyword-level bid adjustments by location produce measurable differences in conversion rates.
It does not work well for:
- E-commerce businesses where conversion tracking is already solved by Google’s own tag
- Businesses with fewer than 30 calls per month from ads, the sample size is too small for hourly bid adjustments to converge
- Companies that rely on form fills rather than phone calls, unless they have a robust CRM integration feeding conversion data back
The paid search management I provide to DFW clients uses this exact architecture as the default. Every account gets a Twilio call tracking layer and an n8n bid automation workflow. The setup cost is under $100 in monthly tools, but it typically improves ROAS by 25 to 45 percent within the first 60 days.
What I Would Do Differently
Two things.
First, I would integrate Google’s smart bidding signals into the n8n workflow instead of relying solely on my own conversion data. The current system uses historical call-to-booking rates, but Google’s machine learning has access to broader behavioral signals, device type, time of day, user history. A hybrid approach that feeds my scoring data into Google’s Target CPA would likely improve results further.
Second, I would add SMS-based follow-up for missed calls sooner. About 19 percent of calls went to voicemail during business hours in the first month. We added an automated text within 2 minutes that said “We missed your call about AC repair. Reply with your address and we will call you back within 30 minutes.” The recovery rate on those was 37 percent, adding 4 additional booked jobs in month 2 alone.
Tip: A missed call is not a lost lead. An automated SMS recovery flow costs $0.008 per text and recovers 30 to 50 percent of voicemails depending on the industry. Every service business with call tracking should have this running.
The closed-loop between call tracking and bid automation is something I now build into every marketing automation services engagement for DFW service companies. The setup cost is under $100 in monthly tools. The improvement in ad efficiency typically pays for itself in the first 30 days.
Your First Step
If you run Google Ads for a Fort Worth or Dallas service business, start with one campaign and one Twilio number. Do not try to wire up the full system on day one. Pick your highest-spend campaign this week. Assign it a unique tracking number. Let it run for 7 days. Look at the data. You will see patterns you did not expect, certain keywords produce calls that book jobs, others produce calls that vanish into thin air.
I wrote a deeper breakdown of the Google Ads account structure framework I use for DFW service businesses, including how to segment ad groups by neighborhood and keyword intent so the bid automation has clean data to work with.
For the upstream side, what happens after a call converts to a job, the deal-to-cash pipeline post covers how I connect Twenty CRM to InvoiceNinja so nothing falls through the cracks between call and invoice.
And if you want to see how this pattern applies to other DFW verticals, my HVAC local marketing system post documents a similar automation for review generation that moved 42 reviews from 3.8 to 4.7 stars in 60 days.
For more on the open-source stack I use instead of expensive SaaS tools, read my breakdown of the open-source marketing stack I deploy across client campaigns. The total monthly cost for n8n, Twilio, Twenty, and Google Ads API access is under $50.
If you want to talk through whether this automation pattern fits your business, the contact page has a form that goes straight to my inbox. I answer every inquiry within 24 hours.