Skip to content

Stripe Identity Fresh

Verify your users' identity documents. Collect and verify passports, driver's licenses, and national IDs using a modal flow embedded in your site.

How it works

  1. Your server creates a VerificationSession
  2. You return the client_secret to the frontend
  3. The user clicks a verify button that opens the Stripe document upload modal
  4. The user captures/uploads their document
  5. Stripe verifies the document and returns results via webhook
  6. You show a confirmation page while verification processes

Before you begin

  1. Fill out the Stripe Identity application in Dashboard → Identity
  2. Optionally customize brand settings in Dashboard → Settings → Branding

Step 1: Create a VerificationSession (server-side)

Create the session on your server — never on the client. This prevents users from overriding verification options or incurring charges on your account.

javascript
const stripe = require('stripe')('sk_test_YOUR_KEY');

// POST /create-verification-session
const verificationSession = await stripe.identity.verificationSessions.create({
  type: 'document',
  provided_details: {
    email: 'user@example.com',
  },
  metadata: {
    user_id: 'USER_ID',
  },
});

// Return only the client_secret to the frontend
const clientSecret = verificationSession.client_secret;

The client_secret is single-use and expires after 24 hours. Send only the client secret to the frontend — never the full session object.

Test your endpoint:

bash
curl -X POST -is "http://localhost:4242/create-verification-session" -d ""

Expected response:

bash
HTTP/1.1 200 OK
Content-Type: application/json

{ "id": "vs_QdfQQ6xfGNJR7ogV6", "client_secret": "vs_QdfQQ6xfGNJR7ogV6_secret_live_..." }

Step 2: Show the document upload modal (client-side)

Add Stripe.js to your page and initialize it with your publishable key:

html
<html>
  <head>
    <title>Verify your identity</title>
    <script src="https://js.stripe.com/v3/"></script>
  </head>
  <body>
    <button id="verify-button">Verify</button>

    <script>
      var stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
      var verifyButton = document.getElementById('verify-button');

      verifyButton.addEventListener('click', function() {
        fetch('/create-verification-session', { method: 'POST' })
          .then(function(response) { return response.json(); })
          .then(function(session) {
            return stripe.verifyIdentity(session.client_secret);
          })
          .then(function(result) {
            if (result.error) {
              alert(result.error.message);
            }
          })
          .catch(function(error) {
            console.error('Error:', error);
          });
      });
    </script>
  </body>
</html>

Step 3: Show a confirmation page

After the user submits their document, redirect to a confirmation page:

javascript
verifyButton.addEventListener('click', function() {
  fetch('/create-verification-session', { method: 'POST' })
    .then(response => response.json())
    .then(session => stripe.verifyIdentity(session.client_secret))
    .then(result => {
      if (result.error) {
        alert(result.error.message);
      } else {
        window.location.href = '/confirmation';
      }
    });
});
Error codeDescription
consent_declinedUser declined verification by Stripe. May need to offer an alternative non-biometric means.
device_unsupportedVerification requires a camera and the device doesn't have one.
under_supported_ageStripe doesn't verify users under the age of majority.
phone_otp_declinedUser unable to verify the provided phone number.
email_verification_declinedUser unable to verify the provided email address.

Handle verification results

Listen for webhooks to receive verification outcomes:

javascript
const express = require('express');
const stripe = require('stripe')('sk_test_YOUR_KEY');
const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.WEBHOOK_SECRET);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case 'identity.verification_session.verified':
      const session = event.data.object;
      // Verification succeeded — access verified outputs
      const verifiedOutputs = session.verified_outputs;
      console.log('Verified name:', verifiedOutputs.name);
      console.log('Verified DOB:', verifiedOutputs.dob);
      break;

    case 'identity.verification_session.requires_input':
      // Verification failed — check last_error
      console.log('Verification failed:', event.data.object.last_error.reason);
      break;
  }

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

Webhook events

EventWhen it fires
identity.verification_session.createdSession created
identity.verification_session.processingDocument submitted, processing
identity.verification_session.verifiedVerification succeeded
identity.verification_session.requires_inputVerification failed — user input needed
identity.verification_session.canceledSession canceled

VerificationSession object

Key fields on the VerificationSession object:

FieldDescription
idSession ID (vs_...)
client_secretSingle-use secret for the frontend modal
statusrequires_input, processing, verified, canceled
typeVerification type — document or id_number
verified_outputsVerified data (name, DOB, address, ID number)
last_errorError code and reason if verification failed
metadataYour key-value data attached to the session

Testing

Use these test values in sandbox:

Test scenarioHow to trigger
Successful verificationUse any test document in sandbox modal
Failed verificationClose the modal without submitting
Processing delayVerification briefly shows processing status before completing

Sandbox sessions are never charged.

Stripe API Reference - Self-contained docs reference, refreshed 2026-05-18