Developer documentation
Proof continuity for your site
Verify signed presentations locally and enforce on a site-private ppid plus assurance. One trial per human and bans that stick need a human proof (IDV) on those actions. Keep your existing login if you want.
Gate a sensitive action
Call the SDK from a user gesture before a trial, claim, checkout, or other abuse-prone step. The popup mints a signed presentation; your backend verifies it locally and enforces on ppid. Full walkthrough: quick start · continuity & abuse.
<script src="https://lemma.id/sdk/proof-verifier.js"></script>
<script>
const verifier = new ProofVerifier({ siteId: 'app.example.com' });
async function claimTrial() {
const { ok, presentation, reason } = await verifier.verifyForBackend({
autoProvision: true,
requiredAssurance: 'ishuman',
});
if (!ok) throw new Error(reason || 'not_verified');
await fetch('/api/claim-trial', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ presentation }),
});
}
</script>
Set siteId to the hostname your users see (for example app.example.com). No registration, no API key for verify.
<lemma-signin> wraps the same flow for custom or login UX. It handles the popup and passkey ceremony; POST the presentation to your gate route.
Verify on your backend
Never trust a bare ppid from the browser. Verify the signed presentation server-side, then enforce on result.ppid and result.assurance. Verification is local: no per-request call to lemma.id.
npm install "@lemma.id/[email protected]"
pip install lemma-proof-verifier
# Zero-install alternative:
curl -O https://lemma.id/sdk/proof-verifier.py
import { createVerifier } from '@lemma.id/proof-verifier';
const verifier = createVerifier({
siteId: 'app.example.com',
requiredAssurance: 'ishuman',
});
app.post('/api/claim-trial', async (req, res) => {
const result = await verifier.verify(req.body.presentation);
if (!result.ok) return res.status(403).json({ error: result.reason });
if (await alreadyClaimed(result.ppid)) {
return res.status(403).json({ error: 'already_claimed' });
}
await recordClaim(result.ppid, result.assurance);
res.json({ ok: true });
});
Enforcement: isHuman & site-block
passkey assurance = continuity. ishuman = IDV-backed one human per account on the same PPID. Use site-block API keys when bans must survive browser clears and new accounts.
Optional: sessions from the same presentation
Many sites keep Google/OAuth/email login and use lemma only on gated actions. If you want passwordless sessions, issue your own cookie after verify. lemma.id is not in the request path afterward.
const user = await findOrCreateUser(result.ppid);
res.cookie('session', signSession(user.id), {
httpOnly: true, secure: true, sameSite: 'lax',
});
Examples: quickstart · integration guide.
Mint a presentation (drop-in button)
<lemma-signin> wraps ProofVerifier for login or gate UX. Always pass requiredAssurance.
<script src="https://lemma.id/sdk/proof-verifier.js"></script>
<script src="https://lemma.id/sdk/lemma-signin.js"></script>
<lemma-signin site-id="app.example.com" required-assurance="ishuman"></lemma-signin>
<script>
document.querySelector('lemma-signin')
.addEventListener('lemma-signin-success', async ({ detail }) => {
await fetch('/api/gate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ presentation: detail.presentation }),
});
});
</script>
autoProvision: true lets first-time users create a lemma.id in the popup.
Local development and testing
- localhost works out of the box: run your site at any
http://localhostport withsiteId: 'localhost'against the production lemma.id popup. - Offline tests: mint valid test presentations without a browser using
@lemma.id/proof-verifier/testing(Node) orlemma_proof_verifier_testing(Python).
Examples in the quickstart. Browsers: support matrix.
Troubleshooting
siteIdhostname mismatch: staging and production subdomains derive different PPIDs.- Persistent
no_credential: pass{ autoProvision: true }for first-time users. assurance_insufficient: match client and backendrequiredAssurance.- Popup blocked: call the SDK from a click handler.
FAQ
Do I need to replace my login?
No. Gate specific actions; keep OAuth/email if you want. lemma returns ppid + assurance, not a session provider.
How do users recover?
Passkey vault sync (iCloud/Google) may restore the WebAuthn credential on a new device, but it does not sync lemma.id contents. Same-person continuity requires lemma.id/link (or isHuman / site-side recovery keyed to ppid). Single-device passkey-only lemma.id instances are not guaranteed recoverable. Say that plainly to users.
Why is there no API key for verify?
Verification is cryptographic and local. Keys are for server-side site-block enforcement only.