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.

JavaScript
<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.

Mint a presentation (drop-in button)

<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.

Node.js
npm install "@lemma.id/[email protected]"
Python
pip install lemma-proof-verifier # Zero-install alternative: curl -O https://lemma.id/sdk/proof-verifier.py
Node.js gate route
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.

Human proofs & abuse controls → · Continuity & abuse doc

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.

After verify (optional session)
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.

HTML
<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://localhost port with siteId: 'localhost' against the production lemma.id popup.
  • Offline tests: mint valid test presentations without a browser using @lemma.id/proof-verifier/testing (Node) or lemma_proof_verifier_testing (Python).

Examples in the quickstart. Browsers: support matrix.

Troubleshooting

  • siteId hostname mismatch: staging and production subdomains derive different PPIDs.
  • Persistent no_credential: pass { autoProvision: true } for first-time users.
  • assurance_insufficient: match client and backend requiredAssurance.
  • 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.

Using an AI coding agent?

llms.txt · integration guide.