- You register a public key (safe to share).
- Clawb gives you a one-time challenge (random bytes).
- Your agent signs that challenge with its private key (secret).
- Clawb verifies the signature using the stored public key.
What you need (inputs)
From the register step you will have:agent_idchallenge_idchallenge(a base64 string)
- the agent’s private key (keep it secret)
Step-by-step: how to attest
1) Register the agent (get a challenge)
CallPOST /v1/agents/register with your agent’s public key.
Clawb returns:
2) Decode the challenge (base64 → bytes)
The challenge is shown as base64 because JSON can’t safely carry raw bytes. Important: you must sign the decoded bytes, not the base64 text. Conceptually:3) Sign the decoded bytes with Ed25519
4) Submit the signature
CallPOST /v1/agents/attest:
What does “sign the decoded challenge bytes” mean?
You will see something like:challenge = "MjAy..."(base64 text)
base64_decode(challenge)(the underlying bytes)
- ✅ correct:
sign(base64_decode(challenge)) - ❌ wrong:
sign("MjAy..." as text)
Common errors (and what they usually mean)
410 challenge expired: you waited too long (challenge has a short lifetime). Re-register to get a new challenge.409 challenge already used: a challenge is one-time use. Re-register.401 invalid signature: usually one of:- you signed the base64 string instead of decoded bytes
- you used the wrong private key
- you accidentally encoded/decoded with the wrong base64 variant
Attestation examples (Node, Python, Go)
Key format note
If you copied the private key from the dashboard and it says something like “PKCS8 base64”, that usually means:- your private key is PKCS#8 DER, represented as a base64 string
challenge_b64= thechallengeyou got from registerpriv_pkcs8_b64= your base64 PKCS#8 private key
Node.js (built-in crypto)
After attestation: signing requests (step-by-step)
Once your agent is active, you’ll typically sign requests that matter (for example when calling your service, or when calling Clawb signed endpoints).Required headers
X-Clawb-Agent-Id: youragent_idX-Clawb-Timestamp: unix epoch milliseconds (example:1739140000000)X-Clawb-Nonce: random string (UUID is fine)X-Clawb-Signature: base64 Ed25519 signature
milliseconds; using seconds causes signature validation failure.
Canonical string (exact format)
Build this string (newlines matter):METHODis uppercase (POST,GET, …)PATHis only the path (example:/v1/check) — not the full URLTIMESTAMPis unix epoch milliseconds fromX-Clawb-TimestampSHA256(body)is lowercase hex of the SHA-256 of the raw request body bytes
X-Clawb-Signature.
Why timestamp + nonce?
They help prevent replay attacks (someone capturing a request and re-sending it later).UI note: “Attest now”
The dashboard may offer an “Attest now” button. That button is a convenience for development:- if the browser generated the keypair, the browser already has the private key
- so it can sign locally and call
/v1/agents/attest