This tab collects prescriptive walkthroughs that tie the Humanity SDK to everyday integration tasks. Each recipe is copy/pasteable, highlights the minimum required inputs, and points to the underlying API surface when you need to drop down a level.
Prerequisites
- Approved Humanity app with
client_id, optional client_secret, and a registered redirect_uri.
- Access to the SDK package published as
@humanity-org/connect-sdk.
- Somewhere secure (session storage, KV store, Redis, etc.) to persist the PKCE
code_verifier, OAuth tokens, and any cursors returned by feed helpers.
import { HumanitySDK } from '@humanity-org/connect-sdk';
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
environment: process.env.HUMANITY_ENVIRONMENT ?? 'sandbox',
clientSecret: process.env.HUMANITY_CLIENT_SECRET, // omit for public clients
});
Example 1 — Kick off OAuth with PKCE
Use buildAuthUrl anywhere you can redirect the user (Next.js Route Handler shown here). Humanity issues the scopes that correspond to the presets you plan to evaluate. Persist the codeVerifier server-side for five minutes max.
import { cookies } from 'next/headers';
export async function GET() {
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: `${process.env.APP_URL}/api/humanity/callback`,
});
const { url, codeVerifier } = sdk.buildAuthUrl({
// Request OAuth scopes - see /concepts/scopes for the full list
scopes: ['identity:read', 'identity:date_of_birth', 'kyc:read'],
state: crypto.randomUUID(),
additionalQueryParams: { prompt: 'consent' },
});
cookies().set('humanity_code_verifier', codeVerifier, {
httpOnly: true,
sameSite: 'lax',
maxAge: 60 * 5,
});
return Response.redirect(url);
}
Behind the scenes: the helper generates a PKCE pair, resolves the correct /oauth/authorize URL for the environment, and uses the OAuth scopes (e.g. identity:read, identity:date_of_birth) that determine which presets your app can access.
Example 2 — Exchange the code and persist the session
Once Humanity redirects back to your redirect_uri, grab the authorization code and the stored code_verifier and trade them for tokens. The helper returns a typed payload that already includes granted preset keys.
import { cookies } from 'next/headers';
export async function GET(request: Request) {
const code = new URL(request.url).searchParams.get('code');
const codeVerifier = cookies().get('humanity_code_verifier')?.value;
if (!code || !codeVerifier) throw new Error('Missing PKCE state');
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
});
const token = await sdk.exchangeCodeForToken(code, codeVerifier);
await persistSession({
authorizationId: token.authorizationId,
accessToken: token.accessToken,
refreshToken: token.refreshToken,
expiresAt: Date.now() + token.expiresIn * 1000,
grantedPresets: token.presetKeys,
});
return Response.redirect('/dashboard');
}
- Refresh tokens are only returned for confidential clients—guard the storage location accordingly.
token.rateLimit (if present) mirrors the X-RateLimit-* headers for your own observability.
Example 3 — Gate features by presets
verifyPreset and verifyPresets hydrate evidence payloads and map rate-limit metadata for you. Use them immediately after sign-in or every time the user attempts to unlock a sensitive action.
const sdk = new HumanitySDK({ /* ... */ });
const verification = await sdk.verifyPresets({
accessToken: session.accessToken,
presets: ['is_human', 'age_over_21', 'country_of_residence'],
});
const failedChecks = verification.results.filter(
(preset) => preset.status !== 'valid' || !preset.value,
);
const failedErrors = verification.errors.map((error) => ({
preset: error.preset,
reason: error.error.error_description ?? error.error.error,
}));
if (failedChecks.length || failedErrors.length) {
return {
allowed: false,
failures: [
...failedChecks.map((preset) => ({
preset: preset.preset,
status: preset.status,
expiresAt: preset.expiresAt,
})),
...failedErrors,
],
};
}
enableHighRiskFeature();
Notes:
- Maximum of 10 presets per batch request; the helper enforces this before hitting the API.
verification.results includes evidence metadata, timestamps, and rate-limit context for audit logging, while verification.errors captures API-side failures.
Example 4 — Poll credential and authorization feeds
Feeds expose incremental changes so you can keep downstream systems synchronized. Store the cursor (or updatedSince) and pass it back to pick up where you left off.
const sdk = new HumanitySDK({ /* ... */ });
const credentials = await sdk.pollCredentialUpdates(session.accessToken, {
updatedSince: lastCredentialSyncIso,
limit: 50,
});
await processCredentialChanges(credentials.items);
await persistCursor('credentials', credentials.cursor ?? credentials.latestUpdatedAt);
const authorizations = await sdk.pollAuthorizationUpdates(session.accessToken, {
status: 'revoked',
updatedSince: lastAuthorizationSyncIso,
});
await processAuthorizationChanges(authorizations.items);
await persistCursor('authorizations', authorizations.cursor ?? authorizations.latestUpdatedAt);
- The SDK validates
limit (1–100) so you fail fast instead of receiving a 4xx from the API.
- Both helpers surface
rateLimit data; honor remaining and reset to avoid throttling.
Example 5 — Proactively revoke access
Revoke a single token, a batch of refresh tokens, or every authorization tied to a user. Set cascade: true to clear refresh tokens that belong to the same authorization automatically.
const sdk = new HumanitySDK({ /* ... */ });
await sdk.revokeTokens({
token: session.refreshToken,
tokenTypeHint: 'refresh_token',
cascade: true,
});
await deleteSession(session.id);
You can also pass tokens: string[] to revoke multiple credentials in one request or supply an authorizationId to wipe an entire consent grant.
Example 6 — Leverage discovery + health endpoints
When you boot your app, call discovery once to warm up the preset registry and know which scopes are currently available. Use healthcheck/readiness for alerting.
const sdk = new HumanitySDK({ /* ... */ });
const configuration = await sdk.getConfiguration();
console.log('Active presets', configuration.presets.map((preset) => preset.key));
const status = await sdk.readiness();
if (status.status !== 'ready') {
throw new Error('Humanity API is not ready, aborting startup');
}
getConfiguration(true) forces a refresh if you suspect the cache is stale.
healthcheck is a lightweight liveness probe; readiness asserts dependencies such as storage and preset registries.
Example 7 — Generate JWTs with your secret key
After validating a Humanity access token, issue your own application JWT. This decouples your session management from Humanity tokens.
import { HumanitySDK } from '@humanity-org/connect-sdk';
import * as jose from 'jose';
const APP_JWT_SECRET = new TextEncoder().encode(process.env.APP_JWT_SECRET!);
export async function POST(request: Request) {
const { code, codeVerifier } = await request.json();
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
clientSecret: process.env.HUMANITY_CLIENT_SECRET,
});
// Exchange code for Humanity tokens
const humanityToken = await sdk.exchangeCodeForToken(code, codeVerifier);
// Verify the user is human
const verification = await sdk.verifyPreset('isHuman', humanityToken.accessToken);
// Issue your own app JWT
const now = Math.floor(Date.now() / 1000);
const expiresIn = 3600;
const appToken = await new jose.SignJWT({
sub: humanityToken.authorizationId,
iss: 'my-app',
aud: 'my-app',
iat: now,
exp: now + expiresIn,
humanityUserId: humanityToken.authorizationId,
isHuman: verification.status === 'valid' && verification.value === true,
scopes: humanityToken.grantedScopes,
presets: [verification.preset],
})
.setProtectedHeader({ alg: 'HS256' })
.sign(APP_JWT_SECRET);
return Response.json({
token: appToken,
expiresAt: new Date((now + expiresIn) * 1000).toISOString(),
isHuman: verification.value,
});
}
To verify your JWT later:
import * as jose from 'jose';
const APP_JWT_SECRET = new TextEncoder().encode(process.env.APP_JWT_SECRET!);
async function verifyAppToken(token: string) {
try {
const { payload } = await jose.jwtVerify(token, APP_JWT_SECRET, {
issuer: 'my-app',
audience: 'my-app',
});
return payload;
} catch {
return null;
}
}
Example 8 — Decode JWT payloads
Extract claims from any JWT without verification (useful for reading Humanity tokens after they’ve been validated):
function decodeJwtPayload(token: string): Record<string, unknown> | null {
try {
const parts = token.split('.');
if (parts.length < 2) return null;
const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
const padded = payload.padEnd(Math.ceil(payload.length / 4) * 4, '=');
const decoded = Buffer.from(padded, 'base64').toString('utf-8');
return JSON.parse(decoded);
} catch {
return null;
}
}
Always verify tokens before trusting their contents. Use decodeJwtPayload only to read claims from tokens already validated by the Humanity API.
Example 9 — Refresh Humanity tokens
Refresh tokens before they expire:
import { HumanitySDK } from '@humanity-org/connect-sdk';
export async function POST(request: Request) {
const { refreshToken } = await request.json();
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
clientSecret: process.env.HUMANITY_CLIENT_SECRET,
});
const refreshed = await sdk.refreshAccessToken(refreshToken);
return Response.json({
accessToken: refreshed.accessToken,
refreshToken: refreshed.refreshToken,
expiresIn: refreshed.expiresIn,
grantedScopes: refreshed.grantedScopes,
});
}
Example 10 — PKCE state and nonce verification
The SDK provides static helpers for verifying OAuth security parameters:
import { HumanitySDK } from '@humanity-org/connect-sdk';
export async function GET(request: Request) {
const url = new URL(request.url);
const code = url.searchParams.get('code');
const callbackState = url.searchParams.get('state');
// Retrieve your stored session (from cookie, KV, etc.)
const storedState = '...'; // from your session storage
const storedNonce = '...'; // from your session storage
const codeVerifier = '...'; // from your session storage
// Verify state matches
if (!HumanitySDK.verifyState(storedState, callbackState)) {
return Response.json({ error: 'state_mismatch' }, { status: 400 });
}
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
});
const token = await sdk.exchangeCodeForToken(code!, codeVerifier);
// Verify nonce in ID token (if openid scope was requested)
if (token.idToken && storedNonce) {
const idPayload = decodeJwtPayload(token.idToken);
if (!HumanitySDK.verifyNonce(storedNonce, idPayload?.nonce as string)) {
return Response.json({ error: 'nonce_mismatch' }, { status: 400 });
}
}
return Response.json({
accessToken: token.accessToken,
authorizationId: token.authorizationId,
});
}
Example 11 — Server-to-server token acquisition
Get tokens for users who have already authorized your app without a browser:
import { HumanitySDK } from '@humanity-org/connect-sdk';
export async function POST(request: Request) {
const { email, userId, evmAddress } = await request.json();
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
});
// Get token by email
if (email) {
const result = await sdk.getClientUserToken({
clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
email,
});
return Response.json(result);
}
// Get token by user ID
if (userId) {
const result = await sdk.getClientUserToken({
clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
userId,
});
return Response.json(result);
}
// Get token by EVM wallet address
if (evmAddress) {
const result = await sdk.getClientUserToken({
clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
evmAddress,
});
return Response.json(result);
}
return Response.json({ error: 'Provide email, userId, or evmAddress' }, { status: 400 });
}
This is useful for background jobs, webhooks, and server-to-server scenarios where you need to verify presets without a browser session.
Example 12 — Fetch user profile via preset
Get user profile information using the humanity_user preset:
import { HumanitySDK } from '@humanity-org/connect-sdk';
export async function GET(request: Request) {
const accessToken = request.headers.get('authorization')?.replace('Bearer ', '');
if (!accessToken) {
return Response.json({ error: 'Missing token' }, { status: 401 });
}
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
});
const result = await sdk.verifyPreset('humanity_user', accessToken);
return Response.json({
preset: result.preset,
value: result.value,
status: result.status,
evidence: {
sub: result.evidence?.sub,
humanityId: result.evidence?.humanity_id,
email: result.evidence?.email,
emailVerified: result.evidence?.email_verified,
walletAddress: result.evidence?.wallet_address,
scopes: result.evidence?.scopes,
},
expiresAt: result.expiresAt,
});
}
Example 13 — Query Engine for declarative checks
Use the Query Engine for complex eligibility checks without hardcoding preset names:
import { HumanitySDK } from '@humanity-org/connect-sdk';
const sdk = new HumanitySDK({
clientId: process.env.HUMANITY_CLIENT_ID!,
redirectUri: process.env.HUMANITY_REDIRECT_URI!,
});
// Simple age check
const ageCheck = await sdk.evaluateQuery({
accessToken: session.accessToken,
query: {
check: { claim: 'identity.age', operator: '>=', value: 21 }
}
});
if (!ageCheck.passed) {
return Response.json({ error: 'Must be 21 or older' }, { status: 403 });
}
// Compound eligibility: US/CA resident AND KYC passed AND high net worth
const eligibility = await sdk.evaluateQuery({
accessToken: session.accessToken,
query: {
policy: {
allOf: [
{ check: { claim: 'kyc.passed', operator: '==', value: true } },
{ check: { claim: 'identity.country', operator: 'in', value: ['US', 'CA'] } },
{
policy: {
anyOf: [
{ check: { claim: 'financial.net_worth', operator: '>=', value: 1000000 } },
{ check: { claim: 'financial.income', operator: '>=', value: 200000 } }
]
}
}
]
}
}
});
if (eligibility.passed) {
enableAccreditedInvestorFeatures();
}
// Data extraction via projection
const userData = await sdk.evaluateQuery({
accessToken: session.accessToken,
query: {
projections: [
{ claim: 'identity.email', lens: 'pluck' },
{ claim: 'identity.country', lens: 'pluck' },
{ claim: 'kyc.passed', lens: 'pluck' }
]
}
});
console.log('User data:', userData.data);
// { "identity.email": "user@example.com", "identity.country": "US", "kyc.passed": true }
See the Query Engine documentation for all operators.
Dropping down to the generated client
All helpers ultimately call the fully generated REST client exposed as sdk.client. Reach for it when you need a controller method that does not yet have a convenience wrapper.
const preset = await sdk.client.presets.getPreset('humanity_user', {
headers: { Authorization: `Bearer ${session.accessToken}` },
});
This keeps the SDK future-proof: new API routes appear in the client immediately after pulling the latest release, while higher-level adapters can be layered on as needed.