← All @molecule/* packages · App templates

@molecule/api-kyc-stripe-identity

Provider bond · kyc · API (Node) · v1.0.1 · Apache-2.0

Stripe Identity KYC bond for molecule.dev — document + selfie verification, normalized status, signed webhooks.

npm install @molecule/api-kyc-stripe-identity

npm · Source on GitHub · Implements @molecule/api-kyc

How it works

@molecule/api-kyc-stripe-identity is a provider bond on the API (Node) side: it implements the kyc core interface (@molecule/api-kyc) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

import { setProvider } from '@molecule/api-kyc'
import { provider } from '@molecule/api-kyc-stripe-identity'

setProvider(provider)

Works with: @molecule/api-bond, @molecule/api-kyc, @molecule/api-secrets

Secrets: STRIPE_SECRET_KEY, STRIPE_IDENTITY_WEBHOOK_SECRET

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Stripe Identity KYC bond for molecule.dev.

Implements the {@link KycProvider} contract from @molecule/api-kyc using Stripe Identity's REST API. Verification sessions are document + selfie (document), id-number (id_number), or hosted address checks (provider-dependent). Webhooks are verified against STRIPE_IDENTITY_WEBHOOK_SECRET.

Setup

  1. Create a Stripe account and enable Stripe Identity.
  2. Set STRIPE_SECRET_KEY and STRIPE_IDENTITY_WEBHOOK_SECRET in the API environment (or pass secretKey / webhookSecret to {@link createProvider}).
  3. Configure a webhook endpoint subscribed to identity.verification_session.verified, identity.verification_session.requires_input, and identity.verification_session.canceled.
  4. Bond at startup: setProvider(provider).

Quick Start

import { setProvider } from '@molecule/api-kyc'
import { provider } from '@molecule/api-kyc-stripe-identity'

setProvider(provider)

Type

provider

Installation

npm install @molecule/api-kyc-stripe-identity @molecule/api-bond @molecule/api-kyc @molecule/api-secrets

API

Interfaces

CreateKycSessionOptions

Caller-supplied parameters when creating a verification session.

interface CreateKycSessionOptions {
  /**
   * Caller's stable identifier for the end user. Stored as provider metadata
   * so webhook events and status lookups can be correlated.
   */
  userId: string
  /** Type of identity check to request. */
  type: KycVerificationType
  /**
   * URL to send the user to after they complete or abandon the
   * provider-hosted flow. Some providers also expose a hosted URL — see
   * {@link KycSession.url}.
   */
  returnUrl?: string
  /**
   * Free-form key/value pairs forwarded to the provider as session metadata.
   * Useful for correlating with caller-side records (case id, app id, etc.).
   * Values are coerced to strings by the provider.
   */
  metadata?: Record<string, string>
}

KycProvider

KYC provider contract.

Each method is stack-neutral; bonds for different providers (Stripe Identity, Persona, Onfido, Sumsub) expose the same shape. Providers throw on errors with sanitized messages — never leaking API keys, webhook secrets, or verbatim provider error bodies that may echo credentials.

interface KycProvider {
  /**
   * Creates a new verification session at the provider.
   *
   * @param options - Session parameters (user, type, return URL, metadata).
   * @returns The created session, including a hosted URL where applicable.
   */
  createVerificationSession(options: CreateKycSessionOptions): Promise<KycSession>
  /**
   * Fetches the current status of a verification session.
   *
   * @param sessionId - Provider-specific session id from
   *   {@link KycSession.sessionId}.
   * @returns The normalized session status.
   */
  getVerificationStatus(sessionId: string): Promise<KycSessionStatus>
  /**
   * Cancels a verification session. Idempotent — canceling an already-canceled
   * session SHOULD return the same status without throwing.
   *
   * @param sessionId - Provider-specific session id from
   *   {@link KycSession.sessionId}.
   * @returns The session status after cancellation.
   */
  cancelVerificationSession(sessionId: string): Promise<KycSessionStatus>
  /**
   * Verifies the signature of an inbound webhook and returns the normalized
   * event. Throws if the signature is invalid or the payload cannot be parsed.
   *
   * @param headers - Inbound request headers (verbatim — bonds extract the
   *   right signature header).
   * @param body - Raw request body bytes (do NOT pass a parsed JSON object —
   *   most providers sign the exact byte sequence).
   * @returns The normalized webhook event.
   */
  processWebhook(headers: KycWebhookHeaders, body: string | Buffer): Promise<KycWebhookEvent>
}

