Northwind Finance.
Verified customers only.
Verify your identity once with Crivacy and access Northwind instantly. No documents are shared with us, and your credential stays yours on chain.
Sign in
Existing account
Register
Create an account
What we use from Crivacy
The integration shape
Pick any step to inspect the snippet. Same code the docs and the dashboard quickstart drawer ship.
Frontend
Drop in button
One script tag plus a button[data-crivacy-verify]. The script handles PKCE state, verifier and the redirect.
<link rel="stylesheet" href="https://app.crivacy.xyz/assets/crivacy/v1/button.css">
<script src="https://app.crivacy.xyz/assets/crivacy/v1/crivacy.js" defer></script>
<button
class="crivacy-button"
data-crivacy-verify
data-client-id="crv_oauth_live_6Mm10CiifQlDmIP9F8UDfCs2"
data-redirect-uri="https://demo.crivacy.xyz/callback"
data-scope="openid kyc credential kyc:scores">
<svg class="crivacy-button__icon" viewBox="0 0 200 168.71" fill="currentColor" aria-hidden="true">
<path d="M16.06,90.54c6.97,4.17,13.94,8.33,20.91,12.5-.73,1.32-1.64,3.18-2.39,5.53,0,0-.76,2.87-1.21,5.5-.6,3.54-.15,11.95,6.17,17.95,5.82,5.52,14.23,6.72,19.82,4.78,3.73-1.29,7.07-3.4,8-4.09,2.26-1.67,3.88-3.41,4.96-4.72,1.5,2.17,3.58,5.79,4.61,10.69.64,3.07.68,5.76.55,7.82-7.85,6.31-15.7,12.62-23.55,18.93l-46.87-30.98,9-43.92Z"/>
<path d="M182.89,90.54c-6.97,4.17-13.94,8.33-20.91,12.5.73,1.32,1.64,3.18,2.39,5.53,0,0,.76,2.87,1.21,5.5.6,3.54.15,11.95-6.17,17.95-5.82,5.52-14.23,6.72-19.82,4.78-3.73-1.29-7.07-3.4-8-4.09-2.26-1.67-3.88-3.41-4.96-4.72-1.5,2.17-3.58,5.79-4.61,10.69-.64,3.07-.68,5.76-.55,7.82,7.85,6.31,15.7,12.62,23.55,18.93l46.87-30.98-9-43.92Z"/>
<polygon points="200 28.52 195.87 69.04 118.25 120.34 100 168.71 81.75 120.34 4.13 69.04 0 28.52 42.65 60.42 87.65 49.66 100 0 112.35 49.66 157.35 60.42 200 28.52"/>
</svg>
<span class="crivacy-button__label">Verify with Crivacy</span>
</button>// On GET /callback (your callback route):
import { CrivacyClient } from '@crivacy/js-sdk';
// Confidential client — keep client_secret in env, never commit.
const client = new CrivacyClient({
clientId: process.env.CRIVACY_CLIENT_ID!,
clientSecret: process.env.CRIVACY_CLIENT_SECRET!,
redirectUri: 'https://demo.crivacy.xyz/callback',
});
const { code, codeVerifier } = await client.handleCallback();
const tokens = await client.exchangeCode({ code, codeVerifier });
const claims = await client.getUserinfo(tokens.access_token);
if (!claims.identity_verified) return denyAccess();
grantAccess(claims.sub);const claims = await client.getUserinfo(tokens.access_token);
if (!claims.identity_verified) return denyAccess();
if (!claims.liveness_verified) return denyAccess();
grantAccess(claims.sub);import crypto from 'node:crypto';
function verifyWebhookSignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map((part) => {
const [key, value] = part.split('=');
return [key, value];
})
);
const timestamp = parts.t;
const receivedHmac = parts.v1;
// Reject if timestamp is older than 5 minutes
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (Math.abs(age) > 300) {
throw new Error('Webhook timestamp too old or too far in the future');
}
const expectedHmac = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const isValid = crypto.timingSafeEqual(
Buffer.from(receivedHmac, 'hex'),
Buffer.from(expectedHmac, 'hex')
);
if (!isValid) {
throw new Error('Invalid webhook signature');
}
return JSON.parse(rawBody);
}
// Usage in Express
app.post('/webhooks/crivacy', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifyWebhookSignature(
req.body.toString(),
req.headers['x-crivacy-signature'],
process.env.CRIVACY_WEBHOOK_SECRET
);
// Process event...
res.status(200).json({ received: true });
} catch (err) {
res.status(401).json({ error: err.message });
}
});Stack
What runs underneath
Sepolia testnet for the credential, Crivacy as the gateway, Didit handling document and biometric capture, this dApp on Next.js.
- ChainSepolia
- GatewayCrivacy
- KYC vendorDidit
- RuntimeNext.js