Public Verification (with OpenSSL)
How to verify any Aira receipt with OpenSSL alone — no SDK, no API key, no trust in Aira's verdict.
The contract
Every action receipt Aira mints is independently verifiable. You can take a receipt id, fetch it from a public endpoint, and verify the Ed25519 signature with OpenSSL or any signature library. You do not need to trust anything Aira says about the receipt — you can re-run the math yourself.
This page is the artifact a regulator or auditor uses to confirm a receipt. It is also the answer to "how do I know your dashboard isn't lying?"
Step 1 — Fetch the receipt
The verify endpoint is public (no auth, no API key) and rate-limited.
curl https://api.airaproof.com/api/v1/verify/action/<action_uuid>The response includes everything you need to re-verify offline:
{
"valid": true,
"receipt_uuid": "...",
"action_uuid": "...",
"verified_at": "2026-04-10T20:00:00Z",
"public_key_id": "aira-signing-key-v1",
"payload_hash": "sha256:fc2a8b...",
"signature": "ed25519:Kp3Lm7Qw9Rv2Tn5Xk8Bj4Hf1Dg6Cs0Ep3Aw7Yt==",
"public_key": "uBa2zVOsAycukt1//mCaACAh0cC4RQ5yZQDNAsfm5io=",
"algorithm": "Ed25519",
"timestamp_token": "MIIB...",
"signed_payload": {
"receipt_version": "1.2",
"alg": "Ed25519",
"action_uuid": "...",
"org_uuid": "...",
"agent_id": "payments-agent",
"action_type": "wire_transfer",
"details_hash": "sha256:...",
"outcome_hash": "sha256:...",
"instruction_hash": "sha256:...",
"model_id": "claude-sonnet-4-6",
"parent_payload_hash": null,
"authorization_ref": {
"policy_evaluation_uuid": "...",
"approval_authorization_uuid": null
},
"created_at": "2026-04-10T19:55:00Z"
},
"message": "Receipt verified. Signature is valid against the published Ed25519 public key.",
"request_id": "..."
}The endpoint actually re-runs the verification before responding. The valid field is the result of recomputing the SHA-256 hash and verifying the signature against the published public key — not just a "this row exists" check.
Step 2 — Verify offline with OpenSSL
You do not need to trust the endpoint. The response gives you everything to re-verify locally.
2a. Reconstruct the signed bytes
The signed_payload field is the exact dict whose canonical JSON form was signed. Canonical JSON means: sorted keys, no whitespace, UTF-8.
import json
import hashlib
with open("response.json") as f:
body = json.load(f)
canonical = json.dumps(body["signed_payload"], sort_keys=True, separators=(",", ":"))
recomputed_hash = "sha256:" + hashlib.sha256(canonical.encode()).hexdigest()
assert recomputed_hash == body["payload_hash"], "Payload was tampered with"
print("Hash matches:", recomputed_hash)2b. Verify the Ed25519 signature
import base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
pub_bytes = base64.b64decode(body["public_key"])
pub_key = Ed25519PublicKey.from_public_bytes(pub_bytes)
sig_b64 = body["signature"].removeprefix("ed25519:")
sig_bytes = base64.urlsafe_b64decode(sig_b64)
pub_key.verify(sig_bytes, canonical.encode())
print("Signature is valid")If the signature doesn't verify, verify() raises InvalidSignature and you have proof the receipt is tampered.
2c. Verify with OpenSSL
If you'd rather use OpenSSL directly:
# Save the canonical bytes
python -c "import json; j=json.load(open('response.json'))['signed_payload']; print(json.dumps(j, sort_keys=True, separators=(',',':')), end='')" > payload.bin
# Save the public key as a raw 32-byte Ed25519 key, then wrap in DER
python -c "
import base64, json
body = json.load(open('response.json'))
pub_raw = base64.b64decode(body['public_key'])
# Ed25519 public keys: 12-byte ASN.1 prefix + 32-byte key
der = bytes.fromhex('302a300506032b6570032100') + pub_raw
open('pub.der', 'wb').write(der)
"
# Save the signature
python -c "
import base64, json
body = json.load(open('response.json'))
sig = base64.urlsafe_b64decode(body['signature'].removeprefix('ed25519:'))
open('sig.bin', 'wb').write(sig)
"
# Verify
openssl pkeyutl -verify -pubin -inkey pub.der -keyform DER -rawin -in payload.bin -sigfile sig.binYou should see Signature Verified Successfully.
Step 3 — Use JWKS for production verification
For production verification at scale, fetch the active signing keys via the standard JWKS endpoint:
curl https://api.airaproof.com/api/v1/.well-known/jwks.json{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"alg": "EdDSA",
"use": "sig",
"kid": "aira-signing-key-v1",
"x": "uBa2zVOsAycukt1__mCaACAh0cC4RQ5yZQDNAsfm5io"
}
]
}Ed25519 keys are encoded per RFC 8037: kty=OKP, crv=Ed25519, alg=EdDSA, x is the base64url-no-padding raw public key. Any JWKS-aware library (Auth0, jose, jsonwebtoken, jwks-rsa, etc.) can fetch this URL and verify any Aira receipt without knowing about Aira at all.
What the receipt commits to
The signed_payload includes everything an auditor needs to chain a receipt to its full upstream context:
| Field | Meaning |
|---|---|
receipt_version | Schema version. Currently 1.2. |
alg | Algorithm used to sign. Ed25519 today; pluggable backend supports more. |
action_uuid | UUID of the agent action. |
org_uuid | UUID of the org that owns the action. |
agent_id | The agent slug (e.g. payments-agent). |
agent_version | Version of the agent at the time of the action. |
action_type | Free-form action category (e.g. wire_transfer). |
details_hash | SHA-256 of the action's details field. The agent never sends raw PII to Aira; only the hash is committed. |
outcome_hash | SHA-256 of the actual outcome reported by the agent. NULL if no outcome details were provided. |
instruction_hash | SHA-256 of the prompt that triggered the action. |
model_id / model_version | Which model produced the action. |
parent_action_uuid / parent_payload_hash | Link to the previous action in the chain of custody. The parent's payload hash is signed inside this payload, so the chain is cryptographically linked, not just FK-linked. |
authorization_ref.policy_evaluation_uuid | UUID of the policy decision that allowed this action. |
authorization_ref.approval_authorization_uuid | UUID of the human authorization (if the action was held for review). |
created_at | ISO timestamp when the action was authorized. |
Plus an out-of-band RFC 3161 timestamp token that proves Aira saw the receipt at a specific moment in time, signed by an independent timestamping authority.
What "valid" actually means
The verify endpoint returns valid: true only when all four of these are true:
- The action exists.
- The receipt exists for that action.
- The reconstructed canonical payload hashes to the stored
payload_hash. - The Ed25519 signature on
payload_hashverifies against the published public key.
If any of these fail, valid: false is returned with a clear message ("Action not found", "Signature does not verify against the published public key. This receipt has been tampered with or the signing key has been rotated.", etc.).
You do not need to trust Aira's verdict. The same response gives you every byte you need to re-run the same check yourself.
Cryptographic Receipts
Receipts are minted by notarize(), not by authorize(). They commit to both the agent's original intent and the real-world outcome.
Framework integrations
First-class SDK integrations for LangChain, Vercel AI, OpenAI Agents, AWS Bedrock, Google ADK, CrewAI, and MCP. Each is honestly labeled gate, audit, or adapter.