Identity Assertions & JWKS
An analyze / analyze-sync call can return, alongside the snapshot, a signed identity assertion: a compact JWT (typ: "identity_assertion") that carries the identity verdict and is cryptographically signed by Identity. Its point is non-repudiation — a third party who does not share any secret with you can verify, independently, that Identity produced this verdict and that it was not altered. Verification is done against a public JWKS endpoint.
The assertion carries the same evidence-grade verdict Identity always returns (identityRiskLevel and supporting fields) — it does not add a new decision. What it adds is provability: a signature anyone can check.
When to use it
Reach for a signed assertion when a party other than the caller must trust the verdict without trusting you:
- A regulator or auditor verifying that an identity decision was genuinely produced by Identity.
- A downstream payment-fraud or risk service that consumes the identity verdict and must confirm it independently before acting on it.
- Any cross-service boundary where "the merchant says Identity said X" is not good enough and the recipient wants to check the signature itself.
Because verification uses a public key, the verifier needs no shared secret and no live call back to you — just the assertion and the public JWKS.
Asymmetric signing is required for verifiable assertions
By default IDENTITY_ASSERTION_ALG is HS256 — a symmetric (shared-secret) algorithm that produces no publicly verifiable key. In that default state the public JWKS endpoint returns an empty key set ({ "keys": [] }) and there is nothing for a third party to verify against.
A verifiable assertion requires asymmetric signing (ES256 / RS256) to be configured. Once an asymmetric key is configured, analyze can emit a signed assertion and the JWKS endpoint publishes the matching public key.
So the honest picture is two states:
| Configuration | Assertion in analyze | jwks.json | Third-party verification |
|---|---|---|---|
Default (HS256, symmetric) | Not publicly verifiable | { "keys": [] } | Not possible without a shared secret |
Asymmetric configured (ES256 / RS256) | Signed assertion returned | Public key published | Anyone can verify against JWKS |
The JWKS endpoint
GET /identity/.well-known/jwks.json — public, unauthenticated, cacheable for 300 s, * CORS. It publishes the public keys that sign identity assertions, in standard JWK Set form:
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"kid": "id-assert-2026-07",
"use": "sig",
"alg": "ES256",
"x": "…",
"y": "…"
}
]
}An empty { "keys": [] } is a valid, expected response when asymmetric signing has not been configured — it is the signal that verifiable assertions are not currently enabled, not an error.
Verifying an assertion
The assertion is a standard JWT: match its header kid to a key in the JWKS, then verify the signature with that public key. Any conformant JWT library works — the example uses jose.
import { createRemoteJWKSet, jwtVerify } from 'jose';
// Fetches + caches the public keys; picks the key by the assertion's `kid`
const JWKS = createRemoteJWKSet(
new URL('https://api-gateway.lunixi.com/api/v1/identity/.well-known/jwks.json'),
);
async function verifyAssertion(assertion) {
const { payload, protectedHeader } = await jwtVerify(assertion, JWKS, {
// algorithms you accept — must be asymmetric
algorithms: ['ES256', 'RS256'],
});
// typ pins this to an identity assertion
if (protectedHeader.typ !== 'identity_assertion') {
throw new Error('not an identity assertion');
}
// payload now carries the verified identity verdict
// e.g. payload.identityRiskLevel, payload.subjectId, payload.traceId
return payload;
}Obtain the assertion
Call analyze / analyze-sync (see Server-side API & Queries). When asymmetric signing is configured, the response includes the signed assertion; hand it to the party that must verify it.
Fetch the public keys
The verifier fetches GET /identity/.well-known/jwks.json (cache it for up to 300 s) and selects the key whose kid matches the assertion header.
Verify
Verify the JWT signature against that public key, restricting accepted algorithms to the asymmetric set, and check typ: "identity_assertion". On success, the payload's verdict is proven to come from Identity, untampered.
Notes
- Public, no secret. JWKS is unauthenticated and cacheable (300 s); verification never requires a shared secret or a call back to you.
- Configure to enable. Under the default
HS256, assertions are not third-party-verifiable and JWKS is empty — configure asymmetric signing (ES256/RS256) to publish a key and emit verifiable assertions. - Pin the type. Always check
typ: "identity_assertion"and restrict accepted algorithms to the asymmetric set when verifying.
Next steps
- Server-side API & Queries — where the assertion is issued
- The Risk Snapshot — the verdict fields carried in the assertion
- Overview — the evidence-provider boundary