SEO vs. GEO | The Future of Search
Discover why GEO (Generative Engine Optimization) is replacing traditional SEO. Learn how to rank for AI citations with Induji Technologies - Request a Quote today!
Induji Technical Team
Induji Technical Team
Content Strategy
For B2B enterprises, the journey from an ad click to a closed deal can span weeks, if not months. This protracted sales cycle creates a massive data chasm for digital advertising platforms. Google Ads, by default, optimizes for immediate, observable conversions—typically, a "Thank You" page view after a form submission.
This leads to a dangerous optimization fallacy: the algorithm treats a lead from a Fortune 500 CTO with a $250,000 budget identically to a lead from a student researching a project. Both are just "one conversion." As a result, your Cost Per Acquisition (CPA) might look great, but your pipeline is filled with low-quality leads, and your actual Return On Ad Spend (ROAS) is abysmal.
The solution is not to abandon automated bidding but to feed it better data. We need to inform Google which clicks actually resulted in revenue. This is achieved through Offline Conversion Imports (OCI), and the technical backbone for this system is a robust data pipeline connecting your single source of truth—your ERP—to the ad platform. This guide provides the definitive architectural blueprint for building this pipeline using ERPNext, Next.js 15, and the Google Ads API.
The goal is to create a seamless, automated flow of information. When a prospect clicks a Google Ad, they are tagged with a unique Google Click Identifier (GCLID). Our architecture must capture this ID, shepherd it through the entire sales process within ERPNext, and then use it to report a conversion back to Google Ads at the precise moment a deal is won.
The core components of this architecture are:
gclid parameter from the URL.gclid. It validates the data and uses the ERPNext REST API to create a new Lead.gclid and trigger an action upon a change in deal status.Opportunity status is updated to Won. This webhook sends a payload of critical data to our processing middleware.gclid to the original ad click, and attributes the revenue value correctly, thereby enriching the bidding algorithm's dataset.This first phase is about ensuring the gclid is captured reliably and permanently associated with the lead record in your ERP.
In your Next.js application, the lead generation form component needs to read the gclid from the URL query parameters. With the App Router in Next.js 15 and React 19, the useSearchParams hook makes this straightforward.
// app/components/LeadForm.tsx
'use client';
import { useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
export default function LeadForm() {
const searchParams = useSearchParams();
const [gclid, setGclid] = useState('');
useEffect(() => {
const gclidParam = searchParams.get('gclid');
if (gclidParam) {
setGclid(gclidParam);
}
}, [searchParams]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch('/api/leads', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('Failed to submit lead');
}
// Handle success (e.g., redirect to thank you page)
window.location.href = '/thank-you';
} catch (error) {
console.error(error);
// Handle error
}
};
return (
<form onSubmit={handleSubmit}>
{/* Your form fields: name, email, company, etc. */}
<input type="text" name="full_name" required />
<input type="email" name="email_id" required />
{/* Hidden field to pass the GCLID */}
<input type="hidden" name="gclid" value={gclid} />
<button type="submit">Submit</button>
</form>
);
}
When the form is submitted, it hits a Next.js API Route. This route is responsible for communicating with ERPNext.
First, prepare ERPNext:
Setup > Customize Form.Lead DocType.custom_gclid (ERPNext automatically prefixes with custom_)Opportunity DocType, and ensure the field is mapped to be carried over when a Lead is converted to an Opportunity.Next, build the API Route:
// app/api/leads/route.ts
import { NextResponse } from 'next/server';
// Assuming you have a helper/SDK for Frappe/ERPNext
import { FrappeClient } from 'frappe-client-ts';
const client = new FrappeClient(
process.env.ERPNEXT_URL!,
{
useToken: true,
token: () => process.env.ERPNEXT_API_KEY!,
}
);
export async function POST(request: Request) {
try {
const body = await request.json();
const { full_name, email_id, gclid } = body;
if (!full_name || !email_id) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
const leadDoc = {
doctype: 'Lead',
lead_name: full_name,
email_id: email_id,
// Pass the GCLID to your custom field
custom_gclid: gclid || null,
};
const newLead = await client.createDoc('Lead', leadDoc);
return NextResponse.json({ success: true, lead: newLead.name }, { status: 201 });
} catch (error) {
console.error('ERPNext Lead Creation Failed:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
With the gclid safely stored, we now wait for the sales team to do their work. When they finally close a deal by updating the Opportunity status to Won in ERPNext, we need to trigger our pipeline.
In your ERPNext desk, search for and navigate to "Webhook".
Click "Add Webhook".
Webhook DocType: Select Opportunity.
Webhook Event: Choose On Update.
Condition: This is crucial to prevent firing on every update. Set the condition to:
doc.status == 'Won' and doc.get_doc_before_save().status != 'Won'
This ensures the webhook only fires on the specific transition to "Won".
Request URL: Enter the URL of the Next.js middleware API route you will build in the next step (e.g., https://your-app.com/api/google-ads-conversion).
Webhook Data: Check "Send Webhook Data" and define the fields to be sent. You only need a few:
custom_gclid (the GCLID from the original Lead)grand_total (the value of the deal)transaction_date (the date the deal was won)currencyRequest Headers: Add a header for security, like X-ERPNext-Secret: YOUR_SECRET_KEY.
This is the final, most critical link in the chain. Our middleware receives the payload from ERPNext and communicates with Google.
This API route acts as a secure and robust processor. It validates the incoming request, formats the data, and handles the Google Ads API communication.
// app/api/google-ads-conversion/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { GoogleAdsApi, enums } from '@google-ads/google-ads-api';
export async function POST(request: NextRequest) {
// 1. Authenticate the webhook request
const secret = request.headers.get('x-erpnext-secret');
if (secret !== process.env.ERPNEXT_WEBHOOK_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const body = await request.json();
const { custom_gclid, grand_total, transaction_date, currency } = body;
// 2. Validate essential data
if (!custom_gclid || !grand_total || !transaction_date) {
console.log('Webhook received with missing data:', body);
// Return 200 to prevent ERPNext from retrying a bad request
return NextResponse.json({ message: 'Missing required conversion data.' });
}
// 3. Authenticate with Google Ads API
const client = new GoogleAdsApi({
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
developer_token: process.env.GOOGLE_DEVELOPER_TOKEN!,
});
const customer = client.Customer({
customer_id: process.env.GOOGLE_ADS_CUSTOMER_ID!,
login_customer_id: process.env.GOOGLE_ADS_MANAGER_ID!, // If using MCC
refresh_token: process.env.GOOGLE_REFRESH_TOKEN!,
});
// 4. Construct and Upload the Conversion
const conversionUploadService = customer.services.ConversionUpload;
const clickConversion = {
gclid: custom_gclid,
conversion_action: `customers/${process.env.GOOGLE_ADS_CUSTOMER_ID}/conversionActions/${process.env.CONVERSION_ACTION_ID}`,
conversion_date_time: new Date(transaction_date).toISOString().replace('T', ' ').split('.')[0], // Format: 'YYYY-MM-DD HH:MM:SS'
conversion_value: grand_total,
currency_code: currency,
};
const response = await conversionUploadService.uploadClickConversions({
customer_id: process.env.GOOGLE_ADS_CUSTOMER_ID!,
conversions: [clickConversion],
partial_failure: true, // Recommended
});
console.log('Successfully uploaded conversion:', response);
return NextResponse.json({ success: true, result: response.results[0] });
} catch (error) {
console.error('Google Ads Conversion Upload Failed:', error);
// Log the error for debugging
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
With this pipeline active, the data flowing into your Google Ads account is transformed. You are no longer telling the algorithm "get me more form fills." You are now providing explicit instructions: "get me more clicks that result in high-value, closed deals."
This enables a strategic shift:
Maximize Conversions to Target CPA: You can now set a realistic Target Cost Per Qualified Opportunity, not just a raw lead.Target ROAS: By sending the grand_total as the conversion value, you can directly instruct Google's AI to optimize for a specific Return On Ad Spend. If you tell Google you want a 5x ROAS, it will actively adjust bids to find users whose behavior patterns correlate with past high-value customers, even if it means a higher cost-per-click or a lower top-of-funnel conversion rate.Be patient. Google's bidding algorithms require data. Expect a learning period of several weeks after implementation. A steady flow of 30-50 offline conversions per month is generally recommended for these strategies to perform optimally.
Q1: What are the prerequisites in Google Ads for this to work?
You must have auto-tagging enabled in your Google Ads account settings. You also need to create a new Conversion Action with the source set to "Import from clicks." This will provide you with the CONVERSION_ACTION_ID needed in the API call.
Q2: How do I handle leads that come from channels without a GCLID?
The architecture is resilient to this. The custom_gclid field in ERPNext should not be a required field. The ingestion API should be coded to handle cases where the gclid is null or undefined. The conversion pipeline will simply not trigger for these non-Google Ads leads, which is the correct behavior.
Q3: Can this be done without Next.js? Absolutely. The middleware can be built with any server-side technology (e.g., Express.js, Python/Flask, PHP/Laravel). However, Next.js API Routes, particularly when deployed on serverless platforms like Vercel, offer an excellent combination of developer experience, scalability, and cost-efficiency for this type of event-driven task.
Q4: What's the typical delay between the ERPNext event and the conversion appearing in Google Ads? The API call from your Next.js middleware is nearly instantaneous. However, Google Ads can take anywhere from a few hours to 24 hours to process and attribute the imported conversion in its reporting interface.
Architecting a closed-loop conversion pipeline is a high-leverage engineering effort that directly impacts marketing efficiency and business profitability. It transforms your ad budget from an expense into a precision-guided investment in revenue.
This is not a trivial task. It requires expertise across system architecture, DevOps, ERP customization, and third-party API integration. If you are ready to unlock the true potential of your B2B marketing with a data-driven bidding strategy, the team at Induji Technologies is here to help.
Discover why GEO (Generative Engine Optimization) is replacing traditional SEO. Learn how to rank for AI citations with Induji Technologies - Request a Quote today!
Induji Technical Team
Learn how to get your brand cited in ChatGPT Search. Follow our 7-step guide to AI Engine Optimization (AIEO) for 31% higher conversion rates.
Induji Technical Team
Discover why AEO is the new SEO. Learn how to optimize for AI answer engines like ChatGPT and Google SGE with Induji - Request a Quote!
Induji Technical Team
Partner with Induji Technologies to leverage cutting-edge solutions tailored to your unique challenges. Let's build something extraordinary together.