Skip to main content
Selo’s answer to a release test carries an attestation: the verdict, the checks and both payment legs, signed by Selo with Ed25519. The public API at https://api.useselo.xyz signs every answer. selo verify checks that signature against keys you trust and checks that the attestation is for the exact release and target you expect. A PASS for one commit cannot open the gate for another.

Prerequisites

  • An attestation file, saved by selo test --attestation <file>. See Run a release test.
  • A checkout of the Selo repository with pnpm install --frozen-lockfile. No wallet is needed, and verification never pays.
  • Selo’s attestation keys: the live URL https://api.useselo.xyz/.well-known/selo-attestation-keys.json, or a copy you committed.

Verify with the CLI

1

Get the keys

Terminal
Output
publicKey is the raw Ed25519 public key in base64url. The key id is selo-ed25519- followed by the first 16 hex characters of the SHA-256 of the raw key bytes. For CI, save this file once, review it and commit it, for example as selo/attestation-keys.json. Trusting a pinned file is stronger than trusting whatever a URL serves at the time.
2

Run selo verify

Terminal
3

Read the result

Verifying the fixed-v2 attestation from the demo merchant’s Mainnet run against the published key prints:
Output
It exits 0, the attested PASS.
You can reproduce this from a checkout of the Selo repository, which commits both Mainnet attestations and the key:
Terminal
The same file presented for another release is refused with exit 2:
Output

What is checked

selo verify checks, in this order, and stops at the first failure: The signature is Ed25519 over the payload’s canonical JSON: object keys sorted at every level and no whitespace. It covers the payload only, never itself. --url is normalized the same way as when you tested, so pass the URL you passed to selo test.
deployment.id is the --release you gave selo test, recorded with verification: "asserted_by_client". Selo signs that you named this release when you tested; it does not check which build was deployed at that URL.

Exit codes

A promotion step should require exit 0. Exit 1 is still a successful verification: Selo did sign a FAIL for that release.

What an attestation contains

This is the payload of the fixed-v2 attestation, abridged to its first two checks and evidence rows. The full file is docs/evidence/g5-release-gate-fixed-v2-attestation.json in the Selo repository.
selo-attestation.json
An abridged payload no longer matches its signature. Always verify the complete file.
The attestation holds no secret, no request body and no report token. It is safe to publish. See Signed attestations for the full schema.

Verify in CI before promotion

Use the GitHub Action in verify mode as the gate of the promote job. The Selo repository’s Release gate demo workflow does exactly this with the committed Mainnet attestations:
.github/workflows/release-gate-demo.yml
The promote job runs only when the gate output is ALLOW. On 26 October 2026 the broken-v1 run failed the gate and skipped promotion, and the fixed-v2 run passed and promoted. See Gate deploys in GitHub Actions.

Verify without Selo’s code

The format is plain enough to check with Node’s standard library alone. This script performs the core checks of selo verify: trusted key, signature, schema version, release, URL and method.
verify-attestation.mjs
Terminal
Output
Change any signed field, such as the verdict of the broken-v1 attestation from FAIL to PASS, and the script stops with the signature does not verify.

Troubleshooting

Your pinned key file does not contain the key that signed this attestation. Compare it with https://api.useselo.xyz/.well-known/selo-attestation-keys.json. If Selo published a new key, review it and add it to your file deliberately.
The attestation is for another release id. In CI, make sure release is the same value in the test and the verify steps, for example ${{ github.sha }} in both.
The URL or method differs from the tested target. Pass exactly the URL and method you tested.
The file is missing, is not JSON, or is not an envelope with payload and signature. Check the path; in CI, check that the artifact was downloaded. The summary file selo-gate.json is not an attestation.
Selo’s answer carried no attestation for that run, so there is nothing to verify. Do not promote on the summary alone.

Next

Signed attestations

The attestation model and every payload field.

selo verify reference

Every flag and exit code.