Charitable Documentation

Learn how to make the most of Charitable with clear, step-by-step instructions.

How Recruit Attribution Works in Charitable Ambassadors | Last-Click vs First-Click

Requires: Charitable Pro 1.8.16+
Charitable Ambassadors 3.0.0+

When Sarah shares her invite link and Marcus signs up to fundraise, you want Sarah to get credit. Attribution is what makes that happen automatically – without Sarah having to send anyone a code, without Marcus having to remember to mention her, and without you having to keep a spreadsheet of who recruited whom.

This page explains how Charitable Ambassadors makes that connection – first in plain English, then with the full technical details at the bottom for developers.

The Short Version

When someone clicks an invite link, a tiny piece of data (a “cookie”) gets stored in their browser. It tells your site, in effect: “this person came from Sarah.”

The cookie sits there quietly for up to 30 days. If, during that window, they go on to submit a fundraiser – whether they do it right after clicking the link, or three weeks later from a different page on your site – the system reads the cookie, sees they came from Sarah, and credits Sarah with the recruit.

That’s the whole idea. The rest of this page is just the details.

The Journey, Step by Step

1. Sarah shares her link    >    2. Marcus clicks    >    3. Cookie stored
                                                                ↓
6. You approve, Sarah gets credit  ←  5. Marcus submits  ←  4. Marcus browses your site

1. Sarah shares her link

From her My Campaigns page, Sarah copies her personal invite URL and shares it however she wants – email, text, social, in person. Her URL looks something like:

https://yoursite.com/?charitable-invite=jA4HZIx2AhBnouMN

The ?charitable-invite=… part at the end is a unique token that identifies her specifically (and, if she used a per-campaign Recruit button, the specific cause she’s recruiting for).

2. Marcus clicks

Marcus’s browser fetches that URL. Before WordPress renders anything, Ambassadors’ URL handler intercepts the request, looks up the token, and confirms it belongs to a real, active inviter (Sarah).

3. The cookie is stored

The system drops a small cookie in Marcus’s browser:

PropertyValue
Namecharitable_invite_token
What’s in itSarah’s token (the same string from the URL)
How long it lasts30 days
Where it’s visibleOnly on your site, not other sites

That cookie is the breadcrumb. It’s how your site will remember that Marcus came from Sarah, even if he closes the tab and comes back later from a completely different page.

After dropping the cookie, the system redirects Marcus to your invite landing page – the page you configured under Charitable > Ambassadors > Invitations > Landing Page. The URL bar updates so the ?charitable-invite=… parameter is no longer visible (it doesn’t need to be – the cookie’s there now).

4. Marcus browses your site

This is where attribution gets powerful. Marcus might submit his fundraiser right now, or he might:

  • Read your “About us” page first
  • Check out the cause campaign in detail
  • Bookmark the site and come back tomorrow
  • Forget for a week, then suddenly remember and search for your site again

Any of those still work, as long as it happens within 30 days and on the same browser. The cookie waits.

5. Marcus submits his fundraiser

Eventually Marcus clicks “Start a fundraiser” (or whatever button leads to your submission form), fills it out, and submits. At the moment WordPress saves his new fundraiser as a draft, Ambassadors’ attribution handler runs:

  1. It reads the charitable_invite_token cookie from Marcus’s browser.
  2. It looks up the token to find the inviter (Sarah).
  3. It stamps the new fundraiser with two pieces of metadata: “invited by user ID = Sarah” and “via token = T123.”
  4. It bumps Sarah’s token’s claim_count by 1.

That stamp is what powers every “recruit” feature in Charitable Ambassadors.

6. You approve, Sarah gets credit

When you approve Marcus’s fundraiser (transitioning it to published – or if you’re on auto-approval, it publishes immediately), three things happen because of that stamp:

  • Marcus’s fundraiser shows up in Sarah’s “Your Recruits” view on her My Campaigns page.
  • Marcus’s recruit counts toward the Recruitment widget on your Overview dashboard.
  • Sarah gets a congratulatory email (“Your recruit’s fundraiser was approved!”) – if you’ve enabled the Email Inviter On Approval setting.

Last-Click vs First-Click – What Happens When Someone Clicks Multiple Links?

