Witness Quick Start

Notarize a JSON log, get a signed receipt, and verify it — all in under 5 minutes.

Get your API key

Sign up at solenoid.systems/pricing. All keys start with sm_ and work across all Solenoid products. See Authentication.

Initialize your chain

Every account needs to initialize once. This generates an Ed25519 keypair for signing receipts.

curl -X POST https://api.solenoid.systems/v1/witness/sys/init \
  -H "Authorization: Bearer sm_your_api_key_here"
{
  "success": true,
  "publicKey": {
    "kty": "OKP",
    "crv": "Ed25519",
    "x": "d75Q3..."
  },
  "createdAt": 1672531200
}

If you’ve already initialized, you’ll get {"success": false}. That’s fine — initialization is one-time.

Notarize a log

Submit any JSON object for notarization.

curl -X POST https://api.solenoid.systems/v1/witness/log \
  -H "Authorization: Bearer sm_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "log": {
      "event": "user.login",
      "user_id": "usr_123",
      "timestamp": 1672531200,
      "ip": "203.0.113.42"
    }
  }'
{
  "version": 1,
  "sequence_id": "42",
  "content_hash": "a1b2c3d4...",
  "prev_hash": "0000000000...",
  "chain_hash": "e4f6a8c2...",
  "signature": "rT8pY3v...",
  "timestamp": 1672531200,
  "public_key": {
    "kty": "OKP",
    "crv": "Ed25519",
    "x": "d75Q3..."
  },
  "log": {
    "event": "user.login",
    "user_id": "usr_123",
    "timestamp": 1672531200,
    "ip": "203.0.113.42"
  },
  "storage_status": "delivered",
  "storage_location": "s3://your-bucket/witness/receipts/42.json"
}

The receipt contains:

  • sequence_id — position in your chain
  • chain_hash — hash linking to previous receipt
  • signature — Ed25519 signature over chain_hash
  • log — your original JSON (embedded for self-contained proof)
  • storage_status — either “delivered” (200) or “buffered” (202)

Verify the receipt

Anyone can verify a receipt using the server-side endpoint.

curl -X POST https://api.solenoid.systems/v1/witness/verify \
  -H "Content-Type: application/json" \
  -d '{
    "receipt": {
      "version": 1,
      "sequence_id": "42",
      "content_hash": "a1b2c3d4...",
      "prev_hash": "0000000000...",
      "chain_hash": "e4f6a8c2...",
      "signature": "rT8pY3v...",
      "timestamp": 1672531200,
      "public_key": { "kty": "OKP", "crv": "Ed25519", "x": "d75Q3..." },
      "log": { "event": "user.login" }
    }
  }'
{
  "valid": true,
  "checks": {
    "signature_valid": true,
    "content_hash_matches": true,
    "chain_link_valid": true
  },
  "metadata": {
    "sequence_id": "42",
    "timestamp": 1672531200,
    "public_key": { "kty": "OKP", "crv": "Ed25519", "x": "d75Q3..." }
  }
}

All three checks must pass for the receipt to be valid.

Configure S3 storage (optional)

By default, receipts are buffered in purgatory (R2). To deliver receipts to your own S3 bucket, configure storage.

curl -X POST https://api.solenoid.systems/v1/witness/config \
  -H "Authorization: Bearer sm_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint": "https://s3.us-west-2.amazonaws.com",
    "bucket": "my-audit-logs",
    "region": "us-west-2",
    "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
    "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  }'
{
  "configured": true
}

After configuration, all receipts are pushed to s3://your-bucket/witness/receipts/{sequence_id}.json. If your S3 is unavailable, receipts are buffered in purgatory and you’ll receive a 202 response with storage_status: "buffered".

Get your public key

Download the public key once for offline verification. The chain_id comes from the init response above; it is the identifier a verifier uses, and it works without an API key.

curl https://api.solenoid.systems/v1/witness/pubkey/wc_9tQvXm2LpR4sK7nB1cD8fG3hJ5wY6zA0
{
  "kty": "OKP",
  "crv": "Ed25519",
  "x": "d75Q3Cl07Ue4KjbAQfYN..."
}

Publish the chain_id wherever you publish receipts: without it nobody can fetch the key that verifies them. Cache the key locally and use it to verify receipts offline with crypto.subtle.verify(). See the client-side verifier for an example.

Complete example

const API_KEY = process.env.SOLENOID_API_KEY;
const BASE = 'https://api.solenoid.systems/v1/witness';

// 1. Initialize (one-time)
await fetch(`${BASE}/sys/init`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${API_KEY}` }
});

// 2. Notarize log
const receipt = await fetch(`${BASE}/log`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    log: {
      event: 'payment.completed',
      amount: 5000,
      currency: 'USD',
      timestamp: Date.now()
    }
  })
}).then(r => r.json());

console.log('Notarized:', receipt.sequence_id);
console.log('Storage:', receipt.storage_status);

// 3. Verify receipt
const result = await fetch(`${BASE}/verify`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ receipt })
}).then(r => r.json());

console.log('Valid:', result.valid);
console.log('Checks:', result.checks);

Best practices

  • Initialize once — call sys/init only on first setup
  • Store receipts alongside logs — keep receipts in your database or filesystem
  • Verify periodically — verify receipts in your test suite or audit process
  • Cache public key — download once for offline verification
  • Handle purgatory — receipts with storage_status: "buffered" are safe in R2 until S3 recovers

Next steps