How to Use the Stripe Identity API: Developer Guide to ID Verification

Complete developer guide to the Stripe Identity API: VerificationSession, webhooks, verified outputs, pricing, and code examples in curl and Node.

Ashley Innocent

Ashley Innocent

23 April 2026

How to Use the Stripe Identity API: Developer Guide to ID Verification

Apidog for Enterprise

On-Premises Deploy

SSO & RBAC

SOC 2 Compliant

Explore Apidog Enterprise

Verifying a user’s real-world identity is one of those tasks that looks simple on a whiteboard and turns into a months-long project the moment you start building it. You need document capture, OCR, face matching, liveness detection, fraud signals, and coverage for dozens of ID types across countries. The Stripe Identity API packages all of that into a single integration, so you can stand up a production-ready ID verification flow in an afternoon instead of a quarter.

This guide walks through every step a developer needs to ship Stripe Identity: enabling it in the dashboard, creating a VerificationSession, choosing between the hosted redirect and embedded Stripe.js component, handling webhooks, and reading verified outputs. You will see curl and Node examples, error handling patterns, and how to test the whole flow locally with Apidog. If you are evaluating alternatives first, skim our roundup of the best KYC APIs before committing.

button

Stripe Identity is a natural fit for teams that already use Stripe for payments, but it works as a standalone product too. The official Stripe Identity docs cover the product surface, and this post fills in the developer-experience gaps: what happens on the wire, which fields matter, and where the common gotchas live.

TL;DR

What is the Stripe Identity API?

Stripe Identity is an ID verification product built on top of Stripe’s core API surface. It gives you a single endpoint family (/v1/identity/verification_sessions) that orchestrates document capture, data extraction, face matching, and fraud scoring. The output is a signed, structured record you can trust: a verified full name, date of birth, address, and optional ID number, paired with the original document images stored in Stripe’s vault.

Under the hood, Stripe uses the same session-plus-webhook pattern you already know from Checkout and Payment Intents. You create a session server-side, Stripe handles the user-facing capture UI, and you get notified when the result is ready. If you have built a Checkout flow before, Identity will feel familiar within minutes.

Authentication and setup

Before you call the API, turn on Stripe Identity in the dashboard. Go to Dashboard > Settings > Identity, accept the terms, and fill in the business details Stripe needs for KYC compliance on its side. The toggle activates Identity for both test mode and live mode on your account.

Authentication uses your standard Stripe secret key. Test keys start with sk_test_, and live keys start with sk_live_. Stripe Identity does not require a separate credential, which keeps the integration surface small.

Install the Node SDK:

npm install stripe

Then initialize a client. Pin the API version so Stripe never surprises you with a schema change:

import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: "2024-06-20",
});

You can now call every endpoint under stripe.identity.verificationSessions.

Core endpoints

Creating a VerificationSession

The VerificationSession is the single object you create per user verification attempt. It controls which document types are accepted, whether a selfie is required, and which fields get returned to your backend.

With curl:

curl https://api.stripe.com/v1/identity/verification_sessions \
  -u "$STRIPE_SECRET_KEY:" \
  -d "type=document" \
  -d "options[document][require_matching_selfie]=true" \
  -d "options[document][require_id_number]=true" \
  -d "options[document][allowed_types][]=driving_license" \
  -d "options[document][allowed_types][]=passport" \
  -d "verified_outputs[]=first_name" \
  -d "verified_outputs[]=last_name" \
  -d "verified_outputs[]=dob" \
  -d "verified_outputs[]=address" \
  -d "verified_outputs[]=id_number" \
  -d "metadata[user_id]=usr_7f3k2"

With the Node SDK:

const session = await stripe.identity.verificationSessions.create({
  type: "document",
  options: {
    document: {
      require_matching_selfie: true,
      require_id_number: true,
      allowed_types: ["driving_license", "passport", "id_card"],
    },
  },
  verified_outputs: [
    "first_name",
    "last_name",
    "dob",
    "address",
    "id_number",
  ],
  metadata: { user_id: "usr_7f3k2" },
});

// Send one of these to the client:
// session.url              -> hosted redirect
// session.client_secret    -> Stripe.js embedded component

A few fields deserve attention. type: "document" tells Stripe to run a document check; the alternative, id_number, does a US-only SSN-style lookup without collecting an ID. allowed_types restricts which document categories the user can upload, which matters for compliance programs that only accept government-issued photo ID. verified_outputs is the allowlist of fields you want returned; Stripe will not expose any data you did not ask for, which keeps your data-minimization posture clean.

Hosted verification redirect vs. embedded Stripe.js

Stripe gives you two integration shapes. The hosted redirect is the fastest path: send the user to session.url, Stripe handles the entire capture experience on a stripe.com domain, and the user bounces back to your return_url. You write roughly ten lines of code.

The embedded flow uses Stripe.js and the @stripe/stripe-js package to mount a verification modal inside your own app. You pass session.client_secret to the frontend and call stripe.verifyIdentity(clientSecret). This keeps users in your product and gives you design control over the surrounding page. Choose it when verification happens mid-flow in a signup or KYC step; choose the redirect when verification is a discrete task.

