Core Concepts / Beginner / 8 min

Decentralized Identifiers (DIDs)

Cryptographic identities that work across systems—no central authority required

The Problem DIDs Solve

Traditional identifiers depend on a central authority:

  • email@company.com — Company controls it; if you leave, you lose it
  • @username — Platform controls it; if they suspend you, it’s gone
  • API key abc123 — Service provider issues and can revoke it

When AI agents need to operate across organizational boundaries, who issues their identity? If Bank A’s agent talks to Bank B’s system, Bank B won’t trust an identifier Bank A controls—and vice versa.

Decentralized Identifiers (DIDs) solve this: cryptographic identifiers where the holder controls the keys, not a central authority.


What Is a DID?

A DID is a globally unique identifier that looks like this:

did:webvh:example.com:agent-42

Breaking it down:

  • did: — signals this is a DID
  • webvh — the DID method (how it resolves)
  • example.com:agent-42 — the method-specific identifier

Key properties:

  1. Self-owned — You generate the DID and hold the private keys
  2. Verifiable — Anyone can cryptographically verify you control it
  3. Resolvable — The DID resolves to a DID Document containing public keys and service endpoints
  4. Persistent — It works as long as you hold the keys, regardless of which service you’re using

How DIDs Work

1. Creating a DID

// Generate a key pair
const keyPair = await generateKeyPair('Ed25519')

// Create a DID (method: webvh)
const did = `did:webvh:yourcompany.com:agent-${uuid()}`

// The DID Document (stored at a well-known location)
const didDocument = {
  "@context": "https://www.w3.org/ns/did/v1",
  "id": did,
  "verificationMethod": [{
    "id": `${did}#key-1`,
    "type": "Ed25519VerificationKey2020",
    "controller": did,
    "publicKeyMultibase": keyPair.publicKey
  }]
}

2. Resolving a DID

Anyone can resolve a DID to get its DID Document:

// Resolver fetches the DID Document
const didDoc = await resolve('did:webvh:yourcompany.com:agent-42')

// Returns public keys, service endpoints, etc.
console.log(didDoc.verificationMethod[0].publicKeyMultibase)

3. Proving Control

To prove you control a DID, sign a challenge with your private key:

// Verifier sends a challenge
const challenge = 'prove-you-control-this-DID'

// You sign it with your private key
const signature = await sign(challenge, privateKey)

// Verifier checks the signature against your DID's public key
const isValid = await verify(signature, challenge, didDoc.verificationMethod[0].publicKeyMultibase)
// → true

DID Methods: did:webvh

There are many DID methods (web, key, ion, ethr, etc.). Affinidi primarily uses did:webvh (Web Verifiable History):

Why did:webvh?

  • Web-based — Resolves via HTTPS, no blockchain required
  • Verifiable history — Tracks key rotation in a tamper-evident log
  • Time-travelable — You can resolve what the DID looked like at any point in time
  • Safe key rotation — If a key is compromised, rotate to a new key without losing the DID

How it works:

  1. DID Document stored at https://yourcompany.com/.well-known/did.json
  2. Every update creates a new version-controlled entry
  3. Hash chain ensures history can’t be rewritten
  4. Old signatures remain verifiable even after key rotation

Example:

did:webvh:affinidi.com:agent-gateway
→ resolves to https://affinidi.com/.well-known/did.json
→ returns DID Document with current public keys + version history

DIDs vs Traditional Identifiers

AspectTraditional (e.g., API key)Decentralized Identifier (DID)
OwnershipService provider controlsYou control (private keys)
PortabilityTied to one serviceWorks across any system that supports DIDs
RevocationProvider can revoke anytimeOnly you can revoke (or rotate keys)
VerificationProvider must confirmAnyone can verify cryptographically
Cross-org trustRequires federation/SSONative—no shared infrastructure needed

Real-World Use Cases

1. AI Agent Identity

Problem: Agents share service accounts, so logs show “account acted” not “which agent.”

Solution: Each agent gets a DID. Every action signed with the agent’s private key → audit trail shows which agent acted.

const agent = {
  did: 'did:webvh:company.com:agent-42',
  privateKey: '...'
}

// Agent signs every request
const request = { action: 'access-PHI', patient: '12345' }
const signature = await sign(request, agent.privateKey)

// Log records: Agent-42 (DID + signature) accessed PHI

2. Cross-Org Agent Trust

Problem: Bank A’s agent talks to Bank B’s system—how does Bank B trust it?

Solution: Agent carries a DID + signed mandate. Bank B verifies the DID signature and checks authorization.

