You need three things: an API key, the SDK, and 15 minutes. Here’s the complete integration path with code examples in Python, Node, and cURL.
Prerequisites: An API key from your dashboard, an HTTP client, and a JWKS-aware JWT library (any standard one will do). Three endpoints — /enroll, /verify, /delete. Every snippet below works with plain HTTP if you’d rather skip the SDK.
Three endpoints. One SDK. Fifteen minutes. Biometric step-up verification running in your staging environment, ready to go live whenever you are.
1. Install the SDK
Python
pip install lorica-sdk
Node.js
npm install lorica-node
No SDK? Use cURL
Every endpoint works with raw HTTP. No SDK required. The examples below show all three approaches.
2. Enroll a user
Enrollment captures the user’s face and stores an encrypted embedding. This happens once — typically during onboarding or the first time they encounter a high-risk action.
Python
from lorica import LoricaClient
import base64
client = LoricaClient(api_key="lk_demo_REPLACE_WITH_YOUR_KEY")
# Read face image and encode
with open("face.jpg", "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
result = client.enroll(
user_id="usr_8f3a2b1c",
image=image_b64
)
print(result)
# {"enrolled": true, "user_id": "usr_8f3a2b1c"}
Node.js
const Lorica = require('lorica-node');
const fs = require('fs');
const client = new Lorica({ apiKey: 'lk_demo_REPLACE_WITH_YOUR_KEY' });
const image = fs.readFileSync('face.jpg').toString('base64');
const result = await client.enroll({
userId: 'usr_8f3a2b1c',
image: image
});
console.log(result);
// { enrolled: true, userId: 'usr_8f3a2b1c' }
cURL
curl -X POST https://api.loricaapi.com/enroll \
-H "X-API-Key: lk_demo_REPLACE_WITH_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_8f3a2b1c",
"image": "'$(base64 -w0 face.jpg)'"
}'
3. Verify a transaction
This is the call that matters. When a user initiates a high-risk action — a withdrawal, a large transfer, a whitelist change — your platform captures a fresh face image and sends it to Lorica with the action context. The response is a signed JWT proving the human authorized that specific action.
Python
# Capture fresh image from webcam or mobile camera
with open("live_capture.jpg", "rb") as f:
live_image = base64.b64encode(f.read()).decode()
result = client.verify(
user_id="usr_8f3a2b1c",
image=live_image,
action_context="withdraw_50000_usdc",
liveness_mode="passive"
)
if result["match"]:
jwt_token = result["jwt"]
print(f"Verified! Confidence: {result['confidence']}")
# Proceed with the withdrawal
# Store the JWT as proof of authorization
else:
print(f"Rejected: {result['rejection_reason']}")
# Block the withdrawal
# Alert the security team
Node.js
const liveImage = fs.readFileSync('live_capture.jpg')
.toString('base64');
const result = await client.verify({
userId: 'usr_8f3a2b1c',
image: liveImage,
actionContext: 'withdraw_50000_usdc',
livenessMode: 'passive'
});
if (result.match) {
console.log(`Verified! Confidence: ${result.confidence}`);
// Proceed with withdrawal, store JWT
} else {
console.log(`Rejected: ${result.rejectionReason}`);
// Block withdrawal, alert security
}
cURL
curl -X POST https://api.loricaapi.com/verify \
-H "X-API-Key: lk_demo_REPLACE_WITH_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_8f3a2b1c",
"image": "'$(base64 -w0 live_capture.jpg)'"
"action_context": "withdraw_50000_usdc",
"liveness_mode": "passive"
}'
What you get back
A successful verification returns a signed JWT containing everything your auditors need:
{
"match": true,
"confidence": 0.94,
"liveness_score": 0.91,
"jwt": "eyJhbGciOiJIUzI1NiIs...",
"verification_id": "ver_a1b2c3d4",
"decoded_jwt": {
"user_id": "usr_8f3a2b1c",
"action_verified": "withdraw_50000_usdc",
"confidence": 0.94,
"liveness_score": 0.91,
"liveness_method": "passive",
"verified_at": "2026-04-01T14:32:07Z",
"capture_hash": "a3f8c912...",
"exp": 1743520627
}
}
Store the JWT alongside the transaction record. It’s your cryptographic proof that a specific human authorized a specific action at a specific time.
Where to put the call
The Lorica verification call goes between the user clicking “Confirm” and the transaction executing. In code, that looks like:
# Your existing withdrawal flow
def process_withdrawal(user_id, amount, destination):
# Step 1: Existing checks (balance, limits, etc.)
validate_withdrawal(user_id, amount, destination)
# Step 2: Capture face (your frontend sends this)
face_image = request.json["face_image"]
# Step 3: Lorica verification (NEW — add this)
result = lorica_client.verify(
user_id=user_id,
image=face_image,
action_context=f"withdraw_{amount}_{currency}",
liveness_mode="passive" # or dual_frame/motion
)
if not result["match"]:
raise VerificationFailed(result["rejection_reason"])
# Step 4: Execute (existing code)
execute_withdrawal(user_id, amount, destination)
# Step 5: Store proof (NEW — add this)
store_verification_proof(
transaction_id=txn.id,
jwt=result["jwt"]
)
That’s 6 lines of new code. Everything else in your flow stays exactly the same.
Common error codes
401— Invalid or missing API key. Check the X-API-Key header.400— Malformed request. Missing user_id, invalid base64, or unsupported image format.404— User not enrolled. Call POST /enroll first.413— Image too large. Maximum 5MB.429— Rate limited. /verify allows 30 requests/minute.422— Face not detected, image too blurry, multiple faces, or quality below threshold.
That’s it
Three endpoints. One SDK. Fifteen minutes. Biometric step-up verification running in your staging environment, ready to go live whenever you are.
If you’d rather not write the integration yourself: I’ll do it for you. Send me access to your staging environment and I’ll have Lorica running on your withdrawal flow in 2 hours. Zero engineering time from your team.
Start now: Express interest — sandbox key delivered immediately. Full docs: loricaapi.com/docs