Webhooks

Never rely on the client redirect to tell you a verification succeeded; always confirm on the backend via webhooks. Register an endpoint at Dashboard > Developers > Webhooks and subscribe to:

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), async (req, res) => {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers["stripe-signature"],
    process.env.STRIPE_WEBHOOK_SECRET
  );

  if (event.type === "identity.verification_session.verified") {
    const session = event.data.object;
    await markUserVerified(session.metadata.user_id, session.id);
  }

  if (event.type === "identity.verification_session.requires_input") {
    await notifyUserToRetry(event.data.object.metadata.user_id);
  }

  res.json({ received: true });
});

Retrieving verified outputs

The webhook payload tells you the session verified, but it does not include the sensitive fields. To read verified outputs, call verificationSessions.retrieve with expand: ["verified_outputs"]:

const session = await stripe.identity.verificationSessions.retrieve(
  "vs_1N...",
  { expand: ["verified_outputs"] }
);

const { first_name, last_name, dob, address, id_number } = session.verified_outputs;

Stripe returns id_number only once; store it encrypted on your side immediately. The document images themselves stay in Stripe’s vault and are accessible through the dashboard for audit.

Common errors and rate limits

The most frequent failure is verification_session.requires_input with a code like document_unverified_other or selfie_face_mismatch. Handle these by surfacing a friendly retry UI; the same session can be reused by calling verificationSessions.cancel and creating a new one, or by redirecting to the same session.url while it is still open.

Stripe applies standard API rate limits of 100 requests per second in live mode and 25 per second in test mode. The /identity/verification_sessions endpoints fall under the same bucket as the rest of the API. If you hit limits, you will see HTTP 429 with a Retry-After header; use exponential backoff and respect the header.

Other errors to watch: unsupported_document_type when the user uploads an ID outside your allowed_types list, and country_not_supported when someone tries a document from one of the countries Stripe Identity does not cover yet.

Stripe Identity pricing

Stripe Identity costs $1.50 per verification in the United States. International pricing varies; most European countries land around $1.50 to $2.00, and Stripe publishes the full per-country breakdown on its pricing page. A verification attempt that ends in requires_input does not count as a billable event; only completed verified sessions bill.

For volume customers, Stripe offers custom pricing that can cut the per-check fee significantly. If you are processing more than 10,000 verifications a month, talk to sales.

Testing Stripe Identity with Apidog

API playgrounds beat hand-rolling curl commands every time, especially when you are iterating on webhook payloads. Apidog imports Stripe’s OpenAPI spec directly, so every field on the VerificationSession object shows up with inline documentation. You can fire real requests at Stripe’s test environment, inspect the response, and replay it as many times as you need.

The webhook testing side is where Apidog saves the most time. You can mock identity.verification_session.verified events locally, fire them at your dev server, and step through your handler without needing a real verification to complete. If you are comparing workflows, our guide on API testing without Postman in 2026 has a deeper walkthrough. Download Apidog to get the desktop client.

FAQ

What is the difference between Stripe Identity and Stripe’s regular KYC? Stripe’s built-in KYC verifies business owners for Connect and Payments accounts. Stripe Identity is a standalone API for verifying your end users; the two systems do not share verification results.

Can I reuse a verified identity across multiple sessions? Yes. Once a session is verified, the verified_outputs are permanent on that session object. If you need to re-verify for a new event, create a fresh session and link it to the user record on your side.

Does Stripe Identity work outside payments? Absolutely. Many customers use it purely as a KYC layer and never touch Stripe’s payments surface. If you need broader sanctions screening on top of ID verification, pair it with a dedicated AML screening API.

How does Stripe Identity compare to Plaid Identity Verification? Stripe focuses on document plus selfie; Plaid leans into bank-verified identity data. See our Plaid API guide for the other approach.

Is selfie liveness mandatory? No. Set options.document.require_matching_selfie to false if you only need a document check. Most fraud teams keep it on, because passive liveness catches a lot of low-effort attacks.

What countries does Stripe Identity cover? 35+ countries across North America, Europe, and parts of Asia-Pacific, with localized document parsers for each. Stripe publishes the live country list in its docs and adds new markets regularly.

Explore more

How to Validate Your API Against Its Spec Without Dredd

How to Validate Your API Against Its Spec Without Dredd

Dredd checks your running API against its spec, but needs a hooks file and a loose spec. Here is an alternative that keeps the spec and tests in one npm CLI.

15 June 2026

How to Install the Apidog CLI With an AI Coding Agent

How to Install the Apidog CLI With an AI Coding Agent

Let your AI coding agent install the Apidog CLI for you. Exact prompts for Claude Code, Cursor, and Copilot, the commands they run, and how to verify each step.

15 June 2026

How to Run Automated API Tests in Azure Pipelines (Step-by-Step)

How to Run Automated API Tests in Azure Pipelines (Step-by-Step)

Run automated API tests in Azure Pipelines step by step: design scenarios in Apidog, trigger them with the Apidog CLI, and fail the build on regressions.

15 June 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs

How to Use the Stripe Identity API: Developer Guide to ID Verification