Skip to main content

Measure the Investors You Refer

Send people to Wefunder from your newsletter, community, or ad campaign, and find out how many of them invested. You create referral codes with your app's server key, put them on your links, and read back weekly counts.

Nothing here needs a founder to approve you, and nothing here changes what your visitors see on Wefunder. A code records that someone arrived through your link. It grants no access and selects no round.

Not a commission program

Wefunder does not pay partners per referral. You may not be paid per referral, or by any amount that depends on investments made in a securities offering, unless you are a registered broker-dealer. Your organization acknowledges this once before its first code (Step 3).

What you'll build​

  1. An application whose server key holds the referral-code scopes.
  2. A referral code, minted once per campaign, channel, or newsletter issue.
  3. Links that carry the code as ?wfr=ref_….
  4. A weekly job that reads each code's counts.

How it works​

  • The link. Add wfr=<code id> to any wefunder.com link: a company page, Explore, the home page, a direct invest link. The parameter name is exactly wfr, lowercase, with no aliases. Unknown and paused codes are ignored without an error.
  • The cookie. On arrival, Wefunder stores the code in a cookie for 30 days. If the visitor arrives through another code later, the newest code wins (last touch among referral codes).
  • The credit. When that visitor invests within 30 days, the investment is credited to the code, in any company, wherever they first landed. A code created for one company or offering credits only investments in that company.
  • One credit per commitment. A reservation that later converts into an investment, or an investment that is amended, keeps its original credit and is not counted again.

Step 1: Give your app the scopes​

In the developer portal, open your application and tick read:referral_codes and write:referral_codes. These are the only scopes besides read:public that a server key can hold, because the codes belong to your app, not to a user.

A token issued to a user, or an installation token, is refused on every referral-code endpoint with 403 app_credentials_required. Use the Client Credentials grant.

Step 2: Mint a token​

curl -X POST https://api.wefunder.com/oauth/token \
-d grant_type=client_credentials \
-d client_id=$WF_CLIENT_ID \
-d client_secret=$WF_CLIENT_SECRET \
-d "scope=read:referral_codes write:referral_codes"

The token lives for two hours. Cache it and re-mint when it expires; the token endpoint allows 20 mints per hour per client.

Step 3: Accept the referral terms​

The first time your organization creates a code, the API answers:

{
"error": {
"type": "attribution_terms_required",
"message": "Your organization must accept the referral attribution terms before creating or reactivating referral codes.",
"details": { "acceptance_url": "https://wefunder.com/developers/orgs/123#attribution-terms" },
"remediation": "An owner or admin of the organization accepts the terms once in the developer portal."
}
}

An owner or admin of your organization opens acceptance_url, reads the terms, and accepts them. This happens once per organization, not per app or per code. Then retry the request.

Step 4: Create a code​

curl -X POST https://api.wefunder.com/referral_codes \
-H "Authorization: Bearer $WF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "October newsletter",
"utm": { "source": "acme_newsletter", "medium": "email", "campaign": "october" }
}'
{
"data": {
"id": "ref_8f3ExampleReferral0000",
"type": "referral_code",
"attributes": {
"label": "October newsletter",
"active": true,
"target": null,
"utm": { "source": "acme_newsletter", "medium": "email", "campaign": "october" },
"metadata": {},
"link_parameter": "wfr=ref_8f3ExampleReferral0000",
"created_at": "2026-10-13T17:30:00Z",
"updated_at": "2026-10-13T17:30:00Z"
}
}
}

Fields you can send:

FieldNotes
labelRequired. Your name for the code, up to 200 characters.
company_id or offering_idOptional, at most one. Credits only investments in that company (co_…) or that offering's company (ofr_…). The offering must be public. Fixed once the code exists. Leave both out for a code that credits any company.
utmOptional presets: source, medium, campaign, content, term. See UTM presets.
metadataOptional flat object for your own use, up to 4 KB.

Your app may hold 500 active codes. Past that, creating or resuming a code returns 422 quota_exceeded; pause codes you no longer use to free a slot. There is no Idempotency-Key on this endpoint, so a retried request can create a second code. Pause the extra one.

Append link_parameter to any wefunder.com URL:

https://wefunder.com/acme?wfr=ref_8f3ExampleReferral0000
https://wefunder.com/explore?wfr=ref_8f3ExampleReferral0000

Mint one code per thing you want to measure separately: a campaign, a channel, a newsletter issue. Counts are never combined across codes, so a code is the smallest unit you can report on.

UTM presets​

If you also tag your links with UTM parameters, presets save you from building every URL by hand. On a visit that carries the code, each preset fills the matching utm_* field the URL leaves blank. A UTM value on the link itself wins over the preset, field by field. Presets never touch a visit without the code.

This makes your own breakdown and the founder's UTM report agree. Presets are labels: nothing is counted or authorized by them. Each value is at most 100 printable ASCII characters. A source or campaign that starts with wf_ is reserved for Wefunder's own channels and returns 422 reserved_utm_prefix.

Step 6: Read the counts​

curl https://api.wefunder.com/referral_codes/ref_8f3ExampleReferral0000/stats \
-H "Authorization: Bearer $WF_ACCESS_TOKEN"
{
"data": {
"id": "ref_8f3ExampleReferral0000",
"type": "referral_code_stats",
"attributes": {
"label": "October newsletter",
"visits": 412,
"unique_visitors": 388,
"visits_as_of": "2026-10-12",
"companies": [
{
"company": "co_7hQExampleCompany0001",
"level": "counts",
"verification_url": "https://wefunder.com/acme/marketing/reports#partners",
"in_scope_since": "2026-W42",
"weeks": [
{ "week": "2026-W42", "investors": { "min": 3, "max": 5 }, "policy_version": 1, "published_at": "2026-10-19T06:05:00Z" },
{ "week": "2026-W43", "investors": { "min": 0, "max": 2 }, "policy_version": 1, "published_at": "2026-10-26T06:05:00Z" }
]
}
]
}
},
"meta": { "has_more": false, "page_count": 1 }
}