Imagine Marcus has been to your site twice:

  • Day 1: clicks Sarah’s invite link, reads your site, doesn’t sign up.
  • Day 15: sees James’s invite link in a Facebook post, clicks it, signs up that same day.

Who gets credit – Sarah or James?

The default answer is last-click: James gets credit, because his link is the one that converted Marcus from “browsing” to “fundraising.” This is the standard model in referral systems and the most common choice for peer-to-peer programs.

But some organizations prefer first-click attribution: Sarah gets credit because she’s the one who introduced Marcus to your cause. Even though James’s nudge was the closer, Sarah did the harder work of bringing someone unfamiliar into your orbit.

Switching to first-click is a single line of code (see the Developer reference below). The tradeoff is just philosophical – there’s no right answer.

Edge Cases Worth Knowing About

A few situations come up regularly. Here’s what the system does in each:

SituationWhat happens
Sarah clicks her own link and tries to sign upSelf-recruit guard. The landing page shows a special “you can’t recruit yourself” variant. Even if Sarah somehow gets to the submission form, the attribution step skips her and logs the attempt.
Sarah’s account is deleted between the click and the approvalMarcus’s fundraiser still has the recruit-stamp meta, so the Overview widget still counts him as a recruit – but the inviter shows as “(deleted user)” and the congratulatory email isn’t sent.
Sarah revokes her token while Marcus is mid-signupAttribution is silently skipped. Marcus’s fundraiser is created normally; it just lands without a recruiter credit.
Marcus’s 30-day cookie expires before he submitsThe submission is unattributed. If Marcus clicks Sarah’s link again before submitting, the cookie is renewed and attribution works.
Marcus clicks the link on his phone but signs up on his laptopThe cookie is per-device. Without browser sync (e.g. Chrome sync), the laptop submission lands unattributed.
Two clicks of the same link, same browserThe token’s view_count goes up by 1; the cookie’s expiry resets to 30 days from the latest click. Nothing else changes.
A caching plugin is serving the landing page from cacheThe system emits no-cache headers on the redirect step, and tells Pro’s cache framework to skip the landing page. If your cache plugin still caches it, the cookie may not get set – you’ll see a self-check notice on the Invitations admin tab.

Where Attribution Shows Up in Your Admin

Once a recruit has been attributed, you’ll see them in these places:

  • Overview > Recruitment widget – counts toward Total / Approved / Pending / Rejected, and to the Recruitment time-series chart.
  • Overview > Top Recruiters widget – your leaderboard of who’s bringing in the most recruits.
  • My Campaigns > Your Recruits (front-end, for the inviter) – Sarah sees Marcus in her list.
  • Invitations > Top Recruiters CSV export – exports the full leaderboard for the active date range.
  • Invitations > Recent Activity CSV export – chronological log of every recruit event.

Where to Look When Something Seems Wrong

If a recruit doesn’t show up where you expect, check Charitable Tools > Log. Every attribution event writes an entry there:

Log codeWhat it means
invite_clickedA valid invite URL was clicked. Confirms the click reached your site.
invite_claimedA submission was successfully attributed to an inviter.
self_recruit_skippedAn inviter tried to recruit themselves. Attribution skipped.
attribution_skipped_revoked_tokenThe token was revoked between click and submit.
inviter_deleted_at_approvalApproval email skipped because the inviter’s user no longer exists.

Filter the log by source: ambassadors_invites to see only invite-related entries.


Developer Reference

The rest of this page is for developers customizing the attribution system.

The cookie

Name:      charitable_invite_token
Value:     The 16-character base62 token string
Lifetime:  30 days (filterable via charitable_ambassadors_invite_cookie_lifetime)
Path:      /
SameSite:  Lax
Secure:    true when is_ssl(), otherwise false
HttpOnly:  false (intentional - may be read by frontend analytics)

The cookie is set with WP’s setcookie() directly, not via JS, so it’s available on the very next request.

The URL handler

