Skip to main content
Your users authenticate to Etherfuse with JSON Web Tokens that you sign. You sign a short-lived JWT for a user, and we verify it against a JWKS URL you’ve registered with us. It’s an ordinary OAuth 2.0 JWT bearer exchange (RFC 7523), so any JWT library works. The simplest and recommended way to use a JWT is to launch the user straight into the Etherfuse app, where they complete flows like KYB. You can also exchange a JWT server-side to provision a user ahead of time or prefetch a refresh token that speeds up launch.

Before you start

1

Generate a signing key pair

Create an asymmetric key pair for an algorithm we support, such as RSA for RS256 or an EC key for ES256. You sign JWTs with the private key and publish the public key (next step).
# RSA 2048
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
2

Host a JWKS endpoint

Publish the public key as a JSON Web Key Set (RFC 7517) at a stable HTTPS URL, e.g. https://your-domain.com/.well-known/jwks.json. Give the key a stable id (kid) so we can match it to a token’s kid header. Most JWT libraries can export a public key to JWK form:
{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS256",
      "kid": "your-key-id",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx...",
      "e": "AQAB"
    }
  ]
}
The URL must be reachable from Etherfuse’s servers over HTTPS and return the key set as JSON. We fetch it fresh on every verification (no caching), so rotating keys is straightforward: add the new key as another entry in keys, start signing with its kid, then remove the old key once no unexpired tokens still reference it.
3

Register your issuer with Etherfuse

Send your Etherfuse representative your issuer (iss) and JWKS URL. We link them to your partner organization so JWTs from that issuer resolve to your customers. (There is no self-serve endpoint for this yet.)

Sign a user JWT

Sign a JWT with the key behind your JWKS. The claims:
ClaimRequiredNotes
issYour registered issuer.
subThe user’s id. A UUID is strongly recommended; see the note below.
audThe token endpoint URL: https://api.etherfuse.com/auth/token (or https://api.sand.etherfuse.com/auth/token in sandbox).
scopeRequired. Use kyb for KYB access. See Scopes.
nonceA fresh random value, unique per token (replay-protected).
email, nameThe user’s email and full name.
exp, iatKeep tokens short-lived.
pictureoptionalProfile image URL.
Strongly recommended: make sub a UUID. sub identifies the person signing in. For an individual customer, that person is the customer: use the same UUID for sub and for the customerId in the Ramp API, so both resolve to one customer. A non-UUID sub still signs in, but can’t be addressed through the Ramp API.
A sub must name a person, never a business. Don’t put a business organization’s id in sub: it’s rejected with invalid_grant. And don’t reuse a person’s sub as a business org id: POST /ramp/organization returns 409. A business is a separate org with its own id; a real person signs in with their own personal sub and completes the business’s verification on its behalf.
import jwt from "jsonwebtoken";
import { randomUUID } from "crypto";

// Reuse one UUID per user; it's their customerId in the Ramp API too.
const customerId = randomUUID();

const token = jwt.sign(
  {
    scope: "kyb",
    nonce: randomUUID(),
    email: "[email protected]",
    name: "Ana García",
  },
  PRIVATE_KEY,                       // the key behind your JWKS
  {
    algorithm: "RS256",
    keyid: "your-key-id",            // matches a kid in your JWKS
    issuer: "https://your-domain.com",
    subject: customerId,
    audience: "https://api.sand.etherfuse.com/auth/token",
    expiresIn: "5m",
  },
);

Launch the user into the app

This is the recommended path. You hand the user’s browser to Etherfuse with their JWT, and the app signs them in and takes them where you point them (for example, KYB).
Launch lives on the app host, not the API host: https://app.etherfuse.com (https://sandbox.etherfuse.com in sandbox). It’s a browser page, not a JSON API.
Every launch carries:
  • grant_type and assertion: either a raw partner JWT (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer) or a refresh_token from a prior server-side exchange (grant_type=refresh_token).
  • target: the app path to land on, e.g. /kyb.
  • return_url (optional): where to send the user when they leave.
Have the user’s browser POST a form to /auth/launch, either as a top-level navigation or into an iframe you’ve embedded (point the form’s target at the frame). The app exchanges the JWT, establishes the session, and redirects to target. A server-rendered auto-submitting form is the usual way:
<form id="launch" method="POST" action="https://sandbox.etherfuse.com/auth/launch">
  <input type="hidden" name="grant_type" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
  <input type="hidden" name="assertion" value="<your_signed_jwt>" />
  <input type="hidden" name="target" value="/kyb" />
</form>
<script>document.getElementById("launch").submit()</script>
Rather not POST a form? Hand the credentials to the launch page over postMessage instead: open it in an iframe or popup and message the JWT in. See Launch via postMessage for the message contract and a working example.

Scopes

The scope claim is required on every partner JWT. Set it to the scope for what the user needs to do:
ScopeGrants
kybAccess to the Business Onboarding (KYB) flow.
Scope is strictly enforced: the claim cannot be omitted, and any value other than the ones documented here is rejected with invalid_scope. Ask only for the scope a user needs.
Need to provision a user without granting any API access yet? Sign the JWT with an empty scope (scope: ""). The user record is created, but the token can’t act until you reissue it with a real scope.

When something fails

Auth failures come back as OAuth errors with an error, an error_description, and an error_uri pointing at the matching entry in Authentication errors. A launch renders these on the page rather than returning them. If you want to read them programmatically, exchange the JWT server-side first: POST /auth/token returns the same error as JSON, so you can branch on error before ever handing the user to a launch.