# SDK Implementation Recipes & Patterns

> Task-based playbooks built on top of [@humanity-org/connect-sdk](/build-with-humanity/build-with-the-sdk-api/humanity-org-connect-sdk.md).

This page 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.

```tsx
import { HumanitySDK } from '@humanity-org/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 of  `buildAuthUrl` to redirect the user (Next.js Route Handler shown here).&#x20;

Humanity issues the scopes that correspond to the presets you plan to evaluate. Persist the `codeVerifier` server-side for five minutes max.

```tsx
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({
    scopes: ['openid', 'identity:read', 'identity:date_of_birth'], 
    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 flattens friendly preset keys (e.g. `is_human`) into the literal scopes (`presets:is_human.read`).

## 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.

```tsx
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.

```tsx
const sdk = new HumanitySDK({ /* ... */ });

const verification = await sdk.verifyPresets({
  accessToken: session.accessToken,
  presets: ['is_human', 'is_21_plus'],
});

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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
import { HumanitySDK } from '@humanity-org/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:

```tsx
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):

```tsx
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;
  }
}
```

> :warning:\
> Always verify tokens before trusting their contents. Use `decodeJwtPayload` only to read claims from tokens already validated by the Humanity API.&#x20;

## Example 9 — Refresh Humanity tokens

Refresh tokens before they expire:

```tsx
import { HumanitySDK } from '@humanity-org/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:

```tsx
import { HumanitySDK } from '@humanity-org/sdk';

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');
 

  // Retrieve your stored session (from cookie, KV, etc.)
  const storedNonce = '...';    // from your session storage
  const codeVerifier = '...';   // from your session storage

  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:

```tsx
import { HumanitySDK } from '@humanity-org/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 });
}
```

> :information\_source:\
> This is useful for background jobs, webhooks, and server-to-server scenarios where you need to verify presets without a browser session.&#x20;

## Example 12 — Fetch user profile via preset

Get user profile information using the `humanity_user` preset:

```tsx
import { HumanitySDK } from '@humanity-org/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 — Static Security Helpers <a href="#example-3--static-security-helpers" id="example-3--static-security-helpers"></a>

The SDK provides static methods for generating and verifying OAuth security parameters.

```typescript
import { HumanitySDK } from '@humanity-org/sdk';

// Generate cryptographically secure state for CSRF protection
const state = HumanitySDK.generateState();        // Default length: 32
const longState = HumanitySDK.generateState(64);  // Custom length

// Generate nonce for replay protection (included in ID token)
const nonce = HumanitySDK.generateNonce();        // Default length: 32
const longNonce = HumanitySDK.generateNonce(64);  // Custom length

// Verify state matches (timing-safe comparison to prevent timing attacks)
const isStateValid = HumanitySDK.verifyState(storedState, receivedState);
if (!isStateValid) {
  return Response.json({ error: 'state_mismatch' }, { status: 400 });
}

// Verify nonce matches (from ID token claims)
const isNonceValid = HumanitySDK.verifyNonce(storedNonce, idTokenNonce);
if (!isNonceValid) {
  return Response.json({ error: 'nonce_mismatch' }, { status: 400 });
}
```

## Static Method Signatures <a href="#static-method-signatures" id="static-method-signatures"></a>

```typescript
// Generate cryptographically secure random strings
static generateState(length?: number): string  // default: 32, minimum: 43
static generateNonce(length?: number): string  // default: 32, minimum: 43

// Timing-safe verification (returns false if either value is null/undefined)
static verifyState(expected: string, received?: string | null): boolean
static verifyNonce(expected: string, received?: string | null): boolean

```

## 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.

```tsx
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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.humanity.org/developer-guides-and-tutorials/sdk-api-guides/sdk-implementation-recipes-and-patterns.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
