This document is for developers building a custom UI to integrate with Hypersign KYC and SSI APIs. By using these APIs directly instead of the pre-built widget, you maintain total control over your UI/UX while leveraging decentralized identity (DID) infrastructure.
The integration follows a Security-First Handshake model:
- Backend: Securely manages the
API_SECRET, fetches administrative tokens, and registers User DIDs. - Frontend: Handles the camera interface, performs OCR extraction, executes Face Match, and signs the final Consent via a Verifiable Presentation.
const KYC_BASE_URL = "https://api.cavach.hypersign.id";
const SSI_BASE_URL = "https://api.entity.hypersign.id";
const DEVELOPER_DASHBOARD_SERVICE_BASE_URL = "https://api.entity.dashboard.hypersign.id"- Dashboard Access: Setup your account in the Hypersign Dashboard.
- Secrets: Download your
SSI_API_SECRETandKYC_API_SECRETfrom the app settings. - Issuer Information: Copy your Issuer DID and Verification Method from the SSI dashboard:
const X_ISSUER_DID = "did:hid:z6MkmEFC8N1AUsinEBNSszXoepHb45p38ZwidV58r1HPqCkU"; const X_ISSUER_VERMETHOD_ID = "did:hid:z6MkmEFC8N1AUsinEBNSszXoepHb45p38ZwidV58r1HPqCkU#key-1";
The backend acts as a secure proxy to ensure administrative keys are never exposed to the browser.
Administrative tokens for KYC and SSI services should be cached locally and refreshed only upon expiry.
async function fetchAdminAccessToken(apiSecret, serviceType) {
const url = `${DEVELOPER_DASHBOARD_SERVICE_BASE_URL}/api/v1/app/oauth?grant_type=${serviceType}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'X-Api-Secret-Key': apiSecret,
'Accept': 'application/json'
}
});
const result = await response.json();
if (!response.ok) throw new Error(`Auth failed: ${result.message}`);
return result.access_token;
}Create a unique session for the user's verification journey.
async function initializeVerificationSession
(kycAdminToken){
const response = await fetch(`${KYC_BASE_URL}/api/v2/session`, {
method: 'POST',
headers: {
'x-kyc-access-token': kycAdminToken,
'Content-Type': 'application/json'
}
});
const result = await response.json();
return result.data.sessionId;
}Every user requires a DID to sign their final verification results.
const users = {}
async function registerUserDid(ssiAdminToken, email) {
const user = users[email]
// check if user already exists
if(user) {
return {
did: user.did,
verificationMethodId: user.did + "#key1"
}
}
// if not proceed to create a new DID
const res = await fetch(`${SSI_BASE_URL}/api/v1/did/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${ssiAdminToken}`
},
body: JSON.stringify({ namespace: ''})
});
const result = await res.json();
// Find the Ed25519 key method required for signatures
const method = result.metadata.didDocument.verificationMethods
.find(m => m.type === 'Ed25519VerificationKey2020');
users[email] = { did: result.did }
return {
did: result.did,
verificationMethodId: method.id
};
}This token authorizes the frontend to perform biometric actions for this specific session only.
async function generateKycUserSessionToken(claims, kycAdminToken, ssiAdminToken, sessionId) {
// A. Issue a DID-signed JWT
const ssiRes = await fetch(`${SSI_BASE_URL}/api/v1/did/auth/issue-jwt`, {
method: "POST",
headers: { "Authorization": `Bearer ${ssiAdminToken}`, "Content-Type": "application/json" },
body: JSON.stringify({
issuer: { verificationmethodId: X_ISSUER_VERMETHOD_ID, did: X_ISSUER_DID },
audience: KYC_BASE_URL,
claims: claims,
ttlSeconds: 3600
})
});
const { accessToken: didJwt } = await ssiRes.json();
// B. Exchange JWT for the KYC User Access Token
const kycRes = await fetch(`${KYC_BASE_URL}/api/v2/auth/exchange`, {
method: "POST",
headers: {
"x-ssi-access-token": ssiAdminToken,
"x-kyc-access-token": kycAdminToken,
"Authorization": `Bearer ${didJwt}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ provider: "client_auth", sessionId })
});
const finalResult = await kycRes.json();
return finalResult.data.kycServiceUserAccessToken;
}app.get('/get-required-tokens-and-session-for-a-user', async (req, res) => {
try {
// 1. Prepare Administrative Access Tokens (using file-based cache)
const kycAdminToken = await fetchAdminAccessToken(KYC_API_SECRET, 'access_service_kyc') // Generate KYC Access Token
const ssiAdminToken = fetchAdminAccessToken(SSI_API_SECRET, 'access_service_ssi') // Generate SSI Access Token
// 2. Initialize the KYC Verification Session
const sessionId = await initializeVerificationSession(kycAdminToken);
let userData = {
name: "John",
email: "john@gmail.com", // Mandatory
userDid: "",
};
// 3. Register a new User DID
const userDidMetadata = await registerUserDid(ssiAdminToken, userData.email);
// 4. Prepare User Claims for the DID JWT
userData.userDid = userDidMetadata.did
// 5. Generate the final User-specific Bearer Token
const userBearerToken = await generateKycUserSessionToken(
userData,
kycAdminToken,
ssiAdminToken,
sessionId
);
// 6. Return comprehensive credentials to the client
res.json({
kycAdminToken,
ssiAdminToken,
userBearerToken,
issuerDid: X_ISSUER_DID,
issuerVerificationMethodId: X_ISSUER_VERMETHOD_ID,
sessionId,
userDid: userDidMetadata.did,
userVerificationMethodId: userDidMetadata.verificationMethodId
});
} catch (error) {
console.error(`[Onboarding Flow Error]: ${error.message}`);
res.status(400).json({ error: error.message });
}
});Call your backend endpoint to receive all necessary session and admin tokens.
async function initKycUI() {
const response = await fetch(`/get-required-tokens-and-session-for-a-user`);
const data = await response.json();
// Map backend response to local state
const state = {
tokens: {
kycAdmin: data.kycAdminToken,
ssiAdmin: data.ssiAdminToken,
userBearer: data.userBearerToken
},
session: { id: data.sessionId },
user: { did: data.userDid, methodId: data.userVerificationMethodId },
issuer: { did: data.issuerDid, methodId: data.issuerVerificationMethodId }
};
}After capturing the ID document via the camera, send the Base64 image for extraction.
For passports, only the front image (the information page) is required in the request payload.
async function processDocumentExtraction(base64Image) {
const response = await fetch(`${KYC_BASE_URL}/api/v2/documents/extract`, {
method: 'POST',
headers: {
'x-kyc-access-token': state.tokens.kycAdmin,
'Authorization': `Bearer ${state.tokens.userBearer}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
documentFront: base64Image,
sessionId: state.session.id,
documentType: "PASSPORT"
})
});
const result = await response.json();
state.session.extractionToken = result.data.extractionToken;
}For Government IDs, the API expects both front and back images, along with a specific ISO country code.
async function processDocumentExtraction(base64Image) {
const response = await fetch(`${KYC_BASE_URL}/api/v2/documents/extract`, {
method: 'POST',
headers: {
'x-kyc-access-token': state.tokens.kycAdmin,
'Authorization': `Bearer ${state.tokens.userBearer}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
documentFront: base64ImageFront,
documentBack: base64ImageBack,
sessionId: state.session.id,
documentType: "GOVT_ID",
countryCode: "IND" // ISO Alpha-3 code (e.g., IND for India)
})
});
const result = await response.json();
state.session.extractionToken = result.data.extractionToken;
}Matches the selfie against the extracted document data.
async function performIdentityMatch(base64Selfie) {
const response = await fetch(`${KYC_BASE_URL}/api/v2/biometrics/verify`, {
method: 'POST',
headers: {
'x-kyc-access-token': state.tokens.kycAdmin,
'x-issuer-did': state.issuer.did,
'x-issuer-did-ver-method': state.issuer.methodId,
'Authorization': `Bearer ${state.tokens.userBearer}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
documentToken: state.session.extractionToken,
sessionId: state.session.id,
selfieImage: base64Selfie,
holderDid: state.user.did,
})
});
const result = await response.json();
if (result.success) {
await submitUserConsent(result.data.credentials);
}
}Finalize the process by creating a Verifiable Presentation and submitting it to the KYC service.
async function submitUserConsent(credentials) {
// 1. Create Presentation via SSI API
const vpRes = await fetch(`${SSI_BASE_URL}/api/v1/presentation`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${state.tokens.ssiAdmin}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
credentialDocuments: credentials,
holderDid: state.user.did,
challenge: state.session.id,
domain: window.location.origin,
verificationMethodId: state.user.methodId
})
});
const vpData = await vpRes.json();
// 2. Submit Final Consent to KYC Service
await fetch(`${KYC_BASE_URL}/api/v2/consents`, {
method: 'POST',
headers: {
'x-kyc-access-token': state.tokens.kycAdmin,
'Authorization': `Bearer ${state.tokens.userBearer}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
sessionId: state.session.id,
presentation: vpData.presentation
})
});
}