Skip to main content
The verification flow enables users to verify their email addresses or phone numbers. This ensures users have access to the contact methods they registered with.

Flow initialization

Verification flows can be initiated for browser-based applications or API clients.

Browser flows

Initialize a verification flow for browser applications:
curl -X GET \
  'https://{project}.projects.oryapis.com/self-service/verification/browser' \
  -H 'Accept: application/json'
Query parameters:
  • return_to - URL to redirect to after successful verification
Response:
{
  "id": "e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b",
  "type": "browser",
  "expires_at": "2026-03-03T12:00:00Z",
  "issued_at": "2026-03-03T11:00:00Z",
  "request_url": "https://{project}.projects.oryapis.com/self-service/verification/browser",
  "ui": {
    "action": "https://{project}.projects.oryapis.com/self-service/verification?flow=e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b",
    "method": "POST",
    "nodes": [...]
  },
  "state": "choose_method"
}

API flows

For native applications (mobile, desktop, server-to-server):
curl -X GET \
  'https://{project}.projects.oryapis.com/self-service/verification/api' \
  -H 'Accept: application/json'
Verification can be disabled in the configuration. If disabled, this endpoint returns a 400 error with message “Verification is not allowed because it was disabled.”

Verification strategies

Kratos supports two verification strategies that can be used for different verification methods.

Code strategy

Send a one-time verification code via email or SMS:
1

Request verification code

Submit the email or phone number to verify:
curl -X POST \
  'https://{project}.projects.oryapis.com/self-service/verification?flow=<flow_id>' \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "code",
    "email": "user@example.com"
  }'
A verification code is sent to the specified address.
2

Submit verification code

Enter the code received via email or SMS:
curl -X POST \
  'https://{project}.projects.oryapis.com/self-service/verification?flow=<flow_id>' \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "code",
    "code": "123456"
  }'
3

Verification complete

The email or phone number is marked as verified in the identity’s metadata.
Send a magic verification link via email:
curl -X POST \
  'https://{project}.projects.oryapis.com/self-service/verification?flow=<flow_id>' \
  -H 'Content-Type: application/json' \
  -d '{
    "method": "link",
    "email": "user@example.com"
  }'
The user receives an email with a verification link. Clicking the link:
  1. Validates the token
  2. Marks the email as verified
  3. Redirects to the configured return URL

Automatic verification

Configure automatic verification during registration:
selfservice:
  flows:
    registration:
      after:
        password:
          hooks:
            - hook: verify
This sends a verification email immediately after registration.

Configuration

Enable and configure verification in your Kratos configuration:
selfservice:
  methods:
    link:
      enabled: true
      config:
        lifespan: 24h
    code:
      enabled: true
      config:
        lifespan: 1h
  
  flows:
    verification:
      enabled: true
      lifespan: 1h
      ui_url: https://your-app.com/verification
      after:
        default_browser_return_url: https://your-app.com/

Flow states

The verification flow progresses through several states:
StateDescription
choose_methodInitial state - user enters email/phone
sent_emailVerification email has been sent
passed_challengeVerification code/link validated successfully

Fetching an existing flow

Retrieve a verification flow by its ID:
curl -X GET \
  'https://{project}.projects.oryapis.com/self-service/verification/flows?id=<flow_id>'

Identity schema configuration

Mark fields as verifiable in your identity schema:
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "traits": {
      "type": "object",
      "properties": {
        "email": {
          "type": "string",
          "format": "email",
          "ory.sh/kratos": {
            "credentials": {
              "password": {
                "identifier": true
              }
            },
            "verification": {
              "via": "email"
            }
          }
        },
        "phone": {
          "type": "string",
          "ory.sh/kratos": {
            "verification": {
              "via": "sms"
            }
          }
        }
      },
      "required": ["email"]
    }
  }
}

Error handling

Status: 410 GoneThe verification flow has expired.Solution: Initialize a new flow.Browser flows include a redirect_to URL in the error response.

Complete flow example

# 1. Initialize the flow
curl -X GET 'https://{project}.projects.oryapis.com/self-service/verification/browser'

# Browser is redirected to UI with flow ID
# User visits: https://your-app.com/verification?flow=<flow_id>

# 2. Request verification code
curl -X POST \
  'https://{project}.projects.oryapis.com/self-service/verification?flow=<flow_id>' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'method=code&email=user@example.com&csrf_token=...'

# 3. Submit code from email
curl -X POST \
  'https://{project}.projects.oryapis.com/self-service/verification?flow=<flow_id>' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'method=code&code=123456&csrf_token=...'

# User is redirected to success page

Email templates

Customize verification emails in your Kratos configuration:
courier:
  templates:
    verification:
      valid:
        email:
          body:
            html: /path/to/verification.html.tmpl
            plaintext: /path/to/verification.txt.tmpl
          subject: Verify your email address
Template variables:
  • VerificationURL - The verification link (link strategy)
  • VerificationCode - The verification code (code strategy)
  • Identity - The user’s identity traits

Checking verification status

Check if an address is verified in the identity metadata:
curl -X GET \
  'https://{project}.projects.oryapis.com/sessions/whoami' \
  -H 'Cookie: ory_kratos_session=...'
{
  "identity": {
    "verifiable_addresses": [
      {
        "value": "user@example.com",
        "verified": true,
        "via": "email",
        "status": "completed"
      }
    ]
  }
}

Requiring verified addresses

Enforce verified emails before allowing certain actions:
selfservice:
  flows:
    login:
      after:
        password:
          hooks:
            - hook: require_verified_address
This prevents login if the user’s email is not verified.

API reference

EndpointMethodDescription
/self-service/verification/browserGETInitialize browser verification flow
/self-service/verification/apiGETInitialize API verification flow
/self-service/verification/flowsGETGet verification flow by ID
/self-service/verificationPOSTSubmit verification flow
All endpoints are defined in selfservice/flow/verification/handler.go:32-37

Build docs developers (and LLMs) love