Sandbox
The sandbox is a separate world, and it is meant to look like one. Each developer
workspace, whether a team organization or your personal one, gets its own realm: a
self-contained copy of Wefunder at an unguessable host under wefunder-sandbox.com,
seeded with a company, an open round, and test users. Nothing in a realm touches live
data, and no live Wefunder login works there.
A team organization gets its realm as soon as it is created. A personal workspace gets one when you create your first application, so its Sandbox tab is empty until then. Create an app and come back; your test client is registered in the realm as part of that step.
Test versus live
Your application has two OAuth clients. The prefix on every identifier tells you which world it belongs to:
| Prefix | World | Where requests go |
|---|---|---|
pk_test_, sk_test_, at_test_, rt_test_ | Sandbox realm | api.wefunder.com routes them to the sandbox cluster |
pk_live_, sk_live_, at_live_, rt_live_ | Live | api.wefunder.com routes them to production |
The API host and the token endpoint are the same in both worlds. Only the authorize URL differs, because it is a browser page:
| World | Authorize URL |
|---|---|
| Live | https://wefunder.com/oauth/authorize |
| Sandbox | https://oauth.wefunder-sandbox.com/oauth/authorize |
A pk_test_ client sent to the live authorize URL gets an error page pointing at the
sandbox one. There is no redirect between the two.
What is in your realm
The Sandbox tab of the developer portal shows your realm's host and its starter data:
- A seeded company with an open SAFE round, reachable at your realm host.
- A syndicate, Sandbox Syndicate, that the founder manages, with one member (the second investor) and three deals: an open SPV deal you can send members to, a closed deal, and an acquired one. The open deal is a 506(b) round, so a test investor can attest accredited and finish checkout in the realm.
- Test logins with their passwords: a founder who edits that company and manages the syndicate, and two investors with a seeded portfolio. Use them to complete the OAuth authorize flow in sandbox, to install your app on the company or the syndicate, and to browse the realm as those users.
- Your
pk_test_client, already registered in the realm.
Because sandbox users are fixtures, the authorize screen in sandbox asks for the seeded login, never your real Wefunder password.
What works in sandbox
The sandbox runs the same API with the same endpoints, scopes, error shapes, and access rules, on your realm's data. That includes installing your app on the realm's company or syndicate and minting the company- or syndicate-owned token the install stands for. The one thing that stays on live is the personal token from the portal's You row; use the sandbox OAuth flow for a test user token instead. The table is the exact line.
| Capability | Sandbox (_test_) | Live (_live_) |
|---|---|---|
Client credentials with your pk_test_ / pk_live_ key: read:public, the realm's (or the live) public catalog | Yes | Yes |
| User OAuth (Authorization Code with PKCE) at the sandbox authorize URL, signing in as the seeded founder or investor. Needs an app with a redirect URI | Yes | Yes |
User-token reads: GET /users/me, the seeded investor's investments, the seeded founder's company and its investments | Yes | Yes |
| The user install your OAuth consent records (shown to the user under Connected apps) | Yes, in your realm | Yes |
Installing on a company or syndicate: the install link, or POST /installations with a user token (which installs and mints in one call) | Yes, on the realm's company or syndicate, signed in as (or with a token for) a seeded login who can edit it. Review is not needed in your own realm | Yes |
Company- and syndicate-owned tokens minted from an install: POST /installations/{id}/tokens | Yes. They are at_test_ tokens and work only in sandbox | Yes |
Revoking an install: DELETE /installations/{id}, or the company's or syndicate's Settings → Installed apps | Yes, in your realm | Yes |
| Personal tokens from the portal's You row | No. Live only; use the sandbox OAuth flow for a test user token | Yes |
Syndicate-owned OAuth token: the authorize request with grant_to=syndicate&syndicate_id=…, see Syndicate-owned tokens | Yes, for a syndicate in your realm that the signed-in test user can edit. As on live, it carries only read:syndicates / write:syndicates and is bound to that syndicate; the seeded founder manages the realm's syndicate | Yes |
| Webhook endpoints: list, fetch, the signed test send, re-enable | Yes, for your "mode": "test" endpoints | Yes |
| Webhook endpoints: create, update, delete, rotate | No. Through the live API, with "mode": "test" for sandbox. See Webhook setup | Yes |
Member links: POST /offerings/{id}/member_links with a syndicate- or company-owned token, the deal page they open, and GET /member_links/{id} | Yes, on the realm syndicate's open deal. There is no webhook for member links in either world: poll GET /member_links/{id}. See Member Links | Yes |
Join sessions: POST /syndicates/{id}/join_sessions and the hosted join page | Yes. The hosted page is on your realm host. join_session.* and syndicate_member.* events go to the syndicate install | Yes (beta) |
Investment sessions: POST /offerings/{id}/investment_sessions and the hosted landing | Yes | Yes (beta) |
| Checkout to a completed investment | Card works, with the Stripe test card 4242 4242 4242 4242. Bank account is not available in sandbox yet. Wire instructions are shown | Yes |
| Deliveries triggered by activity in your realm | To the installs in the realm, delivered to your app-wide "mode": "test" endpoints. investment.* and offering.* events go to installs on the offering's company. An app installed on a syndicate does not receive the investment events for the SPVs that syndicate leads unless it is also installed on the SPV's company. syndicate_member.* events go to installs on the syndicate, and join_session.* events go to the syndicate install that created the session | Yes, per install |
Installing in your realm
The portal's Installations page shows, under each install link, the same link on your
realm host. Open it, sign in with the seeded founder from the Sandbox tab, and consent;
or call POST /installations with a test user token from the sandbox OAuth flow. Either
way the install lives in your realm: it never appears in the portal's live list, and you
see and revoke it under the company's Settings → Installed apps in the realm. Tokens
minted from it are at_test_ tokens, refused by live, and a live install's tokens are
refused by the sandbox. Investor PII (read:investors:pii) follows the live rule: a
sandbox install carries it only once Wefunder has approved your app for it.
What is different
- No money moves. Payment, identity, and signature gates that need a real person are driven by simulation tooling in the realm rather than by real providers.
- Realm data is yours alone. Other developers cannot see it, and it is not a copy of production.
Walkthroughs
Each walkthrough starts from a fresh realm and uses your pk_test_ client. Logins and
passwords are on the portal's Sandbox tab. Ids below are placeholders: use the ones
your realm returns.
Sign in a test user with OAuth
- Send the browser to the sandbox authorize URL,
https://oauth.wefunder-sandbox.com/oauth/authorize, with yourpk_test_client id, one of your redirect URIs, and a PKCE challenge. See Authorization Code with PKCE. - Sign in as the seeded founder or an investor and consent.
- Exchange the code at the token endpoint. You get an
at_test_token. GET /users/mewith that token returns the test user.
Install on the syndicate and send a member to its deal
-
Install your app on the realm syndicate as the seeded founder, and get the syndicate-owned
at_test_token. First tick the scopes these walkthroughs use on your app:read:installations,write:installations,read:syndicates,write:syndicates,read:invite_links,write:invite_linksandwrite:investment_sessions. OAuth refuses a scope your app has not declared, and the install carries the scopes ticked when it is created. Then get a test user token for the founder from the OAuth walkthrough above, requestingread:installations write:installations, and either:- From the API.
POST /installationswith{"target_type": "syndicate", "target_id": "syn_…"}. Leaving outscopesasks for every scope your app declares. The response has the install underdataand the syndicate-owned token undertoken, shown once. The credential istoken.access_token. - From the install link. Open the install link on your realm host from the
portal's Installations page, sign in as the founder and consent. Consent
installs the app but hands you no token. Call
GET /installationswith the founder's token to find the install'sinst_…id, thenPOST /installations/{id}/tokensto mint the syndicate-owned token (againtoken.access_token, shown once).
The sandbox install is not listed in the live portal. You see and revoke it in the realm, under the syndicate's Settings → Installed apps.
- From the API.
-
GET /syndicates/{syndicate_id}/dealswith that token ({syndicate_id}is the syndicate'ssyn_…id) lists the open deal withstatus: "open". Itsofr_…id is the{offering_id}below. -
POST /offerings/{offering_id}/member_linkswith{"member_link": {"reference": "member_1"}}. The response has the link'surlon your realm host andstate: "created". -
Open the
urlin a signed-out browser. The deal page renders on your realm host and says you were invited to invest. -
Click Invest and sign up with a new email and password. The terms shown are the invited ones.
-
Pay by card with
4242 4242 4242 4242and finish checkout. -
GET /offerings/{offering_id}/member_links(orGET /member_links/{id}) shows the link withstate: "invested", the new account'swefunder_user_id, and theinvestment_id.
No webhook tells you a link was bound or invested; poll the link. investment.* events
go to installs on the offering's company, not to the syndicate install.
Bring a new member in with a join session
- With the syndicate-owned token from the install above, call
POST /syndicates/{syndicate_id}/join_sessionswith{"join_session": {"email": "[email protected]", "success_url": "https://…"}}. The token needswrite:syndicate_members, whichwrite:syndicatesincludes. - Open the session's
url. The hosted join page is on your realm host. - Sign up with the same email the session is bound to.
- Attest that you are accredited, then join.
- The browser returns to your
success_urlwithstatus=joined. - Your app-wide
"mode": "test"endpoint receivessyndicate_member.invited,syndicate_member.joinedandjoin_session.completed. Create that endpoint first, through the live API with"mode": "test"(see Webhook setup).
Invest through an investment session
- With the syndicate-owned token, call
POST /offerings/{offering_id}/investment_sessionswith{"investment_session": {"email": "[email protected]"}}. The install needswrite:investment_sessions. - Open the session's
urlto reach the hosted landing on your realm host, and create the account it asks for. - Go through checkout and pay by card with
4242 4242 4242 4242. GET /investment_sessions/{id}showsstatus: "completed".
Resetting a realm
Resetting a realm is not yet self-serve. If your seed data gets into a state you cannot recover from, write to support and we will re-seed it.
Promoting to live
Nothing needs to be "switched" for the paths the sandbox covers. Your live client already
exists and works on the companies and syndicates your organization manages. Swap _test_
credentials for _live_ ones, point users at the live authorize URL, and install again on
live: sandbox installs and their tokens stay in the realm.
Installing your app on someone else's company or syndicate requires app review from the
portal.
Sandbox realms exist for the public API described on this site. The Partner SPV
simulation endpoints under /partner/sandbox/… belong to the invite-only Partner
preview and are not part of the public surface.