Partner Implementation Checklist
- Store and validate callback state nonce per login attempt.
- Call init-auth after successful primary credentials.
- Call consume-result before creating partner session.
- Treat non-PASS outcomes as login denial.
Use these implementation patterns to add FraudShield as a precise second step. Your backend remains the session authority while we handle the cognitive verification.
User logs in on your site
Your existing username/password form stays unchanged. Validate credentials in your own backend.
Your backend calls init-auth
POST /api/product/v1/init-auth with partnerId, userId, callbackUrl, and a random state nonce.
User completes visual challenge
Redirect or popup the user to the verifyPath. They solve the challenge on our hosted secure page.
Callback returns result
We redirect back to your callbackUrl with result, signature, and the original state.
Your backend validates signature
POST /api/product/v1/partner/consume-result with the signature. We confirm PASS or deny.
Grant or deny session
On PASS create your app session. On FAIL/LOCKED deny login. No visual secrets ever leave our system.
consume-result must be called from your backend only. ?mode=test|live. Send credentials in x-api-key header./api/product/v1/init-authStart a visual verification session
Params: partnerId, userId, callbackUrl, state
Returns: sessionToken, verifyPath, expiresAt
/api/product/v1/challenge/:sessionTokenFetch challenge data (used by our hosted UI)
Params: sessionToken (path)
Returns: vegetables, alphabetGrid, keypadLayout, stage, fruitSelectionComplete
/api/product/v1/verifyStep 1 select fruit, Step 2 submit alphabet inputs (hosted UI)
Params: sessionToken + selectedFruit OR sessionToken + inputs
Returns: FRUIT_OK/PASS/FAIL, status, attempts, verificationSignature
/api/product/v1/partner/consume-resultServer-side signature validation (MANDATORY)
Params: signature
Returns: result, userId, partnerId, state, consumedAt
/api/product/v1/session/:sessionTokenCheck session status (for polling/audit)
Params: sessionToken (path)
Returns: status, attemptCount, verifiedAt, consumedAt
/api/product/v1/metaAPI availability and routing metadata
Params: —
Returns: product, mode, endpoints[]
Choose language and use init-auth, consume-result, and callback handler snippets.
Node.js - Start verification
// Node.js — Start visual verification
const crypto = require("crypto");
async function startVisualAuth(userId) {
const state = crypto.randomUUID();
const res = await fetch(
`${process.env.FRAUDSHIELD_BASE_URL}/api/product/v1/init-auth?mode=live`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.FRAUDSHIELD_API_KEY,
},
body: JSON.stringify({
partnerId: process.env.FRAUDSHIELD_PARTNER_ID,
userId,
callbackUrl: `${process.env.APP_BASE_URL}/auth/visual/callback`,
state,
}),
}
);
if (!res.ok) throw new Error("Failed to start visual auth");
const data = await res.json();
// Store state in session for callback validation
req.session.pendingVisual = { userId, state };
return data; // { sessionToken, verifyPath, expiresAt }
}Node.js - Validate result
// Node.js — Validate verification result
async function consumeVisualResult(signature) {
const res = await fetch(
`${process.env.FRAUDSHIELD_BASE_URL}/api/product/v1/partner/consume-result?mode=live`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.FRAUDSHIELD_API_KEY,
},
body: JSON.stringify({ signature }),
}
);
if (!res.ok) throw new Error("Signature validation failed");
return await res.json();
// { result: "PASS", userId, partnerId, state, consumedAt }
}Node.js - Callback handler
// Node.js (Express) — Callback handler
app.get("/auth/visual/callback", async (req, res) => {
const { result, signature, state } = req.query;
const pending = req.session.pendingVisual;
if (!pending || state !== pending.state) {
req.session.pendingVisual = null;
return res.status(401).send("Invalid state");
}
if (result !== "PASS" || !signature) {
req.session.pendingVisual = null;
return res.status(401).send("Visual verification failed");
}
const consumed = await consumeVisualResult(signature);
req.session.user = consumed.userId;
req.session.pendingVisual = null;
return res.redirect("/dashboard");
});# Your backend .env — Add these variables FRAUDSHIELD_BASE_URL=https://fraudshield.example.com FRAUDSHIELD_API_KEY=pk_live_your-api-key-here FRAUDSHIELD_PARTNER_ID=your_company_id APP_BASE_URL=https://your-app.example.com # For test/sandbox (optional) FRAUDSHIELD_SANDBOX_URL=https://sandbox.fraudshield.example.com FRAUDSHIELD_SANDBOX_API_KEY=pk_test_your-sandbox-key
Bad request — invalid or missing parameters
Unauthorized — missing or invalid API key / token
Forbidden — callback URL not in allowlist, or wrong API key for partner
Not found — user not enrolled or session doesn't exist
Conflict — session already verified (duplicate submission)
Gone — session expired (past TTL)
Locked — too many failed attempts, session is locked
Rate limited — too many requests, slow down
Never expose your live API key in frontend/client-side code.
Always validate the state nonce in callbacks to prevent CSRF.
Always call consume-result from your backend — never trust callback query params alone.
Use HTTPS callback URLs in production (enforced by our API).
Generate a unique random state for every authentication attempt.
Treat all non-PASS results as login denial.
Set reasonable session TTL; our challenges expire in 5 minutes by default.
Log request IDs, verification results, and suspicious attempts for audit.
Get credentials: partnerId and API key from onboarding.
Enroll users: create visual profiles in admin.
Add init-auth: call after successful credential check.
Handle callback: verify state and consume signature server-side.
Go live: switch to live mode and production keys.