Rensei docs
Audit

Audit JWKS

.well-known/audit-keys JWKS.

The /.well-known/audit-keys/{workspace_id} endpoint publishes the public JWKS (JSON Web Key Set) used to sign audit events. External auditors and SIEM systems can verify audit event signatures independently - without Rensei systems - using these keys.

Endpoint

GET /.well-known/audit-keys/{workspace_id}

No authentication required. This endpoint is fully public with CORS open to all origins.

The {workspace_id} is your organization's workspace identifier, available in Settings > Security > Audit Keys.

Also supports .json suffix (transparent redirect):

GET /.well-known/audit-keys/{workspace_id}.json

Response headers

HeaderValue
Content-Typeapplication/json
Cache-Controlpublic, max-age=300, stale-while-revalidate=3600
ETagDigest of current key set (supports conditional GET)
Access-Control-Allow-Origin*

The 5-minute max-age plus 1-hour stale-while-revalidate allows auditing tools to cache keys aggressively while still picking up key rotations promptly.

JWKS response

{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "alg": "EdDSA",
      "use": "sig",
      "kid": "key_01abc...",
      "x": "base64url-encoded-public-key",
      "rensei:workspace_id": "ws_01abc...",
      "rensei:created_at": "2026-01-01T00:00:00Z",
      "rensei:revoked_at": null
    }
  ]
}

Standard RFC 7517 fields

FieldValueDescription
ktyOKPKey type: Octet Key Pair (EdDSA family)
crvEd25519Curve: Edwards Curve 25519
algEdDSAAlgorithm: Edwards-curve Digital Signature Algorithm
usesigKey usage: digital signatures
kidstringKey ID - matches the kid claim in audit event JWS headers
xbase64urlPublic key material

Rensei extension members

FieldDescription
rensei:workspace_idThe workspace this key belongs to
rensei:created_atISO 8601 timestamp when the key was provisioned
rensei:revoked_atISO 8601 timestamp when the key was revoked, or null if still active

A non-null rensei:revoked_at means the key is no longer used to sign new events, but events signed during the key's active period remain verifiable with it.

Verifying an audit event signature

Audit events carry a JWS (JSON Web Signature) in their signature field. Verification steps:

Fetch the JWKS from /.well-known/audit-keys/{workspace_id}. Find the key whose kid matches the event's kid header claim.

Verify the signature using the Ed25519 public key (x field) against the event's canonical payload bytes.

Check the hash chain. Each event includes the SHA-256 hash of the previous event in the prevHash field. Walk the chain to detect any tampering.

Optionally verify the Merkle anchor. If the event is covered by an anchor, verify the inclusion proof against the anchored Merkle root.

Verification example (TypeScript)

import { createVerify } from 'crypto';

interface AuditEvent {
  eventId: string;
  payload: Record<string, unknown>;
  hash: string;
  prevHash: string;
  signature: string; // base64url-encoded Ed25519 signature
  kid: string;       // key ID
}

interface JwkKey {
  kty: string;
  crv: string;
  x: string;
  kid: string;
  'rensei:revoked_at': string | null;
}

async function fetchPublicKey(workspaceId: string, kid: string): Promise<JwkKey> {
  const res = await fetch(`https://app.rensei.ai/.well-known/audit-keys/${workspaceId}`);
  const jwks = await res.json();
  const key = jwks.keys.find((k: JwkKey) => k.kid === kid);
  if (!key) throw new Error(`Key ${kid} not found in JWKS`);
  return key;
}

async function verifyAuditEvent(
  event: AuditEvent,
  workspaceId: string,
): Promise<boolean> {
  const jwk = await fetchPublicKey(workspaceId, event.kid);

  // Import the Ed25519 public key
  const publicKey = await crypto.subtle.importKey(
    'jwk',
    { kty: 'OKP', crv: 'Ed25519', x: jwk.x },
    { name: 'Ed25519' },
    false,
    ['verify'],
  );

  // Canonical payload: JSON-serialized event fields (deterministic key order)
  const canonicalPayload = JSON.stringify({
    eventId: event.eventId,
    payload: event.payload,
    hash: event.hash,
    prevHash: event.prevHash,
  });

  const signatureBytes = Buffer.from(event.signature, 'base64url');
  const payloadBytes = new TextEncoder().encode(canonicalPayload);

  return crypto.subtle.verify('Ed25519', publicKey, signatureBytes, payloadBytes);
}

Conditional GET (ETags)

Use If-None-Match to avoid re-downloading the JWKS when it hasn't changed:

# First request: get ETag
curl -I "https://app.rensei.ai/.well-known/audit-keys/ws_01abc..."
# HTTP/2 200
# etag: "a1b2c3d4e5f6"

# Subsequent request: conditional GET
curl "https://app.rensei.ai/.well-known/audit-keys/ws_01abc..." \
  -H 'If-None-Match: "a1b2c3d4e5f6"'
# HTTP/2 304 Not Modified (no body - use cached JWKS)

Key rotation

Rensei rotates signing keys periodically. During rotation:

  1. A new key is provisioned - it appears in the JWKS with rensei:revoked_at: null.
  2. The old key is revoked - its rensei:revoked_at is set to the rotation timestamp.
  3. New events are signed with the new key. Old events retain valid signatures under the old key.

Both active and revoked keys appear in the JWKS response to support verification of historical events.

On this page