Deep technical reference for Clawdentity's protocol, verification pipeline, security model, and deployment.
For an overview and quick start, see README.md.
- Agent-to-Agent Communication: Complete Flow
- Verification Pipeline
- Operator Controls
- What Gets Shared
- Core Features (MVP)
- Discovery Mechanisms
- Security Architecture
- Deployment
- MVP Goals
OpenClaw webhooks are a great transport layer, but they authenticate using a single shared webhook token. OpenClaw requires hooks.token when hooks are enabled, and inbound calls must provide the token (e.g., Authorization: Bearer ... or x-openclaw-token: ...).
OpenClaw docs: https://docs.openclaw.ai/automation/webhook
That means "just replace Bearer with Claw" does not work without upstream changes.
For MVP, Clawdentity runs as a proxy/sidecar in front of OpenClaw:
Caller Agent
|
| Authorization: Claw <AIT> + X-Claw-Proof/Nonce/Timestamp
v
Clawdentity Proxy (verifies identity + trust policy + rate limits)
|
| x-openclaw-token: <hooks.token> (internal only)
v
OpenClaw Gateway (normal /hooks/agent handling)
What happens to the OpenClaw hooks token?
- It stays private on the gateway host.
- Only the proxy uses it to forward requests to OpenClaw.
- You never share it with other humans/agents.
This section walks through every step from zero to two OpenClaw agents exchanging their first message. Each step adds a security guarantee that the shared-token model cannot provide.
┌─────────────────────────────────────────────────────────────────────────┐
│ CLAWDENTITY REGISTRY │
│ │
│ Issues identities (AIT) · Publishes revocation list (CRL) │
│ Validates agent auth · Manages invite-gated onboarding │
└───────────────┬─────────────────────────────────┬──────────────────────┘
│ │
issues AIT + auth issues AIT + auth
│ │
┌───────────▼──────────┐ ┌───────────▼──────────┐
│ AGENT ALICE │ │ AGENT BOB │
│ (OpenClaw + keys) │ │ (OpenClaw + keys) │
│ │ │ │
│ Ed25519 keypair │ │ Ed25519 keypair │
│ AIT (signed passport)│ │ AIT (signed passport│
│ Auth tokens │ │ Auth tokens │
└───────────┬───────────┘ └──────────┬───────────┘
│ │
signs every request signs every request
with private key with private key
│ │
┌───────────▼──────────┐ ┌───────────▼──────────┐
│ ALICE'S PROXY │◄─────────│ Bob sends signed │
│ (Cloudflare Worker) │ HTTP POST│ request to Alice │
│ │ │ │
│ Verifies identity │ └──────────────────────┘
│ Checks revocation │
│ Enforces trust pairs │
│ Rejects replays │
│ Rate limits per agent│
└───────────┬───────────┘
│
only verified requests
reach OpenClaw
│
┌───────────▼──────────┐
│ ALICE'S OPENCLAW │
│ (localhost, private) │
│ │
│ Receives message │
│ Never exposed to │
│ public internet │
└───────────────────────┘
An admin creates an invite code. A new operator redeems it to get API access.
Admin Registry
│ │
│ clawdentity invite create │
│──────────────────────────────►│ Generates clw_inv_<random>
│◄──────────────────────────────│ Stores with optional expiry
│ │
│ Shares invite code │
│ out-of-band (email, etc.) │
│ │
New Operator Registry
│ │
│ clawdentity invite redeem │
│──────────────────────────────►│ Creates human account
│◄──────────────────────────────│ Issues API key (shown once)
│ │
│ Stores API key locally │
Security: Invite codes are single-use and time-limited. One agent per invite prevents bulk abuse.
The operator creates an agent identity. The private key never leaves the machine.
CLI (operator's machine) Registry
│ │
│ 1. Generate Ed25519 keypair │
│ (secret.key stays local) │
│ │
│ 2. POST /v1/agents/challenge │
│ { publicKey } │
│─────────────────────────────────────►│ Generates 24-byte nonce
│◄─────────────────────────────────────│ Returns { challengeId,
│ │ nonce, ownerDid }
│ │
│ 3. Sign canonical proof with │
│ private key (proves ownership) │
│ │
│ 4. POST /v1/agents │
│ { name, publicKey, challengeId, │
│ challengeSignature } │
│─────────────────────────────────────►│ Verifies signature
│ │ Creates agent record
│ │ Issues AIT (JWT, EdDSA)
│ │ Issues auth tokens
│◄─────────────────────────────────────│ Returns { agent, ait,
│ │ agentAuth }
│ Stores locally: │
│ ~/.clawdentity/agents/<name>/ │
│ ├── secret.key (private, 0600) │
│ ├── public.key │
│ ├── ait.jwt (signed passport) │
│ ├── identity.json │
│ └── registry-auth.json │
Security: Challenge-response proves the operator holds the private key without ever transmitting it. The 5-minute challenge window prevents delayed replay. Each challenge is single-use.
What's in the AIT (Agent Identity Token):
| Claim | Purpose |
|---|---|
sub |
Agent DID (did:cdi:<authority>:agent:<ulid>) — unique identity |
ownerDid |
Human DID — who owns this agent |
cnf.jwk.x |
Agent's public key — for verifying PoP signatures |
jti |
Token ID — for revocation tracking |
iss |
Registry URL — who vouches for this identity |
exp |
Expiry — credential lifetime (1-90 days) |
Operators exchange peer metadata out-of-band (alias, DID, proxy URL). No relay invite code is required.
Alice's Operator Bob's Operator
│ │
│ POST /pair/start (proxy API) │
│ receives clwpair1_... ticket │
│────────────────────────────────────────►│
│ │
│ │ POST /pair/confirm (proxy API)
│ │ ticket + responder metadata
│ │
│ │ Persists trusted peer metadata:
│ │ alias + DID + proxy URL
│ │
│ │ Relay transform reads paired peers
Security: Setup uses only public peer metadata (DID + proxy URL + alias). No keys, tokens, or secrets are exchanged. Alice and Bob must complete proxy pairing (/pair/start + /pair/confirm) before either side can send messages.
Bob's OpenClaw triggers the relay. Every request is cryptographically signed.
Bob's OpenClaw relay-to-peer.ts Alice's Proxy Alice's OpenClaw
│ │ │ │
│ Hook trigger: │ │ │
│ { peer: "alice", │ │ │
│ message: "Hi!" } │ │ │
│─────────────────────►│ │ │
│ │ │ │
│ 1. Load Bob's credentials │ │
│ (secret.key, ait.jwt) │ │
│ 2. Look up "alice" in │ │
│ peers.json → proxy URL │ │
│ 3. Sign HTTP request: │ │
│ ┌─────────────────────┐ │ │
│ │ Canonical string: │ │ │
│ │ POST /hooks/agent │ │ │
│ │ timestamp:<unix> │ │ │
│ │ nonce:<random> │ │ │
│ │ body-sha256:<hash> │ │ │
│ │ │ │ │
│ │ Ed25519.sign(canon, │ │ │
│ │ secretKey) → proof │ │ │
│ └─────────────────────┘ │ │
│ 4. Send signed request: │ │
│ POST https://alice-proxy/hooks/agent │
│ Authorization: Claw <ait> │ │
│ X-Claw-Timestamp: <ts> │ │
│ X-Claw-Nonce: <random> │ │
│ X-Claw-Body-SHA256: <hash> │ │
│ X-Claw-Proof: <signature> │ │
│ X-Claw-Agent-Access: <token>│ │
│ │─────────────────────►│ │
│ │ │ │
│ │ VERIFICATION PIPELINE │
│ │ ───────────────────── │
│ │ ① Verify AIT signature │
│ │ (registry EdDSA keys) │
│ │ ② Check timestamp skew │
│ │ (max ±300 seconds) │
│ │ ③ Verify PoP signature │
│ │ (Ed25519 from AIT cnf key) │
│ │ ④ Reject nonce replay │
│ │ (per-agent nonce cache) │
│ │ ⑤ Check CRL revocation │
│ │ (signed list from registry) │
│ │ ⑥ Enforce trust pair │
│ │ (is Bob trusted for Alice?) │
│ │ ⑦ Validate agent access token │
│ │ (POST to registry) │
│ │ │ │
│ │ │ ALL CHECKS PASSED │
│ │ │ │
│ │ │ Forward to OpenClaw: │
│ │ │ POST /hooks/agent │
│ │ │ x-openclaw-token: <t> │
│ │ │──────────────────────►│
│ │ │ │ Message
│ │ │◄──────────────────────│ delivered!
│ │◄─────────────────────│ 202 │
│◄─────────────────────│ │ │
| Check | Failure | HTTP Status | Meaning |
|---|---|---|---|
| AIT signature | PROXY_AUTH_INVALID_AIT |
401 | Token is forged or tampered |
| Timestamp skew | PROXY_AUTH_TIMESTAMP_SKEW |
401 | Request is too old or clock is wrong |
| PoP signature | PROXY_AUTH_INVALID_PROOF |
401 | Sender doesn't hold the private key |
| Nonce replay | PROXY_AUTH_REPLAY |
401 | Same request was sent twice |
| CRL revocation | PROXY_AUTH_REVOKED |
401 | Agent identity has been revoked |
| Trust policy | PROXY_AUTH_FORBIDDEN |
403 | Agent is valid but not trusted for this recipient |
| Agent access token | PROXY_AGENT_ACCESS_INVALID |
401 | Session token expired or revoked |
| Rate limit | PROXY_RATE_LIMIT_EXCEEDED |
429 | Too many requests from this agent |
- Action: registry API revoke (
DELETE /v1/agents/:id) - Scope: global (registry-level identity revocation)
- Effect: every receiving proxy rejects that revoked token once CRL refreshes.
- Use when: key compromise, decommissioning, or ownership/admin suspension events.
- Action: remove/deny trusted caller pair in local proxy trust state (or keep approval-required first contact)
- Scope: local only (that specific gateway/proxy)
- Effect: caller is blocked on this gateway immediately, but remains valid elsewhere unless globally revoked.
- Use when: policy mismatch, abuse from a specific caller, temporary trust removal.
- Global revoke = sender owner/admin authority at registry.
- Local block = receiver operator authority at their own gateway.
- Opposite-side operator cannot globally revoke someone else's agent identity; they can only deny locally.
- Receiver blocks caller locally for immediate containment.
- Sender owner/admin performs registry revoke for ecosystem-wide invalidation.
- Proxies return:
401for invalid/expired/revoked identity403for valid identity that is not trusted locally for the target recipient
- Shared in-band on each request: AIT + PoP proof headers
- Shared publicly: registry signing public keys + CRL (signed, cacheable)
- Never shared: the agent's private key or identity folder
- Handled by:
apps/registry,packages/sdk - Registry issues signed AITs tied to agent DID + owner DID.
- Registry publishes verification material (
/.well-known/claw-keys.json) and signed CRL. - SDK + proxy verify signatures, expiry windows, and token validity locally.
- Handled by:
packages/sdk,apps/proxy - Each request carries PoP-bound headers:
Authorization: Claw <AIT>X-Claw-TimestampX-Claw-NonceX-Claw-Body-SHA256X-Claw-Proof
- Proxy rejects tampered payloads, nonce replays, and stale timestamps.
- Handled by:
apps/proxy - Proxy Worker verifies AIT + CRL + PoP before forwarding to OpenClaw.
- Enforces durable trust pairs for sender/recipient DID.
- Applies per-agent rate limiting.
- Keeps
hooks.tokenprivate and only injects it internally during forward. - By default,
INJECT_IDENTITY_INTO_MESSAGE=trueto prepend a sanitized identity block (agentDid,ownerDid,issuer,aitJti) into/hooks/agentpayloadmessage. SetINJECT_IDENTITY_INTO_MESSAGE=falseto keep payloads unchanged.
- Handled by:
crates/clawdentity-cli,crates/clawdentity-core clawdentity init+clawdentity registerfor local identity bootstrap and registry enrollment.clawdentity agent create <name>for local keypair + agent registration.clawdentity agent inspect <name>for local identity/auth state inspection.clawdentity agent auth refresh <name>/clawdentity agent auth revoke <name>for per-agent auth lifecycle.clawdentity api-key create|list|revokefor PAT lifecycle.clawdentity install --platform <platform>for provider artifact install/bootstrap.clawdentity provider setup --for <platform> --agent-name <name>for runtime/hook setup.clawdentity provider doctor --for <platform>andprovider relay-test --for <platform> --peer <alias>for readiness and relay diagnostics.clawdentity connector start <agentName>andconnector service install|uninstall <agentName>for runtime operations.
- Runtime probes local OpenClaw base URL reachability on an interval:
CONNECTOR_OPENCLAW_PROBE_INTERVAL_MS(default10000)CONNECTOR_OPENCLAW_PROBE_TIMEOUT_MS(default3000)
- While probe state is down, inbound replay skips direct hook delivery attempts and keeps messages pending in the connector inbox.
- Runtime replay retries OpenClaw hook delivery with bounded backoff:
CONNECTOR_RUNTIME_REPLAY_MAX_ATTEMPTS(default3)CONNECTOR_RUNTIME_REPLAY_RETRY_INITIAL_DELAY_MS(default2000)CONNECTOR_RUNTIME_REPLAY_RETRY_MAX_DELAY_MS(default8000)CONNECTOR_RUNTIME_REPLAY_RETRY_BACKOFF_FACTOR(default2)
- Hook
401/403responses are treated as auth-rotation signals: connector re-reads~/.clawdentity/openclaw-relay.jsonand retries. - Connector forwards structured identity headers to local OpenClaw hooks:
x-clawdentity-agent-didx-clawdentity-to-agent-didx-clawdentity-verified
- Connector
/v1/statusnow surfacesinbound.openclawGatewayalongsideinbound.openclawHook.
- Handled by:
apps/registry,apps/cli - Invite-gated registration model with admin-issued invite codes.
- One-agent-per-invite policy for simple quota and abuse control.
- Feature work follows a deployment-first gate tracked in GitHub issues.
- Handled by:
apps/registry,apps/proxy,apps/cli - Out-of-band contact card sharing.
- Registry
gateway_hintresolution. - Pairing-code flow for first-contact trust approval (PAT-verified owner start + one-time confirm).
Expected operator flow starts from the CLI command:
clawdentity install --platform openclaw
clawdentity provider setup --for openclaw --agent-name <agent-name>Installer logic prepares OpenClaw runtime artifacts automatically:
~/.openclaw/skills/clawdentity-openclaw-relay/SKILL.md~/.openclaw/skills/clawdentity-openclaw-relay/references/*~/.openclaw/skills/clawdentity-openclaw-relay/relay-to-peer.mjs~/.openclaw/hooks/transforms/relay-to-peer.mjs
Install is idempotent and logs deterministic per-artifact outcomes (installed, updated, unchanged).
The CLI package ships bundled skill assets so clean installs do not depend on a separate @clawdentity/openclaw-skill package at runtime.
- GitHub workflow:
.github/workflows/publish-cli.yml - Trigger:
workflow_dispatchwith inputs:version(semver, required)dist_tag(defaultlatest)
- Required GitHub secret:
NPM_TOKEN - Publish target: npm package
clawdentity - Workflow runs CLI lint/typecheck/test/build before publishing.
MVP supports three ways to "find" another agent:
- Out-of-band share: human shares a contact card (verify link + endpoint URL)
- Registry
gateway_hint: callee publishes an endpoint, callers resolve it via registry - Pairing code (proxy): "Approve first contact" to establish a mutual trusted agent pair
No one shares keys/files between agents. Identity is presented per request.
- Agent private key: secret, local only, never leaves agent machine.
- Registry signing key: secret, server-side only, signs AIT and CRL.
- OpenClaw
hooks.token: secret, only present on gateway host/proxy. - AIT + PoP headers: transmitted per request, safe to share in-band.
- Do not expose OpenClaw webhooks directly to the public internet. Follow OpenClaw guidance (loopback, tailnet, trusted reverse proxy). Docs: https://docs.openclaw.ai/automation/webhook
- Clawdentity PoP signatures must bind:
- method, path, timestamp, nonce, body hash
- and reject nonce replays
- Reject tampering: any change to method/path/body/timestamp/nonce invalidates proof.
- Reject unauthorized callers: AIT verification + trust-pair enforcement.
- Reject compromised identities quickly: CRL-based revocation checks.
- Contain abuse: per-agent rate limits at proxy boundary.
- Guarantees:
- caller identity can be cryptographically verified
- caller ownership is traceable via token claims
- revocation can be enforced without rotating shared OpenClaw token
- Limits:
- if the endpoint that holds the agent private key is compromised, attacker can sign as that agent until revocation
- if CRL refresh is delayed, enforcement follows configured staleness policy (
fail-openorfail-closed)
- Treat any identity fields (agent name/description) as untrusted input; never allow prompt injection via identity metadata.
- Keep OpenClaw behind trusted network boundaries; expose only proxy entry points.
- Rotate PATs and audit trusted pair entries regularly.
- Store PATs in secure local config only; create responses return token once and it cannot be retrieved later from the registry.
- Rotation baseline: keep one primary key + one standby key, rotate at least every 90 days, and revoke stale keys immediately after rollout.
Nx monorepo with pnpm workspaces:
clawdentity/
├── apps/
│ ├── registry/ — Identity registry (Cloudflare Worker)
│ │ Issues AITs, serves CRL + public keys
│ │ Worker config: apps/registry/wrangler.jsonc
│ ├── proxy/ — Verification proxy (Cloudflare Worker)
│ │ Verifies Clawdentity headers, forwards to OpenClaw
│ │ Worker config: apps/proxy/wrangler.jsonc
│ ├── cli/ — Operator CLI
│ │ Agent create/revoke, invite, api-key, config
│ └── openclaw-skill/ — OpenClaw skill integration
│ Relay transform for agent-to-agent messaging
├── packages/
│ ├── protocol/ — Canonical types + signing rules
│ │ AIT claims, DID format, HTTP signing, endpoints
│ └── sdk/ — TypeScript SDK
│ Sign/verify, CRL cache, auth client, crypto
└── Configuration
├── nx.json — Monorepo task orchestration
├── pnpm-workspace.yaml
└── tsconfig.base.json
- Development env (
ENVIRONMENT=development):pnpm dev:proxy - Local env (
ENVIRONMENT=local):pnpm dev:proxy:local - Fresh deploy-like env:
pnpm dev:proxy:fresh - Development deploy command:
pnpm -F @clawdentity/proxy run deploy:dev - Production deploy command:
pnpm -F @clawdentity/proxy run deploy:production - Environment intent:
localis local Wrangler development only;developmentandproductionare cloud deployment environments.
- Development env (
ENVIRONMENT=development):pnpm dev:registry - Development env with local D1 migration apply:
pnpm dev:registry:local
- GitHub workflow:
.github/workflows/deploy-develop.yml - Trigger: push to
develop - Runs full quality gates, then deploys:
- registry (
apps/registry, envdev) with D1 migrations - proxy (
apps/proxy, envdev)
- registry (
- Health checks must pass with
version == $GITHUB_SHAfor:https://dev.registry.clawdentity.com/health- deployed proxy
/healthURL (workers.dev URL extracted from wrangler output, or optionalPROXY_HEALTH_URLsecret override)
- Required GitHub secrets:
CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID
- Create agent identity (local keypair + registry-issued AIT)
- Send signed requests (PoP per request, replay-resistant)
- Verify locally (signature + expiry + cached CRL)
- Kill switch (revoke → proxy rejects within CRL refresh window)
- Discovery (share endpoint + verify link; optional pairing code)
- README.md — overview, quick start, and comparison
- Execution and issue governance: GitHub issue tracker