Skip to content
On this page and tools

Sign in with Bynefit

OAuth 2.0 / OpenID Connect

Add a "Sign in with Bynefit" button to your site or app. Bynefit is a standards-compliant OAuth 2.0 / OpenID Connect provider — the same flow you use for Google, Apple, or Microsoft.

Issuer

All flows start at id.bynefit.com — the dedicated auth domain, which serves the endpoints below and nothing else. Bookmark the discovery document if your client supports auto-configuration:

OpenID configurationJSON
GET https://id.bynefit.com/.well-known/openid-configuration

It advertises the canonical endpoints:

EndpointURL
Authorizationhttps://id.bynefit.com/oauth/authorize
Tokenhttps://id.bynefit.com/oauth/token
UserInfohttps://id.bynefit.com/oauth/userinfo
JWKShttps://id.bynefit.com/oauth/jwks.json

Tokens are signed RS256. Always validate iss equals https://id.bynefit.com and aud equals your client_id before trusting the claims.

Register a client

Team admins register OAuth clients at /dash/sites/oauth. Each client is scoped to one team and gets a unique client_id.

Site-registered clients are public clients:

The flow (PKCE)

  1. Generate a high-entropy code_verifier (43–128 chars). Compute code_challenge = BASE64URL(SHA256(code_verifier)).
  2. Redirect the user to:
    Step 1 — AuthorizeGET
    https://id.bynefit.com/oauth/authorize
      ?client_id=site_xxxxxxxxxxxx
      &redirect_uri=https://yoursite.com/auth/callback
      &response_type=code
      &scope=openid+profile+email
      &state=<random>
      &code_challenge=<S256-of-verifier>
      &code_challenge_method=S256
  3. The user signs in (if needed) and approves the scopes. Bynefit redirects back to your redirect_uri with ?code=…&state=…. Verify state matches what you sent.
  4. Exchange the code for tokens:
    Step 2 — Token exchangePOST
    POST https://id.bynefit.com/oauth/token
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code
    &code=<code>
    &redirect_uri=<same redirect_uri>
    &client_id=site_xxxxxxxxxxxx
    &code_verifier=<the verifier>
    Response (JSON): access_token, refresh_token, id_token (JWT), expires_in, token_type=Bearer, scope.
  5. Verify the id_token: signature against /oauth/jwks.json, iss == https://id.bynefit.com, aud == your client_id, expiry.
  6. Call GET /oauth/userinfo with Authorization: Bearer <access_token> for profile claims (or just trust the id_token claims for openid + profile + email).

Code for each step

Make the verifier and challenge

Keep the verifier until the token exchange. In a browser keep it in sessionStorage; on a server keep it in the session.

function base64url(bytes) {
  return btoa(String.fromCharCode(...new Uint8Array(bytes)))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

async function pkcePair() {
  const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
  return { verifier, challenge: base64url(digest) };
}

Send the person to Bynefit

GETauthorize
https://id.bynefit.com/oauth/authorize?response_type=code
  &client_id=site_0123456789abcdef01234567
  &redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fcallback
  &scope=openid%20profile%20email
  &state=RANDOM_STATE
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256

Exchange the code

The code works once and expires after 10 minutes. The request is form-encoded, not JSON.

curl -X POST https://id.bynefit.com/oauth/token \
  -d grant_type=authorization_code \
  -d code=CODE_FROM_THE_REDIRECT \
  -d client_id=site_0123456789abcdef01234567 \
  -d redirect_uri=https://example.com/auth/callback \
  -d code_verifier=YOUR_CODE_VERIFIER
200JSON
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "9f2c…",
  "scope": "openid profile email",
  "id_token": "eyJhbGciOiJSUzI1NiIs…"
}

id_token is only there when openid was granted. The access token lasts an hour and the refresh token 60 days. Errors are JSON with error and error_description: 400 invalid_request, invalid_grant or unsupported_grant_type, and 401 invalid_client.

Refresh

Trade a refresh token for new tokens before the access token runs out. Each refresh token works once: the answer carries a new one, and the old one stops working at once, so store the new one. A new id_token comes back too, so a site signed in weeks ago sees today's name and picture.

curl -X POST https://id.bynefit.com/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=YOUR_REFRESH_TOKEN \
  -d client_id=site_0123456789abcdef01234567

Ask who is signed in

curl https://id.bynefit.com/oauth/userinfo \
  -H "Authorization: Bearer ACCESS_TOKEN"
200JSON
{
  "sub": "bu_0123456789abcdef0123456789abcdef",
  "name": "Sam Rivera",
  "nickname": "samr",
  "picture": "https://bynefit.com/media/avatars/samr.jpg",
  "profile": "https://bynefit.com/p/samr",
  "bio": null,
  "email": "sam@example.com",
  "email_verified": true
}

Which claims appear depends on the scopes granted: profile gives name, nickname, picture, profile and bio; email gives email and email_verified. The person chooses, per site, which email, phone number and business a site sees, so the answer can differ between sites and can change. When they share a business with the site, a business claim is added; it is absent when they chose not to share one. A 401 invalid_token means the token is expired, revoked, or was issued to a different client.