KycSession

A verification session created with a KYC provider.

Sessions are the unit of state — once created they progress through statuses ({@link KycStatus}) until terminal (verified or canceled).

interface KycSession {
  /** Provider-specific session identifier. Opaque to callers. */
  sessionId: string
  /**
   * Provider-hosted URL to redirect the user to. Some providers (e.g. those
   * using a client-side SDK with a single-use token) MAY return `null` —
   * callers must then use the SDK directly.
   */
  url: string | null
  /**
   * Optional epoch-millis expiry of the hosted session. After this time the
   * session URL stops working and the caller must create a new session.
   */
  expiresAt?: number
}

KycSessionStatus

Result of {@link KycProvider.getVerificationStatus}.

interface KycSessionStatus {
  /** Provider-specific session identifier. */
  sessionId: string
  /** Normalized status across providers. */
  status: KycStatus
  /**
   * Verification type at create time. Useful for callers that do not store
   * the type alongside the session id.
   */
  type?: KycVerificationType
  /**
   * Provider-specific reason code when status is `requires_input` or
   * `canceled`. Opaque string — meant for logging / display, not branching.
   */
  lastErrorCode?: string
  /** Provider-specific human-readable error reason. */
  lastErrorReason?: string
}

KycWebhookEvent

A normalized webhook event. Returned by {@link KycProvider.processWebhook} after the signature has been verified.

interface KycWebhookEvent {
  /** The normalized event type. */
  type: KycWebhookEventType
  /** Provider-specific session identifier the event applies to. */
  sessionId: string
  /** Caller-supplied user id stored in session metadata. */
  userId?: string
  /** Verification type at create time. */
  verificationType?: KycVerificationType
  /** Caller-supplied metadata stored on the session. */
  metadata?: Record<string, string>
  /** Provider-specific reason code for failure events. */
  lastErrorCode?: string
  /** Provider-specific human-readable failure reason. */
  lastErrorReason?: string
  /** Raw provider event object — kept for round-tripping / debugging. */
  raw?: Record<string, unknown>
}

StripeIdentityProviderOptions

Configuration options for {@link createProvider}.

Every field defaults to environment variables so handlers can call createProvider() with no arguments. Tests inject overrides (especially {@link StripeIdentityProviderOptions.fetch} and {@link StripeIdentityProviderOptions.apiBaseUrl}).

interface StripeIdentityProviderOptions {
  /**
   * Stripe secret API key. Defaults to `process.env.STRIPE_SECRET_KEY`.
   */
  secretKey?: string
  /**
   * Stripe Identity webhook signing secret. Defaults to
   * `process.env.STRIPE_IDENTITY_WEBHOOK_SECRET`.
   */
  webhookSecret?: string
  /**
   * Override the Stripe API base URL. Defaults to `https://api.stripe.com`.
   * Useful for tests pointing at a fake Stripe server.
   */
  apiBaseUrl?: string
  /** Request timeout in milliseconds. Defaults to `15_000`. */
  timeoutMs?: number
  /**
   * Override the Stripe API version sent on every request. Defaults to
   * Stripe's pinned `2024-06-20`.
   */
  apiVersion?: string
  /**
   * Maximum allowed clock-drift between the bond's host and Stripe when
   * verifying webhook signatures, in seconds. Defaults to `300` (5 minutes),
   * matching Stripe's recommendation.
   */
  webhookToleranceSeconds?: number
  /**
   * Replace the global fetch implementation. Tests inject a stub here;
   * production callers should leave it unset.
   */
  fetch?: typeof fetch
}

Types

KycStatus

Normalized verification status, common across all KYC bonds.

  • pending — the session has been created but the user has not started.
  • requires_input — provider has rejected or paused; the user must take another action (resubmit document, retry selfie, etc.).
  • processing — provider is currently reviewing submitted material.
  • verified — verification succeeded.
  • canceled — the session was canceled (by caller, user, or provider).
type KycStatus = 'pending' | 'requires_input' | 'processing' | 'verified' | 'canceled'

KycVerificationType

Type of identity check requested for a verification session.

  • document — government-issued photo ID + selfie liveness check.
  • id_number — verifies a government-issued number (e.g. SSN) without a document.
  • address — verifies the user's residential address.

Provider support varies; bonds MAY throw when asked for a type they do not support.

type KycVerificationType = 'document' | 'id_number' | 'address'

