ServicesBadge

Signed Badges and Verification

A badge image is a claim; anyone can screenshot a green "passing" badge. Signed metadata turns the claim into a receipt: the service signs the template identity, version, resolved-state hash, and a validity window with its Ed25519 key, and anyone - including offline consumers - can verify it.

Getting signed metadata

Two switches, either is enough: set "verification": {"signed_metadata": true} on the template so every JSON render carries metadata, or pass "include_metadata": true on a single POST /api/v1/badges/render request. The image GET routes return only the SVG (with the state hash as the ETag) - to obtain the receipt, use the JSON render. The response's verification block, captured live:

{
  "template_id": "XHSbLagCVnnPAZvejzAu",
  "template_version": 1,
  "state_hash": "sha256:ca0aeea44cb44f964b81a0d3f5be6f26661bcbdb294091fb2198a5ce17b352ad",
  "issued_at": "2026-08-28T13:29:10.969519+00:00",
  "expires_at": "2026-08-28T13:29:40.969519+00:00",
  "signature_alg": "ed25519",
  "signature": "Vqc99EBUeL7IDa1Ft1U4doWiL6ODBLf6DgHMppbedscvRTtAgwKZb8o7Ss40J9BPdS3fQXjR3rPJ8sSP8_wyCg",
  "public_key": "3zP35w_-lNmXazf64MV4bvzeV3BXbM1ovGw8-yl2rUM"
}

The validity window equals the template's cache TTL (30 seconds here): a receipt is only as fresh as the render it describes.

Verifying

POST /api/v1/badges/verify takes the metadata block as its request body; the CLI reads it from a file:

snug badge verify --metadata metadata.json
{ "valid": true, "template_id": "XHSbLagCVnnPAZvejzAu", "template_version": 1 }

Verification is fail-closed and always answers 200 with a valid flag - branch on valid, not on the HTTP status. Both failure modes:

{ "valid": false, "reason": "signature mismatch", "...": "..." }
{ "valid": false, "reason": "signature has expired", "...": "..." }

The first came from altering state_hash after signing; the second from re-verifying the same metadata after its expires_at passed.

Offline and external verification

The signing public key is available to any authenticated caller:

snug badge public-key
# Algorithm: ed25519
# Public Key: 3zP35w_-lNmXazf64MV4bvzeV3BXbM1ovGw8-yl2rUM

It is also embedded in every metadata block, so a consumer can pin it once and verify signatures without trusting the server that served the badge. When a VerifyRequest includes a public_key (base64url), the service verifies against that key instead of its own, by replaying a fresh metadata block with its public_key field intact, which exercises the caller-supplied-key path. Verification failures are also counted per template in snug badge template-metrics as verification_failures.

The exact request and response fields are in the Badge API reference.

On this page