Treat email_verified with care for older accounts. Accounts made before 11 May 2026 report true without having confirmed the address. Do not use it alone as proof that someone controls an email.

Client rules

Sign in on your own page

A sign-in sheet that opens over your page, without sending people away, is available to the websites listed on the client. Everything the person types is inside a frame served by the sign-in host, which your page's script cannot read. On a Bynefit-hosted site the auth kit module draws the button and runs the whole flow; elsewhere you can still use the redirect flow above. Allow the sign-in host in your frame-src and connect-src if your site sets a Content Security Policy, and keep scripts on the page trusted, because tokens are kept in the browser. If the frame cannot load, the sheet offers to continue in a new window.

Silent sign-in

For returning users already signed into Bynefit, pass prompt=none on the authorize call. The server either redirects back with a code immediately (no UI shown) or returns one of these errors so your client can fall back to manual sign-in:

ErrorMeaning
login_requiredUser has no active Bynefit session — show the manual button.
consent_requiredUser is signed in but hasn't yet approved your scopes — start the normal flow.

Scopes

ScopeGrants
openidRequired. Identifies this as an OIDC request and authorizes an id_token.
profileName, username, bio, and profile picture (picture, an absolute https URL).
emailEmail address + verification status.
phonePhone number + verification status. Available to first-party clients only at this time.
creator.readThe person's creator status, milestones, tiers, activity and content stats, through the creator hub endpoints below. Needs staff approval for the client.
earnings.readThe person's creator earnings, bonuses, payouts and ambassador earnings, through the creator hub endpoints below. Needs staff approval for the client.

Scopes that need approval

creator.read and earnings.read are not open by default. A manager or admin of the client's Space asks for them on the client's page at /dash/sites/oauth; Bynefit staff are told and approve or decline each one. Until a scope is approved it is left out of the grant: the authorization still succeeds with the scopes the client may use, the token response's scope omits it and the person is never shown it on the consent screen (the request fails with invalid_scope only when no allowed scope remains). Check the scope in the token response before you call an API that needs one. Once approved, the person sees it on the consent screen as "Your creator status, milestones, tiers, and content stats" or "Your creator earnings and payout status", and can still decline.

Additional scopes (teams:read, wallet:read, tasks:read, purchases:read, feeds:read) exist for first-party Bynefit apps and aren't open to site-registered clients. Reach out if your use case needs one.

What a signed-in token can call

Signing in gives your site an access token. Besides the userinfo endpoint, it is the bearer token for two Bynefit APIs, each gated by what the person approved:

APINeedsWhat it does
Client portal APIA sign-in client owned by a practice Space; no extra scope. The person must be that practice's customer, and some routes need the practice add-on (see the page)The signed-in person reads their own engagements, documents and messages on a practice site, and a practice manager can run the pipeline from its admin page. Calls look in the Space that owns the client.
Creator hub API, /api/creator/web/*creator.read or earnings.read on the clientThe creator's own numbers. With creator.read: activity, analytics, milestones, tiers, top-content. With earnings.read: earnings-breakdown, bonuses, payouts, ambassador-earnings. overview returns the sections the token's scopes allow.

Call the creator hub with Authorization: Bearer. All its endpoints are GET, answer only about the signed-in person, and send no cross-origin permission except to https://creators.bynefit.com and any origin Bynefit has added for a partner hub, so a call from another website's browser is refused (403 on the preflight); call it from your own server if you need it elsewhere. A token without the scope answers 403 insufficient_scope; an expired or revoked token, or one from a first-party Bynefit app, answers 401 invalid_token.

Subject identifier (sub)

The sub claim is a stable, opaque, per-issuer user identifier. Treat it as your primary key when storing per-user data on your side — never key on email (which can change) or display name. Two different Bynefit users will never share a sub; the same user signing into two different clients gets the same sub.

Errors

Returned as standard OAuth error params on the redirect_uri or as JSON from the token endpoint:

Error codeWhen
invalid_requestMissing required parameter, bad redirect_uri, or missing PKCE.
invalid_clientclient_id is unknown, inactive, or doesn't match the redirect_uri.
invalid_grantcode expired (10-minute TTL), already used, or code_verifier doesn't match the original challenge.
invalid_scopeRequested a scope not allowed for this client.
access_deniedUser clicked Deny on the consent screen.
login_required / consent_requiredSilent (prompt=none) flow couldn't complete without UI.

Revocation

Users can revoke your client's access at /dash/me/apps. When revoked, the consent record is deleted and every outstanding access + refresh token is marked revoked. Your next call to /oauth/token with a refresh token will return invalid_grant; access tokens stop working immediately.

The browser kit (for static sites)

If your site is hosted by Bynefit (*.site.bynefit.com or a verified custom domain), the auth module of the Bynefit kit wraps the entire flow into one config block plus a couple of HTML attributes — no PKCE plumbing required. See the site-config guide.

CORS

/oauth/token, /oauth/userinfo, and /oauth/jwks.json respond with Access-Control-Allow-Origin: * so browser-based clients on any origin can call them. Credentials are never echoed — the flow's security comes from PKCE plus the redirect-URI allowlist on your client.