// Bank A's agent
const agentDID = 'did:webvh:bankA.com:trading-agent-5'

// Mandate (signed by Bank A)
const mandate = {
  agent: agentDID,
  authorizedBy: 'did:webvh:bankA.com:trader-alice',
  permissions: ['execute-trades'],
  expiry: '2026-12-31'
}

// Bank B verifies:
// 1. Agent's DID signature is valid
// 2. Mandate signature from Bank A is valid
// 3. Agent is still authorized (check Trust Registry)

3. Per-Agent Attribution

Problem: Incident response—“which agent modified production?”

Solution: Every agent has a DID, every change logged with DID + signature.

// Audit log entry
{
  timestamp: '2026-07-21T19:15:00Z',
  action: 'modify-config',
  actor: 'did:webvh:company.com:agent-99',
  signature: '...',
  authorizer: 'did:webvh:company.com:engineer-bob'
}

// Instant attribution—no detective work

DIDs in the Affinidi Stack

Agent Gateway issues DIDs for every agent:

// Agent onboarding
const newAgent = await agentGateway.createAgent({
  name: 'Trading Agent 5',
  authorizedBy: 'trader-alice@bankA.com'
})

// Returns:
{
  did: 'did:webvh:bankA.com:agent-5',
  keyPair: { public, private },
  didDocument: { ... }
}

Trust Registry stores authorization policies linked to DIDs:

// Query: Is this DID authorized?
const isAuthorized = await trustRegistry.query({
  agent: 'did:webvh:bankA.com:agent-5',
  action: 'execute-trades',
  resource: 'AAPL'
})
// → { authorized: true, authorizedBy: 'did:webvh:bankA.com:trader-alice' }

Elements Services issues verifiable credentials to DIDs:

// Issue a mandate credential
const mandate = await elements.issueCredential({
  holder: 'did:webvh:bankA.com:agent-5',
  type: 'TradingMandate',
  claims: {
    authorizedBy: 'trader-alice',
    permissions: ['execute-trades'],
    expiry: '2026-12-31'
  }
})

Getting Started

For Developers

Create a DID:

npm install @affinidi/affinidi-tdk
import { DID } from '@affinidi/affinidi-tdk'

const did = await DID.create({
  method: 'webvh',
  domain: 'yourcompany.com'
})

console.log(did.id) // did:webvh:yourcompany.com:...
console.log(did.keyPair) // { public, private }

Resolve a DID:

const didDoc = await DID.resolve('did:webvh:affinidi.com:agent-gateway')
console.log(didDoc.verificationMethod)

For Architects

When to use DIDs:

  • ✅ Agents operating across organizational boundaries
  • ✅ Audit trails that need to prove identity cryptographically
  • ✅ Avoiding shared credentials/service accounts
  • ✅ Long-lived agent identity that survives key rotation

When NOT to use DIDs:

  • ❌ Internal human authentication (use SSO/OIDC)
  • ❌ Short-lived, ephemeral processes
  • ❌ Systems where cryptographic verification is overkill

Further Reading

W3C Specification:

Affinidi Docs:

Related Deep Dives:

Related Solutions:

Cookie Preferences

We use cookies to enhance your experience. You can manage your preferences below. For more information, read our Cookie Policy.

Strictly Necessary Always Active

These cookies are essential for core website functions such as security, session integrity, and cookie preference storage. They cannot be disabled.

  • _cf_bm: Distinguishes humans from bots (Cloudflare) · 30m
  • _cfuvid: Ensures secure browsing (Cloudflare) · Session
  • __hs_initial_opt_in: Prevents HubSpot's banner · 7 days
  • _gtm_debug: GTM debug mode (testing only) · Session
Analytics

These cookies help us understand how visitors interact with the site so we can improve content and performance. All data is aggregated and anonymous.

  • _ga, _gid, _gat: Google Analytics · Session – 2 years
  • __hstc, hubspotutk, __hssrc: HubSpot visitor tracking · 13 months
  • __hs_opt_out: HubSpot opt-out preference · 6 months
Marketing & Targeting

These cookies allow us and our partners to serve personalised ads and measure campaign performance.

  • _gcl_au, _gcl_dc: Google Ads conversion tracking · 90 days
  • IDE: Google Display Network personalisation · 1 year
  • _fbp: Meta / Facebook remarketing · 90 days
  • li_gc, _li_fat_id, bcookie: LinkedIn tracking · 1–24 months
  • guest_id, personalization_id: Twitter/X analytics · 2 years