KycWebhookEventType

Discriminated event type emitted by {@link KycProvider.processWebhook}.

Bonds normalize provider-specific event names into one of these three variants. Provider-specific raw payload is preserved in {@link KycWebhookEvent.raw} so callers needing extra detail can opt in.

type KycWebhookEventType =
  'verification.verified' | 'verification.requires_input' | 'verification.canceled'

KycWebhookHeaders

Headers required for {@link KycProvider.processWebhook}. Lower-cased keys.

Different providers use different header names for the signature; bonds are responsible for picking the right one(s) from this map. Callers should pass the request's headers verbatim.

type KycWebhookHeaders = Record<string, string | string[] | undefined>

Functions

createProvider(options)

Creates a Stripe Identity provider.

function createProvider(options?: StripeIdentityProviderOptions): KycProvider
  • options — Optional configuration. Falls back to STRIPE_SECRET_KEY / STRIPE_IDENTITY_WEBHOOK_SECRET env vars when secretKey / webhookSecret are omitted.

Returns: A {@link KycProvider} implementation.

verifyStripeSignature(payload, signatureHeader, secret, toleranceSeconds, now)

Verifies a Stripe webhook signature. Equivalent to Stripe.webhooks.constructEvent minus the SDK dependency.

function verifyStripeSignature(
  payload: string | Buffer<ArrayBufferLike>,
  signatureHeader: string | undefined,
  secret: string,
  toleranceSeconds: number,
  now?: number,
): void
  • payload — The raw request body.
  • signatureHeader — The stripe-signature header value.
  • secret — The webhook signing secret.
  • toleranceSeconds — Allowed clock-drift between hosts.
  • now — Current epoch millis (injectable for tests).

Constants

kycStripeIdentitySecretDefinitions

Secret definitions required by the Stripe Identity KYC bond.

const kycStripeIdentitySecretDefinitions: SecretDefinition[]

provider

The Stripe Identity provider. Lazily initialized on first use so that environment variables are read at call time rather than import time.

const provider: KycProvider

Core Interface

Implements @molecule/api-kyc interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setProvider } from '@molecule/api-kyc'
import { provider } from '@molecule/api-kyc-stripe-identity'

export function setupKycStripeIdentity(): void {
  setProvider(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1
  • @molecule/api-kyc ^1.0.1
  • @molecule/api-secrets ^1.0.1

Environment Variables

  • STRIPE_SECRET_KEY (required) — Stripe secret key
    • Setup: Stripe Dashboard → Developers → API keys; use the sk_test_ key in test mode, sk_live_ in production.
    • Get it here: https://dashboard.stripe.com/apikeys
    • Example: sk_test_...
  • STRIPE_IDENTITY_WEBHOOK_SECRET (required) — Stripe Identity webhook signing secret

Runtime Dependencies

  • @molecule/api-bond
  • @molecule/api-kyc
  • @molecule/api-secrets

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • Starting a verification from the UI creates a real session: createVerificationSession is called, the app persists the returned KycSession.sessionId on the user's record with stored status pending, and the user is handed to the provider-hosted session.url — NOT a home-grown document-capture screen, and NOT marked verified at creation.
  • COUNTERPARTY (the identity check runs out-of-band on the external vendor and can't be completed for real in-sandbox): verify the decision against the app's OWN stored KYC state — deliver a verification.verified (or verification.requires_input / verification.canceled) event to the webhook endpoint, or poll getVerificationStatus, and confirm the user's stored status flips pendingverified / requires_input / canceled and the UI shows it. Observe the transition, never guess it.
  • KYC-gated features are enforced SERVER-SIDE: while the stored status is not verified, the restricted action is REJECTED by the server (not merely a hidden button); once verified, the same user is allowed. Flipping the stored status changes access after a full reload.
  • processWebhook rejects a forged decision — a bad/missing signature THROWS and becomes a 4xx with NO state change (the user stays unverified); only a signature-verified event may flip stored status.
  • A user CANNOT self-verify: no endpoint accepts a client-sent "verified" flag or lets a caller PATCH their own status, and landing back on returnUrl alone changes nothing — the only path to verified is a signature-verified webhook or a server-side getVerificationStatus check.
  • SECURITY / PRIVACY — identity documents and PII stay server-side: the user is redirected to the provider-hosted flow (the app never receives or stores raw ID images), one user can't read another's session/status/PII by guessing its id, and neither the documents nor the webhook secret are logged in the clear.