KYC Integration
Embed Solidus KYC verification into your application. Users complete identity verification once and receive a Verifiable Credential they can present anywhere, eliminating repeated KYC processes.
Prerequisites
- An account on verify.solidus.network
- An API key (generate one in the Verify dashboard under Settings > API Keys)
Integration Modes
Solidus Verify supports two integration patterns:
| Mode | How it works | Best for |
|---|---|---|
| Hosted flow | Redirect users to the Solidus-hosted verification page | Fastest integration, no UI work needed |
| API flow | Upload documents directly via API calls | Custom UI, mobile apps, embedded experiences |
Base URL
All API requests use the following base URL:
https://verify.solidus.network/v1Authenticate every request with your API key in the Authorization header:
Authorization: Bearer YOUR_API_KEYHosted Flow
Step 1: Create a Verification Session
There is no webhook or prefill field on session creation. Webhooks are a separate resource —
register an endpoint once via POST /webhooks and it receives events for every future session
(see the Webhooks guide). Solidus doesn’t collect pre-fill contact fields at
session-creation time.
const response = await fetch('https://verify.solidus.network/v1/verifications', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({
// The DID of the user being verified (optional for hosted flow)
subjectDid: 'did:solidus:testnet:7Hk3mRtQZv...',
// Verification level: 1 = basic (ID + liveness), 2 = enhanced (+ address)
level: 1,
// Where to redirect after verification completes
redirectUrl: 'https://myapp.com/verification-complete',
}),
})
const session = await response.json()The response nests the session fields under session, alongside the hosted-flow sessionUrl:
{
"session": {
"id": "ver_2xK9mP4qR7nL",
"organizationId": "org_3Vn9wLkDfT",
"status": "pending",
"level": 1,
"sandbox": false,
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"redirectUrl": "https://myapp.com/verification-complete",
"sessionToken": "sess_tok_7Hk3mRtQZv...",
"createdAt": "2026-05-07T12:00:00Z",
"expiresAt": "2026-05-07T13:00:00Z"
},
"sessionUrl": "https://verify.solidus.network/v/s/sess_tok_7Hk3mRtQZv..."
}Step 2: Redirect the User
Send the user to sessionUrl. They will see the Solidus verification interface where they upload documents and complete a liveness check.
// In your frontend
window.location.href = session.sessionUrlStep 3: Handle the Redirect
After verification, the user is redirected to your redirectUrl with the session ID as a query parameter:
https://myapp.com/verification-complete?session=ver_2xK9mP4qR7nL&status=completedStep 4: Check the Result
Query the session status to get the verification result and issued credential.
const result = await fetch(
'https://verify.solidus.network/v1/verifications/ver_2xK9mP4qR7nL',
{
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
}
)
const verification = await result.json()The completed verification response (nested under session, same shape as GET /verifications/:id
in the Verify API reference):
{
"session": {
"id": "ver_2xK9mP4qR7nL",
"status": "completed",
"level": 1,
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"documents": [
{
"type": "passport",
"side": "front",
"status": "verified",
"extractedData": {
"firstName": "Jane",
"lastName": "Doe",
"dateOfBirth": "1990-03-15",
"documentNumber": "***redacted***",
"nationality": "US"
}
}
],
"liveness": { "status": "passed", "score": 0.97, "completedAt": "2026-05-07T12:05:00Z" },
"credentialId": "vc_3Kn8rTpXm2",
"completedAt": "2026-05-07T12:05:30Z",
"createdAt": "2026-05-07T12:00:00Z"
}
}Fetch the issued credential separately from the SDK or chain by its credentialId — the session
response above doesn’t embed the full credential object.
API Flow
Use the API flow when you want full control over the UI. You collect the documents from the user and upload them directly.
Step 1: Create a Session
Same as the hosted flow, but omit redirectUrl:
const response = await fetch('https://verify.solidus.network/v1/verifications', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({
subjectDid: 'did:solidus:testnet:7Hk3mRtQZv...',
level: 1,
}),
})
const { session } = await response.json()
const sessionId = session.idStep 2: Upload Documents
Upload identity documents to the session. Each upload needs side and type fields — there is
no document_front/document_back/selfie type enum. Mark the last document upload
final: 'true' to trigger processing (there is no separate submit step — see Step 3).
// Upload the front of an identity document
const frontUpload = new FormData()
frontUpload.append('file', frontImageFile)
frontUpload.append('side', 'front')
frontUpload.append('type', 'passport') // or driving_license | national_id | residence_permit
frontUpload.append('final', 'false')
await fetch(
\`https://verify.solidus.network/v1/verifications/${sessionId}/documents\`,
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
body: frontUpload,
}
)
// Upload the back of the document (required for ID cards and driver's licenses).
// final: 'true' marks the document step complete and triggers processing.
const backUpload = new FormData()
backUpload.append('file', backImageFile)
backUpload.append('side', 'back')
backUpload.append('type', 'passport')
backUpload.append('final', 'true')
await fetch(
\`https://verify.solidus.network/v1/verifications/${sessionId}/documents\`,
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
body: backUpload,
}
)Document upload fields:
| Field | Type | Required | Description |
|---|---|---|---|
side | front | back | Yes | Which side of the document |
type | passport | driving_license | national_id | residence_permit | Yes | Document type |
final | 'true' | 'false' | No | If 'true', marks the document step complete and triggers processing |
Liveness (face match) is a separate flow — request a challenge with
GET /verifications/:id/liveness-challenge, then upload the response frames with
POST /verifications/:id/liveness. There is no selfie document type or proof_of_address
document type.
Step 3: Processing Starts Automatically
There is no /submit endpoint. Uploading the final required document with final: 'true' (Step 2)
triggers processing directly — poll or wait for a webhook as shown in Step 4.
Step 4: Receive Results
Results arrive via webhook or polling. See the Webhooks guide for webhook setup. To poll:
let status = 'processing'
while (status === 'processing') {
const res = await fetch(
\`https://verify.solidus.network/v1/verifications/${sessionId}\`,
{
headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
}
)
const data = await res.json()
status = data.session.status
if (status === 'processing') {
await new Promise((r) => setTimeout(r, 3000))
}
}Webhook Events
Configure webhooks to receive real-time notifications about verification status changes.
| Event | Description |
|---|---|
verification.completed | Verification finished successfully, credential issued |
verification.failed | Verification failed (document issues, face mismatch, etc.) |
verification.expired | Session expired before the user completed verification |
credential.issued | A new Verifiable Credential was issued |
credential.revoked | An existing credential was revoked |
Default events (if events is omitted when creating the endpoint): verification.completed,
credential.issued.
Webhook payload example:
{
"id": "evt_8nR3kL5mQ2",
"type": "verification.completed",
"createdAt": "2026-05-07T12:05:30Z",
"data": {
"verificationId": "ver_2xK9mP4qR7nL",
"status": "completed",
"subjectDid": "did:solidus:testnet:7Hk3mRtQZv...",
"level": 1,
"credentialId": "urn:uuid:credential-id-here"
}
}See the Webhooks guide for full details on signature verification and delivery handling.
Sandbox Mode
Use sandbox mode during development to test the full flow without real identity documents.
Set sandbox: true when creating a session. Control the outcome with sandboxOutcome:
const response = await fetch('https://verify.solidus.network/v1/verifications', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({
subjectDid: 'did:solidus:testnet:7Hk3mRtQZv...',
level: 1,
sandbox: true,
sandboxOutcome: 'pass', // 'pass', 'fail', 'timeout', or 'document_rejected'
redirectUrl: 'https://localhost:3000/verification-complete',
}),
})Sandbox sessions:
- Skip real document analysis
- Complete within seconds
- Issue test credentials (not valid for production)
- Support all the same API endpoints and webhooks
Available sandbox outcomes:
| Outcome | Behavior |
|---|---|
pass | Verification succeeds, test credential issued |
fail | Verification fails with simulated document issues |
timeout | Simulates a processing timeout for testing error handling |
document_rejected | Simulates a rejected document for testing error handling |
Verification Statuses
A session moves through these statuses:
pending ──> processing ──> completed
│
├──> failed
│
└──> expired| Status | Description |
|---|---|
pending | Session created, waiting for user to start |
processing | Documents uploaded, verification in progress |
completed | Verification passed, credential issued |
failed | Verification failed |
expired | Session expired (default: 1 hour) |
Error Handling
The API returns standard HTTP status codes with error details:
{
"error": {
"code": "invalid_document",
"message": "The uploaded document could not be read. Please upload a clear photo.",
"details": {
"documentType": "document_front",
"reason": "blur_detected"
}
}
}Common error codes:
| Code | Description |
|---|---|
invalid_document | Document image is blurry, cropped, or unreadable |
face_mismatch | Selfie does not match the document photo |
document_expired | The identity document has expired |
unsupported_document | Document type or country not supported |
session_expired | Verification session has expired |
rate_limit_exceeded | Too many requests (limit: 100 requests/minute) |
Next Steps
- Webhooks — set up and manage webhook endpoints
- Credential Flow — understand the full credential lifecycle
- Express.js Middleware — verify credentials in your API