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 chainchain_hash— hash linking to previous receiptsignature— Ed25519 signature over chain_hashlog— 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/initonly 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
- API Reference — full endpoint documentation
- Client Verifier — verify receipts in your browser