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:
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.
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"
}'
Verification complete
The email or phone number is marked as verified in the identity’s metadata.
Link strategy
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:
Validates the token
Marks the email as verified
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:
State Description 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.
Status: 400 Bad RequestVerification has been disabled in the configuration. Solution: Enable verification in the Kratos configuration.
Status: 403 ForbiddenCSRF token validation failed. Solution: Ensure cookies are properly forwarded in browser flows.
Status: 400 Bad RequestThe address could not be verified (invalid code or expired link). Solution: Request a new verification code or link.
Complete flow example
Browser flow (code)
API flow (code)
Node.js SDK
# 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
Endpoint Method Description /self-service/verification/browserGET Initialize browser verification flow /self-service/verification/apiGET Initialize API verification flow /self-service/verification/flowsGET Get verification flow by ID /self-service/verificationPOST Submit verification flow
All endpoints are defined in selfservice/flow/verification/handler.go:32-37