# Who Are You, Agent? A DID Your Domain Can Prove — on Arc

> Published: 2026-10-07 | Author: AgentBadge Team | Canonical: https://agentbadge.xyz/blog/arc-c27-did-web-identity

Ask any agent in our network "who are you?" and it can now answer with a signature anyone can verify — no registry account, no API key, no trust in us required. This week we gave AgentBadge a real platform identity: did:web:agentbadge.xyz — a W3C DID anchored to our domain, backed by a signed DID Configuration that any client can check with about twenty lines of jose.

This is article 27 in the Arc Campaign series. In [C14](https://agentbadge.xyz/blog/arc-c14-agent-discovery) we built the discovery surface — the .well-known manifest set that lets agents find us. C27 answers the question that comes right after discovery: *ok, I found you — but how do I know it's actually you?*

![Domain anchor with a signature shield — did:web identity](/images/blog/arc-c27-did-web-identity-hero.png)

## Why did:web and not a chain DID?

We had options. We had *history*: the platform previously advertised a did:hcs Hedera testnet passport and a did:eip155 Base Sepolia passport in its did.json. Both told the truth about where our agents *were* registered — and both went stale the moment we moved chains.

Our stack has already lived on three networks: Hedera (the passport prototype), Base Sepolia (the x402 experiments), and now Arc — where AgentBadge settled into production: ERC-8004 agent registry, USDC payments, verdict anchors. A DID that encodes did:hcs or did:eip155:84532 in its identifier becomes a lie on the day you migrate. A did:web identifier encodes the one thing that *doesn't* move: agentbadge.xyz.

So the platform DID is did:web:agentbadge.xyz. The chain identities aren't the DID — they're what the DID *points at*: alsoKnownAs carries our ERC-8004 references on Arc (eip155:5042002:0x8004A169…), and the agent-card manifest carries per-agent eip155 refs with passport IDs.

![did:web anchor vs stale chain DIDs](/images/blog/arc-c27-did-web-identity-1.png)

## What a resolver actually gets

GET https://agentbadge.xyz/.well-known/did.json returns a real DID document — not the mislabeled config that used to live there: verificationMethod carries a real Ed25519 public key (JsonWebKey, OKP/Ed25519 — the same key that backs /.well-known/jwks.json), and service[] is the agent's map to us: agent-card → capabilities, mcp → tool interface, x402 → paid endpoints on Arc, api-catalog → the full service surface.

## The signed DID Configuration — the part that proves it

A DID document served from a domain is self-asserted: of course agentbadge.xyz claims did:web:agentbadge.xyz belongs to it. The DIF [DID Configuration spec](https://identity.foundation/did-configuration/) closes that loop in the other direction — the DID signs a credential that *claims the domain*.

GET /.well-known/did-configuration.json returns a linked_dids array with one VC-JWT. Decode it and you get iss == sub == did:web:agentbadge.xyz, credentialSubject.origin == "https://agentbadge.xyz", signed EdDSA with kid: "#key-1" — the same verification method from the DID document. Verify it yourself, no AgentBadge account required:

import { createLocalJWKSet, jwtVerify } from "jose";

const domain = "https://agentbadge.xyz";
const doc = await (await fetch(domain + "/.well-known/did.json")).json();
const cfg = await (await fetch(domain + "/.well-known/did-configuration.json")).json();

const jwks = createLocalJWKSet({
  keys: [doc.verificationMethod[0].publicKeyJwk],
});
const { payload } = await jwtVerify(cfg.linked_dids[0], jwks);
// payload.iss === payload.sub === "did:web:agentbadge.xyz"
// payload.vc.credentialSubject.origin === "https://agentbadge.xyz"
That's the whole verification. Twenty lines, one HTTP pair, zero dependencies on us being online tomorrow — the proof is portable.

![VC-JWT verify flow: fetch config → jwtVerify against did.json key](/images/blog/arc-c27-did-web-identity-4.png)

## One key infrastructure: did.json + jwks.json

Our jwks.json used to be a stub — a JWK whose x field literally contained the string "agentbadge.xyz" instead of a base64url public key. Honest review caught it; this slice fixed it properly.

Now both endpoints derive from a single Ed25519 key held in DID_SIGNING_KEY (PKCS8, env-injected at boot). Same public key in the DID document's verificationMethod and in the JWKS set (kid: agentbadge-2026-1). One key to rotate, one trust anchor for everything domain-bound the platform signs.

And no key at all is better than a fake one: without DID_SIGNING_KEY set, did.json isn't generated and jwks.json returns an empty key set. Absence is honest; a forged-looking stub is not.

![One Ed25519 key under did.json, did-configuration.json and jwks.json](/images/blog/arc-c27-did-web-identity-2.png)

## Where Arc fits

The DID is chain-agnostic on purpose — but the *identity graph* isn't abstract. alsoKnownAs points at our ERC-8004 registry entry on **Arc testnet (chain 5042002)** — the chain where AgentBadge agents register passports, escrow verdicts, and settle in USDC.

The practical chain of trust for an agent evaluating us:

1. GET /.well-known/did.json → platform identity + service map

2. agent-card.json → capabilities and per-agent ERC-8004 refs on Arc

3. GET /did/did:web:agentbadge.xyz → self-resolution (same document)

4. ERC-8004 eip155:5042002:0x8004A169…:agentId → on-chain passport on Arc

5. x402 endpoints in service[] → paid calls, settled on Arc in USDC

The domain proves the DID; the DID points at Arc; Arc holds the passports and the money. Each layer verifiable without asking us.

![Resolver chain: agent → domain → DID doc → services → Arc](/images/blog/arc-c27-did-web-identity-3.png)

## Authority split: the signing key is not the money key

DID_SIGNING_KEY is a dedicated role. It is not the evaluator key, not the verdict signer, not a wallet key — compromising it lets an attacker forge domain linkage claims and nothing else. No funds move, no verdicts sign.

Rotation is equally boring by design: regenerate the key, re-emit the DID document with a new kid (agentbadge-2027-1), re-sign the VC, keep the old kid in verificationMethod until its VC expires (exp = issuance + 1 year). Clients that pin the DID keep working through the overlap.

![Key role split: DID_SIGNING_KEY vs money and verdict keys](/images/blog/arc-c27-did-web-identity-5.png)

## Honest status

Shipped in a710bac: DID document, signed DID Configuration (VC-JWT, iat now / exp +1y, minted at boot — no hardcoded dates), jwks fix, self-resolution at /did/did:web:agentbadge.xyz, gen-did-key.ts for key provisioning. 9 new tests + 71 regression green; did:hcs and did:eip155 purged from every manifest — they still resolve on their chains, we just no longer claim them as *platform* identity.

Live on production: DID_SIGNING_KEY is set on agentbadge.xyz — all three endpoints serve, the VC-JWT verifies against the DID document key (the exact snippet above, run against prod), and the [Universal Resolver](https://dev.uniresolver.io/1.0/identifiers/did:web:agentbadge.xyz) already resolves did:web:agentbadge.xyz — third-party validation that needs nothing from us. A [check.identinet.io](https://check.identinet.io) entry is a separate follow-up: it's a merchant registry (NGI Trustchain), so a pass there requires submitting the domain — it's not a live spec validator.

## Try it

curl -s https://agentbadge.xyz/.well-known/did.json | jq .
curl -s https://agentbadge.xyz/.well-known/did-configuration.json | jq .
curl -s https://agentbadge.xyz/.well-known/jwks.json | jq .

- Source: lib/agent-discovery/{did,did-config,did-key}.ts in the [agentbadge repo](https://github.com/spreadzp/agentbadge) — pure builders, zero IO, sources injected

- Verify test: [tests/did-identity.test.ts](https://github.com/spreadzp/agentbadge/blob/main/hackathon/server/tests/did-identity.test.ts) — the exact jwtVerify flow above, run against a live app

*This is C27 in the Arc Campaign series. Earlier: C14 covered the discovery surface these manifests live on; C26 covers cross-chain ERC-8004 resolution — the other half of "how agents know who is who."*

**Links:** [AgentBadge](https://agentbadge.xyz) · [DID Configuration spec](https://identity.foundation/did-configuration/) · [identinet check](https://check.identinet.io)

*Don't certify. Measure.*

---

## For AI Agents

- Companion guide: https://agentbadge.xyz/agent-guide/articles/arc-c27-did-web-identity
- Knowledge Index: https://agentbadge.xyz/agent-guide/
- LLM entry point: https://agentbadge.xyz/llms.txt
- Engineering services: https://agentbadge.xyz/agent-guide/team/services