Charitable_Ambassadors_Invites::handle_invite_url() is hooked into init at priority 1. It:

  1. Returns early if $_GET['charitable-invite'] is empty.
  2. Returns early if is_admin() (admin requests don’t trigger attribution).
  3. Looks up the token via Charitable_Ambassadors_Invites_Tokens::lookup_by_token().
  4. Returns early if the token is missing, revoked, or belongs to a deleted user.
  5. Sets the cookie via setcookie().
  6. Calls Charitable_Ambassadors_Invites_Tokens::increment_view( $token_id ).
  7. Emits charitable_nocache_headers() (Pro 1.8.15.2+) or nocache_headers() (WP core fallback).
  8. Resolves the landing page via charitable_ambassadors_get_invites_setting( 'landing_page_id' ) and builds a redirect URL with charitable-invite stripped.
  9. wp_safe_redirect( $landing_url, 302 ) + exit.

The 302 status code is deliberate so that cache layers don’t memoize the redirect itself – only the destination page, which is dynamic by charitable_is_dynamic_page.

The attribution handler

Charitable_Ambassadors_Invites::on_campaign_submission_save() is hooked into Pro’s charitable_campaign_submission_save action. Signature:

do_action( 'charitable_campaign_submission_save', $data, $campaign_id, $user_id, $form );

The handler is signature-adaptive because the verification harness invokes it with a legacy 2-arg ( $fundraiser_id, $user_id ) shape; in production it always receives the 4-arg shape. The handler:

  1. Resolves the cookie value ($_COOKIE['charitable_invite_token']).
  2. Returns early if no cookie.
  3. Looks up the token; returns early on miss or revoked status.
  4. Self-recruit guard: returns early if $token_row->inviter_user_id === (int) $user_id, logs self_recruit_skipped.
  5. Writes the two attribution meta keys:
    update_post_meta( $campaign_id, '_charitable_ambassadors_invited_by_user_id', (int) $token_row->inviter_user_id );
    update_post_meta( $campaign_id, '_charitable_ambassadors_invited_via_token_id', (int) $token_row->token_id );
    
  6. Calls Charitable_Ambassadors_Invites_Tokens::increment_claim( $token_id ).
  7. Fires do_action( 'charitable_ambassadors_invite_claimed', $token_row, $campaign_id, $inviter_user_id ).
  8. Logs invite_claimed to Charitable Tools > Log.

The two post-meta keys

These are the source of truth for everything downstream:

Meta keyTypeUsed by
_charitable_ambassadors_invited_by_user_idint (WP user ID)Recruitment widget, Your Recruits view, inviter-on-approval email gate.
_charitable_ambassadors_invited_via_token_idint (token_id PK)Token-level analytics. Lets you trace a recruit back to a specific scoped URL.

These are never removed by the plugin – even if the inviter is deleted, the meta sticks (you’ll see “(deleted user)” in Top Recruiters). To clear attribution for a specific recruit, delete the post_meta entries directly:

delete_post_meta( $campaign_id, '_charitable_ambassadors_invited_by_user_id' );
delete_post_meta( $campaign_id, '_charitable_ambassadors_invited_via_token_id' );

Switching attribution mode

Add to your theme’s functions.php or a site-specific plugin:

add_filter( 'charitable_ambassadors_invite_attribution_mode', function () {
    return 'first_click'; // default is 'last_click'
} );

Under first_click, the URL handler still updates the cookie on every click (so view counts are accurate per inviter), but only sets the cookie value if no existing cookie is present. Once a cookie is set, subsequent clicks update its expiry but not its value.

Under last_click (default), every click sets a new cookie value, replacing any prior inviter.

Filters

FilterDefaultPurpose
charitable_ambassadors_invite_attribution_mode'last_click'Switch to 'first_click'.
charitable_ambassadors_invite_cookie_lifetime30 * DAY_IN_SECONDSCookie lifetime in seconds.
charitable_ambassadors_invite_cookie_samesite'Lax'SameSite cookie attribute. Use 'Strict' if your invite URLs are only ever clicked from links on your own domain.
charitable_ambassadors_invite_self_recruit_allowedfalseSet true to disable the self-recruit guard. Not recommended.

Actions