How to read the ranges​

  • One cell per company per week. A week is an ISO week, Monday to Sunday, UTC. A cell counts the unique investors your code referred into that company in that week.
  • Every count is a range, never an exact number. Ranges are currently 3 wide: {0, 2}, {3, 5}, {6, 8}, and so on. Seven investors read { "min": 6, "max": 8 }. The bottom range includes zero, so {0, 2} can mean none.
  • Published once, never revised. A week's cell appears early on the following Monday (UTC) and does not change afterwards, even if an investment is later canceled. The current week has no cell yet.
  • Every week appears. A company's series runs from in_scope_since to the latest published week, with no gaps. A week with no investors reads {0, 2} like any other low week.
  • There are no totals. Not per code, per company, or across weeks, and no amounts. You can add cells up yourself; the sum is a range, and summing weeks counts investor-weeks, since someone who invests in two weeks appears in both.
  • policy_version tells you which range rule produced a cell. The width may change in the future, only from a future week onward; a published cell is never re-released.

Ranges protect investors. If a code went to one person, an exact count would tell you whether that person invested. A range of 3 that includes zero does not.

Which companies appear​

A code made for a company covers that company from the week the code was created. A code with no target covers every company whose page its visitors landed on, from the week of the first such visit. A company never enters the list because someone invested there; only a visit adds it. The response is paginated over companies (cursor, per_page); each company's weekly series is always complete.

visits and unique_visitors count arrivals up to the end of visits_as_of (UTC) and refresh daily. GET /referral_codes and GET /referral_codes/{id} carry the same visit numbers under stats, but never investor counts.

Polling​

Cells change once a week. Read your codes' stats once on Monday after 06:05 UTC and store the result; reading more often returns the same data.

Rows instead of ranges: when a founder installs your app​

The ranges above are what any app gets for its own codes. If a company's founder also installs your app, you see that company's referred investments as rows instead. The level field on each company tells you which you are getting:

levelWhat decides itEach row carries
countsYour app is not installed on the company, or the install has neither scope belowWeekly ranges, as above
anonymizedYour app's installation on the company grants read:attribution:anonymizedOpaque investment and investor tokens, week, amount_tier (under_1k, 1k_to_5k, 5k_to_25k, 25k_and_up), status (active or inactive), time_to_invest (same_day, within_week, within_30_days)
fullThe installation is at founder tier and lists read:attribution:full itselfThe investment id, the investor's name and email, the exact amount, status, clicked_at, applied_at, and the UTM source and campaign when one was recorded

The opaque tokens are stable for your app and that company, and differ across apps and across companies, so they cannot be used to follow one investor between companies.

Each installed company returns up to 100 rows. To page further, request that company alone with company_id=co_… and pass investments_cursor from the previous investments_next_cursor.

The read:attribution:* scopes are granted through Wefunder's attribution program, not the portal picker; contact us to enable them on your app. They take effect only on companies where a founder has installed your app. Installed access applies to that company alone: every other company in the same response keeps its ranges.

Let the founder check your numbers​

A founder can see every partner's referrals into their company, with exact visits, investment counts, and amounts, in the Partners section of their company's reports (Fundraising → Analytics → Reports). Each company in your stats response carries a verification_url that opens that section. The founder sees your organization's name and each code's ref_ id and label, so when you quote a code, they can find it.

The founder's numbers are exact and current, and count only investments that are still active. Your ranges are fixed when each week is published, so the two will not always match exactly.

Managing codes​

TaskRequest
List your codesGET /referral_codes (filter with active=true or active=false; meta.active_limit is your cap)
Look one upGET /referral_codes/{id}
RenamePATCH /referral_codes/{id} with label
Pause or resumePATCH /referral_codes/{id} with active: false or active: true
Change UTM presetsPATCH /referral_codes/{id} with utm (replaces all presets)

A paused code is ignored on new arrivals, but cookies it already set keep crediting investments for up to 30 days. Resuming a code counts against the 500-code cap and needs your organization's accepted terms. The target company or offering cannot be changed; mint a new code instead.

Errors you may see​

Status and typeMeaning
403 app_credentials_requiredYou used a user or installation token. Use a client_credentials token.
403 insufficient_scopeThe token lacks read:referral_codes or write:referral_codes. Tick them on the app and mint a new token.
404 not_foundThe code is not one of your app's, the company_id is not in this code's stats, or the target you sent at creation is not a public company or offering.
422 attribution_terms_requiredYour organization has not accepted the terms. Open details.acceptance_url.
422 quota_exceededYour app already has 500 active codes.
422 reserved_utm_prefixA preset source or campaign starts with wf_.
422 invalid_paramsBoth company_id and offering_id were sent.
422 validation_errorAnother field is invalid; details.errors says which.

See Error Handling for the error envelope.

Webhook events​

Two webhook events cover referrals. referral_cohorts.published arrives every Monday once the new week's cells are ready. investment.referred is sent only through an installation on the investment's company that grants an attribution scope. Without an endpoint, poll the stats endpoint on Mondays.

The legacy Attribution API​

The older Attribution API, which reported UTM matches on a single campaign, was removed on 2026-10-14. Its endpoints answer 410 Gone. Use referral codes instead.