@emblemapp/sdk
v2.3.0
Published
Emblem age-verification SDK — thin, contract-aligned client for the Publisher API v1
Readme
Emblem SDK v1
Contract-aligned SDK for the Emblem Publisher API (v1). This SDK is a thin adapter over HTTP and mirrors Emblem's partner contract surfaces:
- hosted verification
- assertion / reusable-proof authorization
- trusted credential enrollment
Requirements
- Node.js 18+ for server usage
Install
npm install @emblemapp/sdkServer usage (apiKey)
import { createClient } from '@emblemapp/sdk'
const client = createClient({
apiKey: process.env.EMBLEM_SECRET_KEY,
})
const start = await client.startVerification({
integration_id: '00000000-0000-0000-0000-000000000000',
callback_url: 'https://publisher.example/callback',
state: 'abc123',
})
// result_token is returned to your callback URL after successful verification
// failed and expired sessions are reported through webhooks
const result = await client.validateVerification({
result_token: resultTokenFromCallback,
})Assertion flow (apiKey only)
const transaction = await client.createAssertionTransaction({
client_id: 'emb_cli_123',
redirect_uri: 'https://publisher.example/assert/callback',
state: crypto.randomUUID(),
})
const assertion = await client.exchangeAssertionCode({
grant_type: 'authorization_code',
code: codeFromCallback,
client_id: 'emb_cli_123',
redirect_uri: 'https://publisher.example/assert/callback',
})assertion.assertion.client_id
assertion.assertion.subject
assertion.assertion.level
assertion.assertion.verified_atAssertion flow methods are server-only. They require a secret key and are rejected in browser contexts.
client_idis required on both assertion transaction creation and code exchangerequires_verificationis the normal business outcome when reusable proof is not available- if you use popup helpers,
openAssertionPopup()returns{ status: 'closed' }when the window closes before completion - reconcile interrupted assertion flows with
getAssertionTransaction(transactionId)
Trusted credential enrollment (apiKey only)
const enrollment = await client.startEnrollment({
client_id: 'emb_cli_123',
redirect_uri: 'https://partner.example/enroll/callback',
state: crypto.randomUUID(),
verification_level: 'L1',
provider: 'SAFEPASSAGE',
external_verification_id: 'provider-session-123',
verified_at: new Date().toISOString(),
})startEnrollment() records trusted-provider provenance and returns a fresh enrollment URL for the user. It is server-only, requires a secret key, and is rejected in browser contexts.
Browser usage (publicKey)
import { createClient } from '@emblemapp/sdk'
const client = createClient({
publicKey: 'emb_pk_live_123',
})
await client.startVerification({
integration_id: '00000000-0000-0000-0000-000000000000',
callback_url: 'https://publisher.example/callback',
})Warning: Secret API keys must never be used in browser contexts. validateVerification(), createAssertionTransaction(), exchangeAssertionCode(), getAssertionTransaction(), and startEnrollment() are server-only.
Client configuration
createClient() accepts exactly one authentication key plus optional transport settings:
| Option | Type | Description |
| ----------- | ----------- | ------------------------------------------------------ |
| apiKey | string | Secret API key (emb_sk_...). Server-side only. |
| publicKey | string | Public API key (emb_pk_...). Safe for browser use. |
| baseUrl | string | Base API URL. Defaults to https://app.emblemapp.com. |
| fetch | FetchLike | Optional custom fetch implementation. |
const client = createClient({
apiKey: process.env.EMBLEM_SECRET_KEY,
baseUrl: process.env.EMBLEM_BASE_URL,
})Provide exactly one of apiKey or publicKey. The SDK throws if both are provided, if neither is provided, or if apiKey is used in a browser context.
Integration environment
External integrations should use the public Emblem endpoint at https://app.emblemapp.com.
Internal staging environments are not intended for external use.
If you override baseUrl for local or internal testing, use credentials and integration IDs issued in that same target environment.
Session reconciliation
Emblem also exposes GET /api/v1/verify/sessions/{sessionId} as a publisher-facing recovery endpoint.
The SDK does not currently provide a dedicated helper for this route; use a server-side fetch call if you need reconciliation lookup.
Assertion reconciliation
The SDK includes getAssertionTransaction(transactionId) for assertion-flow recovery lookup.
Trusted credential notes
The SDK wraps the HTTP routes, but it does not provision trusted-partner access for you.
startEnrollment()still requires a provisionedAuthorizationClient- the client usually needs
allowCredentialIssuance = true - the target environment must still be provisioned correctly on the Emblem side
Popup helpers
For browser popup/new-window integrations, the SDK exports:
openAssertionPopup(authorizeUrl, options?)handleAssertionPopupCallback(options?)
These helpers only normalize popup callback handling. They do not exchange the code and do not expose reusable proof in browser code.
Trust boundary
Using the assertion flow does not, by itself, grant permission to mint or issue new Emblem-backed credentials. Trusted providers may be approved separately for both reusable-proof authority and issuance authority.
Errors, rate limiting, retries, timeouts
The SDK throws an EmblemApiError when the API returns an error envelope.
import { EmblemApiError } from '@emblemapp/sdk'
try {
await client.startVerification({
integration_id: '00000000-0000-0000-0000-000000000000',
callback_url: 'https://publisher.example/callback',
})
} catch (err) {
if (err instanceof EmblemApiError) {
// Stable error code from the API contract.
console.log(err.code)
// Optional error metadata.
console.log(err.request_id, err.details)
// Rate limiting: check the code and respect Retry-After when provided.
if (err.isRateLimited) {
console.log('retryAfter (seconds):', err.retryAfter)
}
}
}This SDK is a thin HTTP wrapper and does not implement retries or request timeouts.
If you need timeouts, provide a custom fetch implementation (e.g. using AbortSignal.timeout() in Node 18+).
Webhook verification
import { verifyWebhookSignature } from '@emblemapp/sdk'
const isValid = verifyWebhookSignature({
// X-Emblem-Signature: t={unix_seconds},v1={hex_hmac}
signature: signatureHeaderValue,
// X-Emblem-Timestamp: {unix_seconds}
timestamp: timestampHeaderValue,
rawBody: rawBodyString,
secret: process.env.EMBLEM_WEBHOOK_SECRET,
})Note: verifyWebhookSignature() is server-only. It requires a webhook secret and Node.js crypto.
Important: rawBody must be the exact raw request body used to compute the signature.
Type generation
Types are generated from openapi/emblem-publisher-api.yaml and committed to the repo. To regenerate:
npm run types:generateTo verify types are up to date:
npm run types:check