Send Your Members to a Deal with Member Links
You run a members site for a syndicate (or a landing page for your company's round). A member clicks a deal card, lands on the deal's Wefunder page, and invests there. Wefunder handles the account, KYC and payment. You get the outcome for that member, keyed on your own member id. This guide builds that flow end to end with member links.
What you need
- An app with
read:invite_linksandwrite:invite_links("View / Create investment links") ticked in the developer portal. - That app installed on your syndicate (or company), after
ticking the scopes, and an installation token for the
install. This guide calls it
WEFUNDER_TOKEN. - An open offering on that syndicate or company. Note its id (
ofr_…), for example fromGET /syndicates/{id}/deals.
Keep the token on your server. Every call below runs server-side.
1. Mint a link when the member clicks
Don't mint links ahead of time. When a member clicks Invest on a deal card, your
server asks Wefunder for that member's link on that offering and redirects them to it.
Pass your member id as reference: repeating the same reference on the same offering
returns the live link (200) instead of minting a new one, so you don't need to store
anything to make the click idempotent.
- curl
- Node.js
curl -X POST https://wefunder.com/api/v2/offerings/ofr_2nVb8KxQ4tLm9RcW7pHs3FdY/member_links \
-H "Authorization: Bearer $WEFUNDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"member_link": {"reference": "member_8812"}}'
// POST /invest/:offeringId on your server, for the signed-in member.
export async function memberLinkUrl(offeringId, member) {
const body = { member_link: { reference: member.id } };
const res = await fetch(`https://wefunder.com/api/v2/offerings/${offeringId}/member_links`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WEFUNDER_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
// 201 minted, 200 the member's existing link on this offering.
if (res.status === 201 || res.status === 200) {
const { data } = await res.json();
return data.attributes.url;
}
const { error } = await res.json();
throw new Error(`${res.status} ${error?.type}: ${error?.message}`);
}
// Express: app.post("/invest/:offeringId", async (req, res) => res.redirect(303, await memberLinkUrl(req.params.offeringId, req.user)));
The response's url is the offering's Wefunder deal page with the link's token. Send
the member there.
2. Track what each member did
List your install's links on an offering and read each link's state. The list is
newest first and paginated: follow meta.next_cursor while meta.has_more is true.
curl https://wefunder.com/api/v2/offerings/ofr_2nVb8KxQ4tLm9RcW7pHs3FdY/member_links \
-H "Authorization: Bearer $WEFUNDER_TOKEN"
state | What happened | What to show the member |
|---|---|---|
created | They haven't redeemed it yet | "Invest" |
bound | A Wefunder account holds it (they invested through it, or you pre-bound it) | "Continue on Wefunder" |
invested | That account invested in this offering after you minted the link; investment_id is set | "Invested" |
canceled | You canceled it before any investment | Mint a fresh link on the next click |
Match rows to your members by reference. Responses carry Wefunder ids only, never
the member's email. Webhooks for these changes aren't available yet, so poll, for
example when the member opens your deals page or on a schedule.
3. Bind repeat members up front
The first time a member invests, their link binds to whichever Wefunder account they
used, and wefunder_user_id shows up on the link. Save it against your member. On
their next deals, pass it when you mint:
if (member.wefunderUserId) body.member_link.wefunder_user_id = member.wefunderUserId;
The link is then bound before they click, so a copy forwarded to someone else is useless to them.
If you repeat a reference with a different wefunder_user_id than the account its
link is bound to, you get 409 reference_conflict: the link already belongs to that
account.
4. Cancel a link
To withdraw an invitation, cancel the link:
curl -X POST https://wefunder.com/api/v2/member_links/il_8kQm2xVb7nRt4WcY9pLe3HdA/cancel \
-H "Authorization: Bearer $WEFUNDER_TOKEN"
Nobody can redeem it after that, and a checkout started after the cancel is priced at standard terms. An investment already made through it keeps its terms. A link nobody invested through is also released from any account you pre-bound it to. Uninstalling your app does the same for every link nobody invested through.
What your members see
- Private rounds and invited pricing. A member link carries the invitation of the syndicate's manager or the company's founder, so it opens a private round's deal page and prices the investment as invited.
- The wrong account. A bound link answers only to its account. Anyone else who
opens it, logged out or signed in to a different account, gets nothing from it and
sees a prompt to sign in with the invited account, with a masked hint of the address
(like
[email protected]). If your member has two Wefunder accounts, that hint tells them which one to use.
Troubleshooting
| Response | Cause | Fix |
|---|---|---|
404 on create | The token isn't an installation token, the install doesn't cover this offering's syndicate or company, or the offering id is wrong | Use the installation token for the install on the offering's syndicate or company |
404 on create with wefunder_user_id | No Wefunder account with that id | Pass the usr_ id exactly as a bound link returned it |
403 | Your app isn't approved yet and the installer doesn't manage your app's organization | Use it on your own syndicate or company, or request approval in the portal |
422 offering_not_open | The offering isn't open for investment yet, or has closed | Mint once it opens |
422 offering_has_no_page | The company has no Wefunder page to link to | Publish the company page |
409 reference_conflict | The reference's live link is bound to a different account | Use the wefunder_user_id the link already has, or a different reference |
To test in the sandbox, use your realm syndicate's open deal; see the sandbox walkthrough.