Verifying a verdict
How a counterparty checks a signed verdict against the public key at verdicts.saifuro.com/.well-known/jwks.json, without a Saifuro account.
Every verdict Saifuro returns is signed. The point of the signature is that the party receiving an approval does not have to trust the party presenting it, and does not need anything from us to check it: verification takes the token and our public key, both of which are in the open.
Where the key lives
The public key is published as a standard JWK Set:
https://verdicts.saifuro.com/.well-known/jwks.jsonVerdicts are JWTs signed with ES256, and the kid header of each one names the key in the set that signed it. When keys rotate, the outgoing and incoming keys are listed side by side, so a verdict issued just before a rotation still verifies just after it. The endpoint allows caching for five minutes, which is how fast a rotation propagates. The set lives on its own host so that key discovery does not share fate with the main site; the earlier location on the apex domain, saifuro.com, permanently redirects here.
What is inside
The claims mirror the decision record described in decision codes. Decoded, an approval looks like this:
{
"iss": "https://verdicts.saifuro.com",
"jti": "dec_5b2e",
"iat": 1788424980,
"exp": 1788425280,
"verdict": "allow",
"mandate": "mnd_7f3a",
"policy_version": "pol_2026-08-12.3",
"nonce": "n_9d02c6e4",
"request": {
"agent": "agt_procurement_01",
"amount": 340.00,
"recipient": "vendor:acme_saas",
"category": "saas"
}
}Amount, recipient, mandate, policy version, and the nonce are all under the signature, and the expiry is five minutes after issue. That combination is what the threat model relies on: an approval cannot be edited in transit, reused at a larger amount, or presented twice.
Checking one
The signature tells you the token came from Saifuro and has not been changed. Whether it is good for the deal in front of you is a claims check, and both halves matter:
- Verify the signature against the JWKS, the issuer, and the expiry.
- Compare
request.amountandrequest.recipientwith the transaction you are about to settle. - Treat a
nonceyou have already seen as a replay, not a coincidence.
The library does the first step. The second and third are yours, and no library knows what deal you are about to settle.
With jose (npm install jose):
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://verdicts.saifuro.com/.well-known/jwks.json'),
);
async function checkVerdict(token, deal) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://verdicts.saifuro.com',
algorithms: ['ES256'],
});
if (payload.verdict !== 'allow') {
throw new Error(`not an approval: ${payload.verdict}`);
}
if (
payload.request.amount !== deal.amount ||
payload.request.recipient !== deal.recipient
) {
throw new Error('approval is for a different transaction');
}
return payload; // record payload.nonce; seeing it again means a replay
}Nothing about the check is specific to a language: the same three steps, the same claim names.
The key set itself is one request away, which is also the quickest way to see the current key ids:
curl -s https://verdicts.saifuro.com/.well-known/jwks.jsonA token to try it on
This is a complete verdict token, signed with the key in our published JWKS — the one decoded above:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InZlcmRpY3QtMjAyNi0wOSJ9.eyJpc3MiOiJodHRwczovL3ZlcmRpY3RzLnNhaWZ1cm8uY29tIiwianRpIjoiZGVjXzViMmUiLCJpYXQiOjE3ODg0MjQ5ODAsImV4cCI6MTc4ODQyNTI4MCwidmVyZGljdCI6ImFsbG93IiwibWFuZGF0ZSI6Im1uZF83ZjNhIiwicG9saWN5X3ZlcnNpb24iOiJwb2xfMjAyNi0wOC0xMi4zIiwibm9uY2UiOiJuXzlkMDJjNmU0IiwicmVxdWVzdCI6eyJhZ2VudCI6ImFndF9wcm9jdXJlbWVudF8wMSIsImFtb3VudCI6MzQwLCJyZWNpcGllbnQiOiJ2ZW5kb3I6YWNtZV9zYWFzIiwiY2F0ZWdvcnkiOiJzYWFzIn19.oV4a02jtE2oEKvnSXxQ-YqJcpp8UvFZiuh3AeTb5Iy5Ony9jC1AA7EX-YPSKDuSFPxSNS4SlXLtU9-j0J36cbQIt was signed on 3 September 2026 at 08:43 UTC and expired five minutes later, so verifying it today fails on exp. That is the mechanism working, not a broken example: an approval is not supposed to outlive its settlement window. To check the signature anyway, pin the verification clock to when the token was alive:
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://verdicts.saifuro.com',
algorithms: ['ES256'],
currentDate: new Date('2026-09-03T08:45:00Z'),
});In PyJWT the same experiment is options={"verify_exp": False} (there is no clock to pin, so the expiry check is skipped instead); in jwx it is jwt.WithClock pinned to the same moment, time.Unix(1788425100, 0). A live integration never pins the clock. An expired approval failing to verify is exactly the guarantee you are relying on.
The claim set above is canonical: the same names the API reference uses for the decision object. The key discovery and the verification procedure on this page do not change as the API grows.