ActionArgsFires when
charitable_ambassadors_invite_url_resolved$token_row, $requestAfter URL handler validates the token, before the cookie is set. Use to short-circuit (e.g. ban specific tokens).
charitable_ambassadors_invite_clicked$token_row, $requestAfter the cookie has been set.
charitable_ambassadors_invite_claimed$token_row, $fundraiser_id, $inviter_user_idSuccessful attribution.
charitable_ambassadors_invite_self_recruit_skipped$token_row, $fundraiser_idSelf-recruit guard blocked attribution.
charitable_ambassadors_invite_attribution_skipped$token_row, $reason, $fundraiser_idCatch-all for any non-success attribution outcome. $reason is one of 'revoked_token', 'self_recruit', 'no_cookie', 'deleted_inviter'.

Logging

Every attribution event is logged to Charitable Tools > Log via charitable_log():

charitable_log( $code, $context, [
    'type'    => 'addon',
    'source'  => 'ambassadors_invites',
    'level'   => 'info', // or 'warning' for skip cases
    'user_id' => get_current_user_id(),
] );

Log codes (the $code argument):

CodeLevelWhen
invite_clickedinfoURL handler resolved a valid token.
invite_claimedinfoSubmission successfully attributed.
self_recruit_skippedwarningSelf-recruit guard fired.
attribution_skipped_revoked_tokenwarningToken was revoked between click and submit.
inviter_deleted_at_approvalwarningApproval email skipped because inviter’s user is gone.
unconfigured_landing_page_admin_viewinfoAdmin viewed the Invitations tab while no landing page was configured.

Caching

The landing page is dynamic (per-user, per-token). The URL handler emits no-cache headers on every invite click, and Pro 1.8.15.2’s charitable_is_dynamic_page filter is set so cache plugins skip the landing page entirely:

add_filter( 'charitable_is_dynamic_page', function ( $is_dynamic, $post_id ) {
    if ( charitable_ambassadors_is_invite_landing_page( $post_id ) ) {
        return true;
    }
    return $is_dynamic;
}, 10, 2 );

If you’re on a Pro version older than 1.8.15.2 (no charitable_is_dynamic_page filter), the fallback nocache_headers() call covers the redirect step but won’t help if a caching plugin caches the landing page directly. The Invitations self-check will warn you about this state.

The tokens table

See How invitation data is stored for the full schema. The columns most relevant to attribution:

ColumnTypePurpose
token_idBIGINT PKThe integer stamped on the recruit’s fundraiser as _charitable_ambassadors_invited_via_token_id.
tokenVARCHAR(32)The string that appears in the URL and the cookie.
inviter_user_idBIGINTThe WP user ID of the inviter.
campaign_idBIGINT NULLWhen NOT NULL, the recruit’s submission auto-attaches to this parent campaign.
statusVARCHAR(20)'active' or 'revoked'. Revoked tokens skip attribution.
claim_countINTBumped by 1 every successful attribution.

Related

Helpful Links

🤝 Get help when you need it

Connect with Customer Support →  

📑 Find the guide you need

Browse the Documentation Hub →  

⬇️ Download proven strategies, campaign ideas, and expert tools
Get the Fundraising Kit →  

💸 Get Free Fundraising Resources
Head to the Charitable Fundraising Hub

🤔 Got questions about Charitable?
Charitable FAQs

Need help understanding non-profit terms and jargon?
See our Non-Profit Glossary

Still have questions? We’re here to help!

Last Modified:

What's New In Charitable

View The Latest Updates
🔔 Subscribe to get our latest updates
📧 Subscribe to Emails

Email Subscription

Join our Newsletter

We won’t spam you. We only send an email when we think it will genuinely help you. Unsubscribe at any time!

automation update

⚡ Visual Automation Builder: Drag and Drop With No Code!

Charitable Automation Connect 2.3.0 introduces the Visual Automation Builder, a full-screen canvas that lays each automation out as a flow of connected cards: a trigger, optional conditions, and a list of actions that run in order.

🧩 Many actions, one trigger: Tag a donor, send an email, add a note, and fire a webhook from a single event, dragged into any order.

✉️ Act inside Charitable: New Send Email, Tag Donor, and Add Donor Note actions run with no external service required.

🔤 Merge tags: Personalize emails and notes with live fields like {first_name}, {total}, and {campaign_name}.

🔁 Apply to existing donors: Run Tag Donor and Add Donor Note against the donors you already have.

