AI Global Academy Join the waitlist

Courses / Advertising

Lesson 7.8 · 60 minClosing the loop: CRM results back to the ad platforms

Duration~60 min in the lesson + ~30 min homework
PrerequisitesCheckpoint lesson-7.7; both campaigns running
Checkpointlesson-7.8

What you will have

When a lead is moved to qualified or won in the CRM, the server sends an event to Meta's Conversions API and an offline conversion to Google Ads, respecting the lead's stored consent. The dashboard shows cost per qualified lead per campaign from spend the owner enters.

Video

The video for this lesson is not recorded yet.

Prompts used in this lesson

Part 5 — Demonstration: the status-change triggers

Prompt to Claude
Purpose: when a lead reaches the qualified or won stage in the CRM, report that
result to Meta and to Google Ads, so both platforms can count and learn from good
leads instead of all form submissions.

Context: brightside-site at lesson-7.7. Status changes go through one server path
and are recorded in lead_status_history. Leads store email, phone, gclid, fbclid,
created_at and ad_consent. The Meta Conversions API module from lesson 7.3 exists.
Before coding, read the current official documentation and follow it, not memory:
Meta "Server Event Parameters" and "fbp and fbc Parameters"; Google Data Manager API
"Send events" for Google Ads offline conversions, and its events.ingest reference.

Do this:
1. Add a migration for a table ad_conversion_log: lead id, platform, stage, sent_at,
   outcome (sent, skipped, failed), reason, platform request id. No personal data.
2. On a status change to qualified or won, after the change is saved:
   - Meta: send a custom event (QualifiedLead or CustomerWon) through the existing
     module with action_source "system_generated", event_time now, event_id
     "<stage>-<lead id>", user_data with hashed em and ph and fbc built from fbclid
     and the lead's created_at.
   - Google: send one event to the Data Manager API ingest endpoint for the matching
     conversion action, with the gclid when present, the SHA-256 hashed normalised
     email, the event timestamp, a transactionId "<stage>-<lead id>", and the
     consent fields.
3. Rules: send nothing for a lead whose ad_consent is false - log "skipped: no
   consent". Send to Google only if the lead has a gclid or an email; to Meta only
   if it has an fbclid, email or phone. Send each stage at most once per lead per
   platform, even if the status is moved back and forth - check the log first.
4. Environment: META_* as before; GOOGLE_ADS_CUSTOMER_ID,
   GOOGLE_ADS_QUALIFIED_ACTION_ID, GOOGLE_ADS_WON_ACTION_ID,
   GOOGLE_SERVICE_ACCOUNT_KEY. A variable AD_CONVERSIONS_DRY_RUN=true makes the
   Google call use validateOnly and adds META_TEST_EVENT_CODE to the Meta call.
5. Show the log entries for a lead on its lead card ("Reported to ad platforms").

Constraints: a failed or slow platform call must never block or undo the status
change; log it as failed with the platform's error message, without secrets or
contact data. Secrets come only from the environment; do not print them. Never send
unhashed contact data.

Done when: the build and tests pass; with dry run on, moving a consented test lead
to qualified logs "sent" for both platforms with a success response from each;
moving a non-consented lead logs "skipped" twice and makes no network call; moving
the first lead back and to qualified again sends nothing new. Report the evidence.

Part 7 — Demonstration: cost per qualified lead

Prompt to Claude
Purpose: show cost per lead and cost per qualified lead for each campaign on the
admin dashboard, using ad spend I enter by hand.

Context: leads carry utm_source and utm_campaign; qualified means the lead reached
qualified or won at any time (see lead_status_history).

Do this: add a table ad_spend (period start, period end, utm_source, utm_campaign,
amount) with a small form on the dashboard to add, edit and delete entries. Add a
dashboard table per campaign for a chosen date range: spend, leads, cost per lead,
qualified leads, cost per qualified lead, won, cost per customer. Show the target
cost per lead from docs/ads-plan.md as a note above it.

Constraints: campaign names must match utm_campaign exactly - offer the existing
values in a dropdown. Show a dash, not zero or an error, when there are no leads.
Exclude leads marked as test. Admin only.

Done when: the build passes, and with sample spend entered for two campaigns you
have verified each figure in the table by hand calculation. Report the working.

Do along

Pause the video where told and do each step on your own project.

  1. After Part 4, pause: create the two Google conversion actions and note their IDs. Complete the Data Manager API access setup; store the key and IDs in .env.local and Vercel yourself.
  2. After Part 5, pause: run the prompt from Part 5. Check the consent rule, the once-only rule and the log's columns. Migrate and deploy.
  3. After Part 6, pause: test with dry run on, then one real send with dry run off. Remove the Meta test code afterwards.
  4. After Part 7, pause: run the prompt from Part 7. Enter your real spend so far for each campaign.
  5. Record results in docs/tracking-qa.md. Commit and tag.

Check your work

  1. Move a consented test lead to qualified: the lead card lists Meta "sent" and Google "sent".
  2. Events Manager shows QualifiedLead from the server; Google returns a request ID and its diagnostics show the event was received.
  3. Move a non-consented lead to qualified: two "skipped: no consent" rows.
  4. Move the first lead away and back: no new rows.
  5. Dashboard: with spend entered, each campaign shows cost per qualified lead, and one figure checked by hand matches.

Common problems

  • Google returns a permission error → the service account has no access to the Ads account, or the API is not enabled in the project. Give Claude the exact error and re-check the access steps.
  • Google rejects the conversion action → wrong ID, a website-type action, or the action is too new. Wait some hours and retry.
  • API setup is out of reach today → use the Google Sheets fallback; ask Claude for a CSV export with the documented columns.

Homework

About 30 minutes, on your own, after the lesson. No later lesson depends on it, and it changes nothing in either ad account.

  1. Your "qualified" rule. Moving a lead to qualified now sends a signal to the ad platforms, so the word needs one meaning. Write two to four conditions a lead must meet in your business, such as area, service and whether you reached them. Deliverable: "What counts as qualified" in docs/ads-plan.md. Done when: applying it to your last five real leads gives a clear yes or no for each.
  2. Pipeline up to date. Go through the real leads from your campaigns and set each to its true stage. Leave test leads alone. Deliverable: no real lead older than two days is still new. Done when: that holds, or you have written "no real leads yet" in docs/tracking-qa.md.
  3. Read the log. Open three lead cards and read the rows under "Reported to ad platforms". Deliverable: a short legend in docs/tracking-qa.md, in your own words. Done when: sent, skipped and failed each have one line saying what it means, and failed also says what you would do.

If a task changed files in the project, commit and push without a tag: git add -A, git commit -m "Homework 7.8", git push.

Save your work

git add -A
git commit -m "Lesson 7.8: CRM outcomes reported to Meta and Google, cost per qualified lead"
git tag lesson-7.8
git push
git push --tags