Verification
Verify signed approval decisions using the public API endpoint or perform offline verification with the Ed25519 public key.
Verification is the process of confirming that a signed approval decision is authentic — that it was really signed by the claimed approver and has not been tampered with. SignedApproval's verification is public and requires no authentication, so any third party can independently confirm an approval without trusting SignedApproval's servers.
There are two ways to verify: online verification via the API endpoint, and offline verification using the Ed25519 public key directly. Both produce the same mathematical result.
Online Verification
The simplest way to verify a decision is to call the public verification endpoint:
curl https://signedapproval.net/api/v1/approvals/req_abc123/verifyNo API key or authentication is required. The response includes the verification result:
{
"valid": true,
"decision": "approved",
"method": "passkey",
"verified_at": "2026-03-23T14:03:00.000Z",
"public_key": "MCowBQYDK2VwAyEA...",
"signature": "base64-ed25519-signature",
"payload": {
"v": 1,
"rid": "req_abc123",
"did": "dec_xyz789",
"approver": "a1b2c3d4e5f6g7h8",
"action": "9i8j7k6l5m4n3o2p",
"decision": "approved",
"method": "passkey",
"ts": 1709000000,
"exp": 1709086400,
"nonce": "abcdef1234567890abcdef1234567890"
}
}The server performs the Ed25519 verification and returns valid: true or valid: false. It also logs the verification attempt in signedapproval_validations for audit purposes.
Offline Verification
Once you have the signature, payload, and public key (all returned by the online endpoint), you can verify offline without ever contacting SignedApproval again:
// Node.js offline verification
import { createPublicKey, verify } from 'crypto';
// These values come from a previous API call or are cached
const publicKeyDer = Buffer.from(publicKeyBase64, 'base64');
const signature = Buffer.from(signatureBase64, 'base64');
// Reconstruct the canonical payload with sorted keys
const payload = JSON.stringify(payloadObject, Object.keys(payloadObject).sort());
const payloadBytes = Buffer.from(payload);
const publicKey = createPublicKey({
key: publicKeyDer,
format: 'der',
type: 'spki',
});
const isValid = verify(null, payloadBytes, publicKey, signature);
console.log('Offline verification:', isValid ? 'VALID' : 'INVALID');# Python offline verification
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.serialization import load_der_public_key
import base64, json
public_key_der = base64.b64decode(public_key_base64)
signature = base64.b64decode(signature_base64)
# Canonical payload with sorted keys
payload = json.dumps(payload_dict, sort_keys=True).encode()
public_key = load_der_public_key(public_key_der)
try:
public_key.verify(signature, payload)
print("Offline verification: VALID")
except Exception:
print("Offline verification: INVALID")What Verification Proves
- Integrity — The payload has not been modified since signing. Changing any field invalidates the signature.
- Authenticity — The decision was signed by the holder of the corresponding private key.
- Non-repudiation — The approver cannot deny having made the decision (assuming their private key was not compromised).
- Temporal binding — The timestamp and expiry are part of the signed payload, so they cannot be altered.
approver and action fields are hashed (first 16 chars of SHA-256) for privacy. If you need to confirm who approved or what was approved, you need the original user ID or action text to recompute the hash and compare.Verification Audit Log
Every online verification attempt is logged in signedapproval_validations, recording:
- The request ID that was verified
- Whether verification succeeded or failed
- The IP address of the verifier
- Timestamp of the verification
This creates an audit trail of who checked which approvals and when, useful for compliance reporting.