100% Attribution Framework
How every dollar Life Without Debt Ltd spends on marketing is tied to a source, and how every donation received is traced back to the asset that produced it. Server-side by design, cookieless-tolerant, GDPR/Privacy-Act clean, and audit-grade for Board reporting.
Contents
- Why attribution matters more for LWD than for a commercial business
- The seven attribution principles
- The four-layer attribution stack
- UTM taxonomy — the naming convention
- The event dictionary — every action we track
- Server-side conversions — Meta CAPI, Google EC, LinkedIn CAPI
- Offline & manual gift attribution
- The four dashboards
- Privacy, consent, and the Australian Privacy Act
- Implementation checklist & go-live gates
1. Why attribution matters more for LWD than for a commercial business
A commercial business measures marketing attribution to grow. A charity like Life Without Debt Ltd measures marketing attribution to survive — and to earn the right to keep raising money. Three reasons:
2. The seven attribution principles
- First-party first, always. Every conversion event is captured on our own servers before it is sent (server-side) to any ad platform. The source of truth lives in Cloudflare D1, not in Meta or Google.
- Server-side over browser-side. Meta CAPI, Google Enhanced Conversions, and LinkedIn CAPI all receive server-fired events. Browser pixels are backup only. This survives iOS ATT, cookie deprecation, and ad blockers.
- Deduplicated identity. Every event carries a hashed email + hashed phone + click ID (fbclid / gclid / li_fat_id). Ad platforms match on hashed PII, so 90%+ of conversions match back to the ad exposure, not just the last-click cookie.
- Both last-touch and first-touch stored. Every gift record stores both the UTM of the session that converted and the UTM of the very first visit that started the donor's journey. Multi-touch attribution is derivable at query time; we don't lose data by picking a model too early.
- Offline gifts count too. Cheques, bank transfers, foundation grants, corporate matched-giving cheques — all entered into the CRM with source metadata. Nothing is "unattributed".
- Consent-first, cookieless-ready. We collect the minimum data required, we tell people we're collecting it, we honour Do-Not-Track, and the whole stack works even if a donor has all cookies disabled.
- Attribution outlives the tool. If Meta CAPI shuts tomorrow, we still know which gift came from which campaign — because the data lives in our D1, not Meta's servers.
3. The four-layer attribution stack
Each layer feeds the next. Layer 1 is the source of truth. Layers 2–4 are where the data comes from. A donor gift is only "fully attributed" when it has data in all four layers where applicable.
4. UTM taxonomy — the naming convention
Every link we distribute — every ad, every email button, every social post, every QR code — carries the same five-parameter UTM string. Consistency is the whole point: sloppy UTMs make attribution unqueryable. This is the contract.
The five parameters
| Parameter | What it means | Vocabulary (locked) |
|---|---|---|
utm_source |
The platform / property that carried the link | meta · google · linkedin · youtube · bing · email · sms · lisa-linkedin · laurence-linkedin · carla-linkedin · lwd-linkedin · lwd-instagram · lwd-facebook · direct · referral · partner · qr · print |
utm_medium |
The nature of the placement | cpc (paid search) · social-paid · social-organic · display · video · email-nurture · email-ask · email-steward · referral · affiliate · direct-mail · pr |
utm_campaign |
The campaign it belongs to (matches Layer 2 briefs) | {room}-{month}-{campaign-slug}. Examples: c-2026-11-christmas-appeal · a-2026-09-launch-outreach · b-2027-02-bank-paradox · c-2027-06-eofy-final |
utm_content |
The specific creative variant | Free text, kebab-case. Examples: margaret-hero-image-01 · 19-25-anchor-headline-a · bank-paradox-carousel-v2 · peace-piece-of-mind-video-30s |
utm_term |
Keyword (paid search) or audience (paid social) | Paid search: keyword. Paid social: audience name. Examples: debt-help-terminal-illness · audience-carers-45-70 · lookalike-donors-1pct |
Full-URL examples
Meta ad, Christmas Appeal, Margaret hero creative, targeting Australian carers 45–70:
https://www.cosailifewithoutdebt.org/donate/monthly
?utm_source=meta
&utm_medium=social-paid
&utm_campaign=c-2026-11-christmas-appeal
&utm_content=margaret-hero-image-01
&utm_term=audience-carers-45-70
LinkedIn thought-leadership post by Laurence, Bank Paradox campaign:
https://www.cosailifewithoutdebt.org/bank-paradox
?utm_source=laurence-linkedin
&utm_medium=social-organic
&utm_campaign=b-2027-02-bank-paradox
&utm_content=bank-paradox-open-letter-v1
&utm_term=audience-csr-esg-leads
Foundation-outreach email, Paul Ramsay Foundation, September launch:
https://www.cosailifewithoutdebt.org/room-a
?utm_source=email
&utm_medium=email-ask
&utm_campaign=a-2026-09-launch-outreach
&utm_content=paul-ramsay-discovery-invite
&utm_term=foundation-paul-ramsay
scripts/utm-lint.mjs before any asset is approved. A broken UTM = a broken attribution chain = a Board reporting gap. This is not a suggestion.
5. The event dictionary — every action we track
Fifteen events. Every one has the same shape. Every one is fired both browser-side (GA4) and server-side (Cloudflare Worker → CAPI + Enhanced Conversions + D1). No custom off-schedule events.
| Event name | Trigger | Params captured | Fired to | Attribution weight |
|---|---|---|---|---|
page_view | Any page load | page_path, page_title, utm_*, referrer | GA4 only | Volume |
scroll_50 | 50% of page scrolled | page_path, session_id | GA4 only | Engagement |
video_watch_25 | 25% of a video watched | video_id, page_path | GA4 · Meta CAPI | Engagement |
video_watch_75 | 75% of a video watched | video_id, page_path | GA4 · Meta CAPI | Strong engagement |
outbound_click | Click to external site | destination_url, link_text | GA4 only | Engagement |
email_signup | Newsletter form submit | hashed_email, utm_*, source_page | GA4 · Meta CAPI · Google EC | Micro-conversion |
donate_start | Click on any donate button | amount_default, page_path, utm_* | GA4 · Meta CAPI | Intent |
donate_amount_selected | Amount chosen on donate form | amount, frequency, page_path | GA4 | Intent |
donate_details_entered | Email + name entered | hashed_email, utm_* | GA4 · Meta CAPI · Google EC | High intent |
donate_complete | Payment successful | gift_id, amount, frequency, hashed_email, hashed_phone, utm_first, utm_last, fbclid, gclid | GA4 · Meta CAPI · Google EC · LinkedIn CAPI · D1 | PRIMARY CONVERSION |
donate_upgrade | Existing donor increases regular gift | gift_id, delta_amount, hashed_email, utm_* | GA4 · Meta CAPI · D1 | Retention |
donate_cancel | Regular gift cancelled | gift_id, months_active, reason | GA4 · D1 | Churn |
foundation_enquiry | Foundation discovery-call form | foundation_name, contact_email, utm_* | GA4 · LinkedIn CAPI · D1 | Room A conversion |
corporate_enquiry | Room B pitch-form submit | company, role, utm_* | GA4 · LinkedIn CAPI · D1 | Room B conversion |
referrer_enquiry | Health-referrer form submit | organisation, contact_role, utm_* | GA4 · D1 | Room F conversion |
event_id UUID. Both the browser (GA4) and the server (Meta CAPI etc.) fire the same event with the same event_id. Ad platforms deduplicate on that ID — so we don't double-count when both browser and server events fire, and we don't lose attribution when only the server fires (ad-blocker case).
6. Server-side conversions — Meta CAPI, Google EC, LinkedIn CAPI
The core mechanic. When a donor completes a gift on /donate, a Cloudflare Worker fires four parallel events. The browser pixel fires a fifth as backup.
The Cloudflare Worker payload (Meta CAPI example)
// POST https://graph.facebook.com/v18.0/{PIXEL_ID}/events
{
"data": [{
"event_name": "Donate",
"event_time": 1735689600,
"event_id": "gift-9f3a7b21-2027-06-15", // dedup key
"event_source_url": "https://www.cosailifewithoutdebt.org/donate/monthly",
"action_source": "website",
"user_data": {
"em": ["e3b0c44...hashed_email_sha256..."],
"ph": ["8d969ee...hashed_phone_sha256..."],
"fbc": "fb.1.1735689500.IwAR2abc123...", // fbclid captured on landing
"fbp": "fb.1.1735689500.987654321", // 1st-party fb browser cookie
"client_ip_address": "203.0.113.42",
"client_user_agent": "Mozilla/5.0..."
},
"custom_data": {
"currency": "AUD",
"value": 19.25,
"content_name": "Regular Giving — $19.25 weekly",
"content_category": "monthly-recurring",
"predicted_ltv": 462.00, // $19.25 × 24 months
"campaign": "c-2026-11-christmas-appeal",
"creative": "margaret-hero-image-01"
}
}],
"test_event_code": null,
"access_token": "{META_CAPI_TOKEN_SECRET}"
}
googleads.googleapis.com with the hashed email as the user identifier. Google matches against signed-in Google accounts and attributes back to the exact ad click (via gclid). Recovers 30–50% of conversions that browser pixels would otherwise lose.
corporate_enquiry or foundation_enquiry event completes. Uses li_fat_id plus hashed work-email where available. Enables LinkedIn's Matched Audiences for retargeting warm corporate leads.
wrangler secret put) — never in code, never in a repo, never in the browser. Rotated quarterly.
7. Offline & manual gift attribution
Not every gift comes through the donate flow. Foundation cheques arrive by post. Corporate matched-giving comes as a bank transfer with a reference. A donor calls Lisa and pledges. All of these still get attributed — the CRM operator enters the source at gift-entry time.
| Gift channel | How attribution is captured | Uploaded back to ad platforms? |
|---|---|---|
| Foundation cheque | Manual CRM entry. Fields: foundation name, program, application ID, first-touch UTM (from the discovery-call form), last-touch (in-person / phone / email). | No (not applicable — foundations don't come via ads) |
| Corporate matched-giving cheque | Manual CRM entry. Fields: company, contact, deal ID, pitch-deck version, LinkedIn campaign that seeded the conversation. | Yes — hashed contact email → LinkedIn CAPI Offline Conversions API. Enables closed-loop LinkedIn ROAS. |
| Phone donation | Recorded in CRM at call end. Ask "how did you hear about us?" — pre-defined dropdown mapped to UTM sources. | Where hashed email/phone available → Meta CAPI + Google EC as offline conversion. |
| Event / in-person donation | QR code on all print collateral carries UTM. Even at events, gifts flow through the digital funnel and land in D1 naturally. | Yes — QR-carried UTMs already fire the standard event chain. |
| Bequest pledge | Manual CRM entry. Fields: solicitor (if referred), Wills Week campaign (if applicable), first-touch UTM from donor's original engagement. | No (long-lag, no meaningful ad-platform loopback) |
8. The four dashboards
Attribution is only useful if humans can read it. Four dashboards, four audiences, four cadences.
| Dashboard | Contents | Access |
|---|---|---|
| D1 — Operations (daily) | Live yesterday's spend, revenue, gifts count, cost per acquisition, ROAS-to-date, top-performing creative, worst-performing creative, budget-pace-to-plan, technical health (CAPI match rate, pixel firing rate, form abandonment). Owned by Carla. | Intranet · /dashboard-ops |
| D2 — Fundraising (weekly) | New regular givers this week, one-off gifts, list growth, email engagement, room-by-room progress against monthly plan, upcoming ask calendar, stewardship queue. Owned by Lisa. | Intranet · /dashboard-fundraising |
| D3 — Board (monthly) | Revenue vs. plan by source (foundations / regular / corporate / other), CAC by channel, retention cohort curves, room-by-room 12-month arc, RG 96 / compliance flags, financial-controls report (fundraising ratio). Owned by Carla, reviewed by the full board. | Intranet · /dashboard-board |
| D4 — Public transparency (quarterly) | Total raised, total spent on service delivery, cost per case closed, illustrative case outcomes (labelled composites until real cases exist), evaluation partner updates. Public-facing, published to /impact. |
Public · /impact |
9. Privacy, consent, and the Australian Privacy Act
Because LWD's audience includes vulnerable people — families in medical and financial crisis — the privacy bar is higher than a commercial business's. Three specific commitments:
/privacy. Donors can withdraw consent at /privacy/withdraw — a single form that stops all future contact and asks whether existing gifts should also be de-identified in reporting.
/privacy, reviewed annually.
10. Implementation checklist & go-live gates
What must be true, in order, before the September launch. Each item has a named owner and a deadline.
| # | Gate | Owner | Latest date | Status |
|---|---|---|---|---|
| 1 | Cloudflare D1 donors, gifts, sessions, events tables migrated to production | Claude / Carla | 15 Aug 2026 | Migration ready |
| 2 | Donate flow shipped (/donate + /donate/monthly + confirmation + webhook) | Claude | 20 Aug 2026 | Layer 3 build |
| 3 | Stripe payment integration (recurring + one-off, PCI-DSS scoped) | Carla + Claude | 25 Aug 2026 | Pending Stripe account |
| 4 | UTM lint script + link-generation helper baked into every asset publisher | Claude | 25 Aug 2026 | Layer 3 |
| 5 | GA4 property created, Measurement Protocol API secret in Cloudflare secrets | Carla | 25 Aug 2026 | Pending Google account |
| 6 | Meta Business Manager + Pixel + CAPI token in Cloudflare secrets | Carla | 28 Aug 2026 | Pending Meta BM |
| 7 | Google Ads account + Enhanced Conversions enabled + conversion IDs mapped | Carla | 28 Aug 2026 | Pending Google Ads |
| 8 | LinkedIn Campaign Manager + Insight Tag + CAPI | Carla | 28 Aug 2026 | Pending LinkedIn |
| 9 | Privacy Policy + Consent Banner + /privacy/withdraw flow live | Claude + Carla legal review | 28 Aug 2026 | Layer 3 |
| 10 | Test transactions pass end-to-end (donor gives $1, all four platforms receive the event, D1 records it, dashboard reflects it) | Carla | 29 Aug 2026 | Blocked on 1–9 |
| 11 | Board approval of attribution framework + privacy policy | Board | 30 Aug 2026 | Agenda item ready |
| 12 | D1–D4 dashboards live and populated | Claude | 31 Aug 2026 | Layer 3 |