Skip to main content

Security Model

What keeps a token from doing more than it should, and what keeps a test integration from touching real money.

Live and test modes​

Both modes share one host, api.wefunder.com. The mode segment in your credentials decides which environment a request reaches.

LiveTest
Client idpk_live_...pk_test_...
Client secretsk_live_...sk_test_...
Access tokenat_live_...at_test_...
Refresh tokenrt_live_...rt_test_...
DataReal investors, companies, and syndicatesAn isolated sandbox with its own records
MoneyRealNone. No payment is ever taken in test mode
Authorize URLhttps://wefunder.com/oauth/authorizehttps://oauth.wefunder-sandbox.com/oauth/authorize

A pk_test_ request cannot read or write live data, and a pk_live_ request never lands in the sandbox. Test and live credential pairs are created together for every application; the live secret is shown once, the test secret can be re-read in the portal. Details in Sandbox.

Tokens​

Every access token is an at_live_ / at_test_ bearer string, but the API issues five kinds, and they differ in who they act as, how long they live, and how they are revoked. The first three come from the OAuth token endpoint and expire on a timer. The last two are minted from an installation and live as long as the install does.

CredentialHow you get itActs asScope ceilingResource bindingExpiresRenewRevoked by
User tokenAuthorization Code + PKCE at /oauth/authorize and /oauth/tokenThe user who consentedThe scopes the user granted, from those ticked on your app. read:investors:pii is stripped at consent and capped on every request unless the user manages your organization or Wefunder has approved your app for itOptional. resource_type, resource_id and resource_scope=required together on the authorize request confine it to one syndicate. The three travel together or the request is rejected, and resource_scope=optional only records a hint2 hoursgrant_type=refresh_token. Refresh tokens have no timer of their own; they work until revokedThe user, from Settings → Connected apps or DELETE /connected-apps/{app_id}
Syndicate-owned OAuth tokenThe same flow with grant_to=syndicate&syndicate_id=syn_…The syndicateread:syndicates and write:syndicates only; other requested scopes are strippedAlways. Bound required to that syndicate2 hoursgrant_type=refresh_tokenA current manager, from the syndicate's Integrations settings; revoking an install of your app on that syndicate sweeps it too
Publishable keyYour app's client id (pk_live_ / pk_test_), sent as the bearerYour app, with no personread:public onlyNoneNeverNothing to renewDeleting the key or disabling the app
Client-credentials tokengrant_type=client_credentials with your pk_ / sk_ pairYour app, with no personread:public onlyNone24 hoursMint another. No refresh token is issued for this grantDeleting the key or disabling the app. Rotating a secret does not revoke tokens already minted
Portal personal tokenDeveloper portal → Installations → the You row → Mint tokenYou, the developer who minted itYour app's declared scopes, capped the same way as a user tokenNoneNeverMint anotherYou, from Settings → Connected apps or DELETE /connected-apps/{app_id}
Installation tokenPOST /installations, POST /installations/{id}/tokens, Mint token in the portal, or Generate API key in syndicate settingsThe company or syndicate the app is installed onThe install's grant, which never exceeds the app's declared scopes; a mint may narrow it furtherAlways. Bound required to the installed-on company or syndicateNeverMint another from the same installAnyone who could revoke the install: DELETE /installations/{id}, or Settings → Installed apps on the company or syndicate

A token that has expired or been revoked fails with 401 on the next request. Refresh and re-mint on the first 401, not per request; see Rate limiting.

Whose authority a token carries​

The two user-owned kinds act as a person, and the API re-reads that person's roles on every request. Losing a syndicate manager role means a user token loses that syndicate immediately, and a deleted or locked account's tokens stop working at once.

The target-owned kinds (the syndicate-owned OAuth token and installation tokens) do not act as the person who created them, and the API never re-checks that person's roles on the target, so a target-owned credential survives the installing person leaving the company or syndicate. That is the property to want for an integration that belongs to the business rather than to one employee, and the reason Resource Binding recommends grant_to=syndicate for tools a whole team relies on. Where the authority comes from differs between the two:

  • An installation token carries the installation's grant: the tier and scopes recorded when the install was made, frozen at that moment, and evaluated against what that tier may do on the target today.
  • A syndicate-owned OAuth token is owned by the syndicate, and the token exchange creates no installation for it. If your app has an active install on that syndicate, that install's tier and scopes govern the token. If it has none, the token is refused once Wefunder enforces installations for syndicate-owned tokens; while that enforcement is still rolling out, such a token acts with full access to its syndicate within its read:syndicates / write:syndicates scopes. Install your app on the syndicate rather than relying on that fallback.

Role changes and account changes are separate cases. Losing a manager role never disables a target-owned token. But every syndicate-owned token (the OAuth kind and installation tokens on a syndicate alike) still records the person who authorized it, and the API refuses it if that person's Wefunder account is deleted or locked, even though the install stays active. A company-owned token records no acting person and is unaffected.

Permission to mint is a separate question from the credential's authority, and it is checked live. POST /installations/{id}/tokens and the portal's Mint token succeed only for someone who could revoke the install right now (a current editor or manager of the target, or an admin of the organization that owns the installing app) or who currently manages the app. An installer who has since left can no longer mint, but the tokens minted while they had standing keep working until the install is revoked (or, for a syndicate-owned token, until the authorizing account is deleted or locked). The install, not the token, is the durable authorization: revoking it sweeps every token minted from it and every token of the installing app bound to that target. Tokens minted through POST /installations, POST /installations/{id}/tokens or the portal's Mint token, syndicate API keys generated from syndicate settings, and the tokens a refresh produces from any of them are linked to their install record, and a request carrying one is refused the moment the install is revoked, even if the sweep has not caught up; only a syndicate API key or refreshed token issued before that link existed is not linked to the install record, so it depends on the sweep.

A suspended or disabled application is refused on every request, whatever kind of token it presents, and cannot mint new ones.

Revocation​

  • A user can revoke an app from their Wefunder account settings, or through DELETE /connected-apps/{app_id} (write:profile). Every token the app holds for that user, personal tokens included, is revoked at once and the revocation is written to the activity feed.
  • Anyone who can edit the company or manage the syndicate, or an admin of the installing app's organization, can revoke an installation with DELETE /installations/{external_id} or from the target's Installed apps settings. Every installation token minted from it stops working. See Installations.
  • A syndicate manager can revoke an individual syndicate-owned token from the syndicate's Integrations settings.
  • Revoked tokens fail with 401 on the next request.

Authorization is server-side​

The API decides what a token may do; nothing the caller sends changes that. Scopes gate the endpoint, the principal's authority gates the resource (a user's current roles, or an installation's grant), bindings narrow the resource set, and organization roles gate app-management surfaces such as webhook endpoints. The same rules apply whether the caller is a script, a partner integration, Wefunder's own MCP server, or a person in a browser.

Irreversible actions have one more gate: a human must approve an intent before they run.

Webhooks​

Deliveries are signed with HMAC-SHA256 over a timestamp and the raw body, secrets are shown once and rotated with an overlap window, and an endpoint never receives data the same grant could not read from the API. See Handling Webhooks.

Audit​

Every API action on a user's behalf is recorded with the application, the acting user, the action, the resource, the outcome, and the time, and is visible to that user. See Activity & Audit Trail.

Your responsibilities​

  • Keep sk_ secrets and refresh tokens server-side and encrypted at rest. Never ship them to a browser or a mobile binary.
  • Never log full tokens or webhook secrets.
  • Request the minimum scopes and bind tokens to a single syndicate when the integration only needs one.
  • Verify webhook signatures against the raw body and reject stale timestamps.