🖥️ Canvas or Simple: Switch views anytime, and automations built before 2.3.0 keep working unchanged.

Read more here.

Integration updated

📬 Introducing Brevo for Charitable: Turn Donors into Subscribers Automatically

The moment a supporter makes a gift is when they are most engaged. With the new Brevo integration for Charitable, you can automatically turn those one-time donors into long-term subscribers without touching a single spreadsheet.

Simply collect donor consent right on your donation form and start your welcome series immediately.

What’s New:

🔄 Automated Subscriber Sync: New donors who opt in are added straight to your Brevo contact list as soon as their payment clears—no manual exports or CSV imports required.

🎯 Granular Consent & Opt-In Control: Customize your checkbox label, choose whether it defaults to checked or unchecked, or turn on Brevo double opt-in to keep your list clean and compliant.

📋 Per-Campaign List Mapping: Route supporters to your global email list or map specific campaigns to targeted Brevo lists to tailor your follow-up messaging.

⚡ 5-Minute Setup: Connect instantly by pasting your Brevo API key into the Newsletter settings, map your contact fields, and start building your email list on autopilot.

Ready to grow your mailing list? Brevo is available now starting on the Charitable Plus plan—connect your account today!

recurring donations updated

💳 Introducing Card Updates: Fix Expired Cards Without Losing Subscriptions!

newExpired or updated credit cards are one of the biggest silent leaks in recurring fundraising. With Card Updates in the Recurring Donations extension, donors can now refresh their payment details directly—keeping their subscription, schedule, and giving history completely intact.

No canceled plans, no lost history, and zero administrative headache for your team.

What’s New:

⚡ 30-Second Self-Service: Donors get a dedicated “Update Card” button in their dashboard that opens Stripe’s secure, PCI-compliant Customer Portal to update card details instantly.

🔒 Scoped & Safe Access: Scoped exclusively to card updates by default, donors can’t accidentally cancel or alter their plans from inside the portal, keeping your webhooks and data in sync.

🤝 Admin-Assisted Support: Helping a donor on the phone? Open their secure Stripe portal in one click from your admin screen or generate a single-use update link to email them.

📋 Automatic Audit Trail: Every payment method update is recorded automatically with a timestamp in both system-wide logs and the individual donor’s profile.

Ready to protect your recurring revenue? Get the Plus or Pro plan and update Recurring Donations to 2.3.0+ and enable “Update Payment Method” under your Settings today!

Integration page builder

Divi Fans Rejoice! Native Divi 5 Campaign Progress Bar Module!

With our new native Divi 5 module, you can anchor your microsites with real-time fundraising stats directly on the visual canvas. Here’s how it works, and why it’s worth turning on today.

Create campaign updates that are VISUAL AND LIVE. You can also:

📊 Campaign Progress Bar: Drop a live progress bar into any Divi 5 layout and show goal progress in real time.

🎨 Deep styling controls: Easily customize the bar and track color, height, and radius to match your brand perfectly.

👁️ Visual Builder ready: Configure and preview everything directly on the Divi canvas as a first-class module.

🔁 Identical rendering: The same exact engine powers this module, meaning consistent design without legacy shims.

✅ Faster launches: Never leave the Divi 5 interface to configure shortcodes or guess how your goal labels will look.

Learn more here.

Integration page builder

👉🏻 New in Charitable: Native Elementor Widgets for Seamless Campaign Building

With native Elementor widgets, you design donation campaigns right alongside the rest of your page without touching code. Here’s how it works, and why it’s worth turning on today.

Create fundraising pages that are VISUAL, NATIVE, AND SHORTCODE-FREE. You can also:

⚡ Mini Donation: Add a compact, high-converting donation widget with preset amounts and full color control.

⏳ Campaign Countdown: Build urgency for a deadline-driven appeal, complete with optional confetti when the goal is hit.

📣 Donation Feed: Prove momentum by showing visitors the social proof of real people giving right now.

🏆 Donor Leaderboard: Celebrate top supporters with gold, silver, and bronze styling to spark friendly giving.

🖼️ Campaign Showcase: Feature multiple campaigns in a landing page grid or carousel, with search, filters, and badges.

Learn more here.