Google Ads conversion tracking with AgentFirstStack
To track Google Ads conversions with AgentFirstStack, an agent adds one container snippet to your site and runs setup with your conversion ID and labels. The browser tag then reports each conversion with enhanced conversions data, and, if you add server credentials, the service also uploads click conversions through Google's Data Manager API with the stored gclid and hashed email or phone. Test traffic is never imported into Google Ads, and a test call returns Google's own answer.
The prompt
Paste this into your coding agent. It finishes everything it can, then asks only for the Google Ads values it couldn't find.
Set up Google Ads conversion tracking for this project with AgentFirstStack Tags. It's a free, open-source tag manager with server-side conversion APIs, built for agents, and you can do the setup yourself: there is no signup form. 1. Read https://agentfirststack.com/llms.txt and https://tags.agentfirststack.com/llms.txt. 2. If this project's .env already has AGENTFIRSTSTACK_API_KEY, use it and read AGENTFIRSTSTACK.md. Otherwise get a key: curl -X POST https://agentfirststack.com/v1/signup, and save api_key in .env as AGENTFIRSTSTACK_API_KEY and in the production secrets. 3. Create a tags container for the production domain, put its snippet in the head of every page, and serve it through a first-party proxy on our own domain (the configs from GET /v1/containers/:id/install?proxy=1, with the proxy key kept server-side), so ad blockers don't drop the conversion hits. Deploy, and run scan. If the site already has Google Tag Manager or a hard-coded AW- tag, tell me what it fires before you change anything. 4. Find the conversions that matter here (purchases, sign-ups, leads) and run setup with platforms.google_ads: the AW- conversion ID and a label per conversion for the browser tag, and, if I have them, server credentials for an Import from clicks conversion action. Map the visitor's email or phone where the page has it, for enhanced conversions. If you have Google Ads tools, use them to find the IDs; otherwise ask me for exactly what setup lists as missing. 5. Validate the draft and fix every error. If it warns google_ads_double_count, tell me which conversion action to keep as primary. Test the google_ads destination and show me Google's answer, open a preview on the conversion pages and read its log, then publish. 6. If the backend knows about conversions the browser doesn't (paid orders), send them to POST /v1/containers/:id/events with the visitor_id and event_id from the page. 7. Write the container id and the env var name (never the key) in AGENTFIRSTSTACK.md, and create a claim link for me with POST https://agentfirststack.com/v1/claim-link. Tell me what you set up, what each test returned, what's waiting on me, and give me the claim link.
Two ways Google Ads hears about a conversion
A conversion can reach Google Ads from the browser, from the server, or both. They use different kinds of conversion action in Google Ads, so decide which one counts.
| Browser tag | Server upload | |
|---|---|---|
| Conversion action in Google Ads | A website action, with a tag: AW- conversion ID and a label | An "Import from clicks" action (type UPLOAD_CLICKS) |
| What it sends | gtag('event', 'conversion', {send_to, value, currency, transaction_id}), with hashed user_data | The visitor's gclid, gbraid or wbraid, value, currency, transaction ID and hashed email, phone or address |
| From you | The conversion ID and labels (IDs only) | customer_id, conversion_action_id, and an OAuth client id, secret and refresh token |
| Covers | Conversions that happen on a page, when the browser loads Google's tag | Conversions only your backend knows, even days after the click; and page conversions whose Google tag was blocked, if the hit still reached the service (see below) |
If one conversion goes through both, Google Ads records it once per action. draft/validate warns google_ads_double_count in that case: keep only one of the two actions as a primary conversion in Google Ads, or drop one side. Many sites start with the tag and add the upload for purchases the backend confirms.
About ad blockers. Conversions your backend sends with POST /v1/containers/:id/events go from your server to ours to Google, so a browser can't block them. Browser hits are different: the default snippet loads from tags.agentfirststack.com and posts to it, and ad blockers can block that host. Serve the container through a path on your own domain with the proxy configs from GET /v1/containers/:id/install?proxy=1 (a Cloudflare Worker, Next.js, Vercel, Netlify or nginx; each sends the container's x-afs-proxy-key). There's no custom-domain (CNAME) option yet.
What the owner provides
AgentFirstStack never creates ad accounts or conversion actions; it sends to yours. An agent with Google Ads tools can look these up. Otherwise setup lists what's missing and where to find it:
- Conversion ID and labels (browser): Google Ads > Goals > Conversions > the action > Tag setup > Install the tag yourself. The ID is
AW-...; the label is the part after the slash insend_to. - Server upload: an Import from clicks conversion action and its numeric id (
ctId=in the action's URL), the account'scustomer_id, and an OAuth client from the Google Cloud console with a refresh token for the scopehttps://www.googleapis.com/auth/datamanager. Addlogin_customer_idwhen access goes through a manager account.
Credentials are sealed at rest and never returned; responses show "configured": true.
Set it up by API
T=https://tags.agentfirststack.com
H="Authorization: Bearer $AGENTFIRSTSTACK_API_KEY"
J="content-type: application/json"
curl -s -X POST $T/v1/containers -H "$H" -H "$J" -d '{"domain":"example.com"}'
curl -s "$T/v1/containers/$C/install?proxy=1" -H "$H" # first-party proxy configs (keep the key server-side)
# add the proxy and the proxied <script> to <head>, deploy, then:
curl -s -X POST $T/v1/containers/$C/scan -H "$H"
curl -s -X POST $T/v1/containers/$C/setup -H "$H" -H "$J" -d '{
"platforms": {
"google_ads": {
"conversion_id": "AW-123456789",
"labels": { "purchase": "AbCdEfGhIj" },
"server": {
"customer_id": "123-456-7890", "conversion_action_id": "987654321",
"client_id": "...apps.googleusercontent.com", "client_secret": "...", "refresh_token": "1//..."
}
}
},
"conversions": [{
"name": "purchase", "when": { "data_layer_event": "purchase" },
"value": { "data_layer": "ecommerce.value" }, "currency": "USD",
"transaction_id": { "data_layer": "ecommerce.transaction_id" },
"email": { "data_layer": "customer.email" }
}]
}'
# -> draft, validation, destinations, missing[] (what's still needed, and where)
curl -s -X POST $T/v1/containers/$C/draft/validate -H "$H"
curl -s -X POST $T/v1/containers/$C/destinations/$D/test -H "$H" # Google's answer, validateOnly
curl -s -X POST $T/v1/containers/$C/preview -H "$H" # preview_url on the live site
curl -s -X POST $T/v1/containers/$C/publish -H "$H" -H "$J" -d '{"note":"google ads purchase"}'
Leave out server to start with the browser tag only, and run setup again when the credentials arrive: it merges into the draft. Conversions can also fire on a thank-you page (thank_you_path), a form submit (form_selector) or a click (click_selector). Over MCP the same calls are tags_setup_conversion_tracking, tags_validate_draft, tags_test_destination and tags_publish.
Enhanced conversions
Enhanced conversions send hashed customer data with a conversion. Google's help describes the feature as sending "hashed first-party, user-provided data from your website when a user converts", hashed with SHA-256 (About enhanced conversions, as of 6 Nov 2026).
- In the browser, the Google Ads tag is configured with
allow_enhanced_conversions: true. When a conversion has an email or phone mapped (oruser_data: "auto", which takes the submitted form's first email and phone field), the runtime hashes them with SHA-256 in the browser and callsgtag('set', 'user_data', ...)withsha256_email_address,sha256_phone_numberand, when known, hashed first and last name with the postal code and country. That happens only when the visitor'sad_user_dataconsent is granted. - On the server, each upload carries the same hashed email and phone as user identifiers next to the click id (through the Data Manager API, also an address when first name, last name, country and postal code are all known). Google's Data Manager guide covers this as "sending Google Ads offline conversions or enhanced conversions for leads" (Send offline conversions, as of 6 Nov 2026).
- In Google Ads, you turn it on: Goals > Settings, expand "Enhanced conversions", check "Turn on enhanced conversions", agree to the customer data terms and choose "Google tag" as the method (Set up enhanced conversions for web using the Google tag, as of 6 Nov 2026). This is a person's decision in their account, so the agent lists it as waiting on you.
Conversions from your backend
For a purchase the browser can't see (a paid invoice, an order confirmed by a webhook), the backend reports it and the service joins it to the visitor's stored click id. Click ids from the landing URL are kept for 90 days.
curl -s -X POST $T/v1/containers/$C/events -H "$H" -H "$J" -d '{"events":[{
"name": "purchase", "visitor_id": "'$VISITOR_ID'", "event_id": "'$EVENT_ID'",
"order_id": "T1001", "value": 49, "currency": "USD", "email": "jane@example.com"
}]}'
visitor_idcomes fromafst('get','visitor_id')on the page, passed along with the checkout request. Pass the browser conversion'safst('get','event_id')asevent_idif the page also tracked it.- The upload uses your
order_idas the transaction ID, or theevent_idwhen there's none. Google Ads uses the transaction ID to catch duplicates for the same conversion action (Use a transaction ID to minimize duplicate conversions, as of 6 Nov 2026); in the Data Manager API, an event whose transaction ID already exists is handled as an adjustment (upgrade guide, as of 6 Nov 2026). - Emails and phones are normalised and hashed before anything is sent. With no click id and no hashed email or phone, the delivery is skipped and the event log says why. Without
ad_user_dataconsent it's skipped too.
Test traffic never becomes a conversion
An agent checking your setup clicks through the site like a visitor. None of that may count in Google Ads:
- In the browser: a tab opened with
?afsa_debug=1, through a preview link, or afterafst('test', true)is test traffic. The Google Ads tag doesn't fire there; the preview log shows it astest_skipped("ad pixels don't fire in the browser; the server-side conversion is sent in test mode"). - On the server: test conversions, backend events sent with
"test": trueand destination tests go to Google withvalidateOnly: true. Google's guide says this checks the request "without ingesting any data" (Data Manager guide, as of 6 Nov 2026). You get Google's real validation errors, and nothing is imported.
Verify it
- Destination test:
POST /v1/containers/:id/destinations/:did/testreturns{ result: { ok, http_status, response }, test_mode }, whereresponseis Google's answer. A bad refresh token or a conversion action of the wrong type shows up here. - Preview:
POST /v1/containers/:id/previewreturns apreview_urlthat runs the draft on the live site. Its log (GET .../preview/:token/log, MCPtags_get_preview_log) lists each trigger matched and each tag fired or skipped, with the reason. - The whole setup:
POST https://agentfirststack.com/v1/setup/testsends each conversion to each ad platform in its test mode and returns a browser plan;GET /v1/setupthen reports the tags rows, ad platform deliveries included. - After real traffic:
GET /v1/containers/:id/events?since=1hshows each conversion with its status per destination, attempts, Google's HTTP status and any error. Transient failures (network errors, 429, 5xx) are retried with backoff, up to 6 attempts.
Data Manager API or Google Ads API
The google_ads destination uploads through Google's Data Manager API by default (POST https://datamanager.googleapis.com/v1/events:ingest). Google's upgrade guide lists the developer token as "Not required" there and "Required" for the Google Ads API (upgrade guide, as of 6 Nov 2026). If you already upload with a Google Ads API developer token, set "api": "google_ads_api" with developer_token and a refresh token for the adwords scope; the server-side guide has the background.
FAQ
Does AgentFirstStack support Google Ads enhanced conversions?
Yes, on both sides. The browser tag loads with allow_enhanced_conversions and sets user_data with SHA-256 hashed email, phone and address fields. The server upload sends hashed email, phone and address as user identifiers next to the gclid. You turn enhanced conversions on in Google Ads and accept its customer data terms.
Will a conversion count twice if I use the tag and the upload?
It can. The tag reports to a website conversion action and the upload to an Import from clicks action, and Google Ads counts each action separately. The validator warns (google_ads_double_count) when one conversion does both: keep one action as primary in Google Ads, or drop one side.
Do I need Google Tag Manager or a developer token?
No. The container replaces the web container, and server uploads go through Google's Data Manager API, which needs no developer token. The older Google Ads API path is available with api set to google_ads_api, and that one needs a developer token.
Can test traffic end up as a conversion in Google Ads?
No. In test traffic (a preview link, ?afsa_debug=1 or afst('test', true)) the Google Ads tag doesn't fire in the browser, and server uploads are sent with validateOnly, which Google validates without ingesting any data. Backend events sent with "test": true are handled the same way.
What if a visitor has no gclid?
The upload still goes out if the conversion carries a hashed email or phone. With no click id (gclid, gbraid or wbraid) and no hashed email or phone, the delivery is logged as skipped with that reason.
Related
- Meta Conversions APIPixel and server, deduplicated by event id.
- Server-side conversion trackingMeta, Google Ads, TikTok and GA4, counted once.
- Stape alternativeServer-side GTM hosting, or conversion APIs built in.
- Google Tag Manager MCP comparedEdit GTM, or let the agent run the tag manager.
- AnalyticsAdd analytics with an AI agentOne prompt, every step, and what's left for you.
- All servicesOne MCP server for every serviceAnalytics, tags, mail and replay tools on one key.