> ## Documentation Index
> Fetch the complete documentation index at: https://docs.proofage.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js SDK

> Create verifications and verify webhooks from Node.js with @proofage/node.

`@proofage/node` signs every request, verifies webhooks with a drop-in handler for any framework with a standard `Request`, and ships a setup check.

## Install

Requires Node.js 22 or newer.

```bash theme={null}
npm install @proofage/node
```

## Configure

```bash theme={null}
PROOFAGE_API_KEY=pk_live_...
PROOFAGE_SECRET_KEY=sk_live_...
```

```ts theme={null}
import { ProofAgeClient } from '@proofage/node';

const client = new ProofAgeClient(); // keys from the environment
// or: new ProofAgeClient({ apiKey: 'pk_live_...', secretKey: 'sk_live_...' });
```

| Option | Environment variable | Default |
| - | - | - |
| `apiKey` | `PROOFAGE_API_KEY` | none |
| `secretKey` | `PROOFAGE_SECRET_KEY` | none |
| `baseUrl` | `PROOFAGE_BASE_URL` | `https://api.proofage.net` (without `/v1`) |
| `timeout` | `PROOFAGE_TIMEOUT` | `30000` ms |
| `retryAttempts` | `PROOFAGE_RETRY_ATTEMPTS` | `3` |
| `retryDelay` | `PROOFAGE_RETRY_DELAY` | `1000` ms, multiplied by the attempt number |

Check the setup from a terminal; it reads `.env.local` and `.env`:

```bash theme={null}
npx @proofage/node verify-setup
```

## Create a verification

```ts theme={null}
const verification = await client.verifications().create({
  external_id: 'user_12345',
  callback_url: 'https://yourapp.example/verified',
  external_metadata: { plan: 'pro' },
});

// send the person to verification.url
```

Request bodies use the API's snake\_case keys.

## Read results

```ts theme={null}
const v = client.verifications('550e8400-e29b-41d4-a716-446655440000');

const verification = await v.get();      // status, reason, duplicate_check, …
const document = await v.document();     // document fields and media
const estimation = await v.estimation(); // minimum-age result

for (const media of document?.media ?? []) {
  if (media.url === null) continue;      // deleted by retention or erasure
  await v.downloadMediaTo(media.id, `./${media.type}.jpg`);
}
```

## Receive webhooks

```ts theme={null}
import { webhookHandler } from '@proofage/node';

// Next.js App Router, Hono, Cloudflare Workers, …
export const POST = webhookHandler(async (payload) => {
  // payload.verification_id, payload.status, payload.reason
});
```

The handler answers `200` when your callback succeeds, `401` on a bad signature, `400` on invalid JSON and `500` if your callback throws. The timestamp tolerance is 300 seconds (`PROOFAGE_WEBHOOK_TOLERANCE`). For other frameworks, `handleWebhook(request)` returns `{ verified, payload, error }`, and `verifyWebhookSignature({ rawBody, signature, timestamp, authClient })` throws on a bad request.

## Everything else

| Method | Endpoint |
| - | - |
| `client.workspace().get()` | `GET /v1/workspace` |
| `client.workspace().getConsent()` | `GET /v1/consent` |
| `client.verifications(id).acceptConsent(body)` | `POST /v1/verifications/{id}/consent` |
| `client.verifications(id).uploadMedia(payload)` | `POST /v1/verifications/{id}/media` |
| `client.verifications(id).submit()` | `POST /v1/verifications/{id}/submit` |
| `client.verifications(id).blockFace({ reason_code, reason })` | `POST /v1/verifications/{id}/blocked-face` |

The server-side capture flow (consent, upload, submit) is in the package README and on [Capture in your own UI](/integration/web/custom-ui).

## Errors and retries

| Error | When |
| - | - |
| `ProofAgeError` | Any API error: `statusCode`, `code`, `message`, `errorData`, `responseBody`. |
| `AuthenticationError` | `401`. |
| `ValidationError` | `422`; `code` is set for a refused image (`FACE_NOT_FOUND`, …), `getErrors()` returns field errors. |
| `WebhookVerificationError` | A webhook with a bad or missing signature. |

`GET` requests retry on `408`, `429`, `5xx`, timeouts and network errors. `POST` requests retry only on `429` and on a connection that never opened, never on a `5xx` or a timeout, so a retry cannot create a second verification. A `429` waits for `Retry-After`.

## Links

* [npm: @proofage/node](https://www.npmjs.com/package/@proofage/node)
* [GitHub: ProofAge/node-client](https://github.com/ProofAge/node-client)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.