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 to 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 to 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 to 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/withdrawa 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 to 9 |
| 11 | Board approval of attribution framework + privacy policy | Board | 30 Aug 2026 | Agenda item ready |
| 12 | D1 to D4 dashboards live and populated | Claude | 31 Aug 2026 | Layer 3 |