Skip to main content

Proof of Resolution

A Proof of Resolution is the artifact Q produces when a user resolves a Q card with a biometric. It is a JSON Web Token signed with ES256 (EC P-256, asymmetric) — a portable, action-scoped, independently verifiable attestation of the user’s verdict. Because it is signed with a private key whose public counterpart is published as a JWKS, an agent or your backend can verify a Proof of Resolution locally, with no round-trip to BotShield.
The token carries a verdict of approve or denied — both are signed. An expired or cancelled card produces no token at all. See Resolution Flow.

Claims

The token’s header carries alg: ES256 and kid: v2. The payload claims:
The Proof of Resolution carries no identity. sub is an opaque BotShield user id; there is no email, no device identifier, and no Adaptive Card body. It attests the verdict on a specific action, nothing more.

Verify locally with the JWKS

BotShield publishes the Resolution public key as a JWK Set at:
The kid in the JWKS matches the JWT header, so verification is the standard JWKS flow. This is the recommended path — no network call to BotShield per verification.

Node

Always check verdict === "approve" before acting — a denied token is a valid, signed refusal, not an authorization.

Verify via the SDK (coming)

A round-trip verifier — sdk/verify-resolution, for callers who prefer not to pin a JWKS client — is planned but not yet available. It will perform the same ES256 verification server-side (rejecting bad signatures, wrong issuer, expired tokens, or an invalid verdict) and return the decoded claims.
Until it ships, local JWKS verification (above) is the supported path — and it is sufficient: it enforces signature, issuer, audience, expiry, and algorithm entirely client-side with no network round-trip.

Key rotation

The kid header tags the key version (today v2). When BotShield rotates keys, the new key is appended to the JWKS under a new kid; the old key is retained until all tokens signed with it expire (24-hour TTL). A correct JWKS verifier selects the key by kid automatically — no action required on rotation. Pin algorithms: ["ES256"] to reject any token that claims a different algorithm.

Webhook events

In addition to (or instead of) polling, Q delivers events to your endpoint via Svix. Each delivery is signed with your endpoint’s whsec_ secret and carries the svix-id / svix-timestamp / svix-signature headers — verify exactly as described in Webhooks.
Not yet firing in the current build. The q.card.proposed and q.resolution.* events are defined as below, but they do not yet fire for agent-proposed cards — partner organization association for the Q flow is still being wired up. Until that lands, use polling (check-status) as the reliable way to learn an outcome. The schemas here are stable; treat the webhook path as forthcoming.

q.card.proposed

q.resolution.confirmed / q.resolution.denied

outcome is confirmed for q.resolution.confirmed and denied for q.resolution.denied. The proof_token field carries the Proof of Resolution JWT — verify it as above before acting. When several cards were confirmed in one ceremony, each fires its own message, all sharing one ceremony_id in metadata.

q.resolution.expired

No proof_token — there is no signed verdict on an expiry. Treat it as “stand down.”
Authenticity comes from the Svix envelope signature, verified with your whsec_ endpoint secret — not from a token inside the webhook. For confirmed/denied events you then independently verify the proof_token JWT. Dedupe on the Svix message id for idempotency; BotShield may retry.

Next steps

Q API Reference

Every agentlink/* operation with request and response shapes.

Webhooks

The shared Svix signature-verification pattern, with per-language snippets.