Fair warning: this post is going to introduce you to a W3C standard, a DIF-ratified specification, and a concept from the self-sovereign identity world.
I know. Bear with me.
By the end, you’ll understand why these things actually matter for the software you’re building right now, and you’ll have a mental model that makes the rest of the Layr8 docs click immediately.
Let’s skip the academic framing and get to the practical stuff.
The Problem DIDComm Solves
Imagine you’re building two services that need to communicate. Traditional approach:
- Service A has an HTTP endpoint
- Service B calls it with an API key to authenticate
- You write docs, share the key, handle key rotation, log the calls
This works fine when Service A and Service B are in the same organization. It gets complicated when they’re not — when you’re connecting a system you control to a partner’s system, or when you’re building AI agents that need to talk to each other across organizational boundaries.
The problems are predictable:
- Who issues and rotates the API keys?
- What happens when you need to revoke access for one party without affecting others?
- How do you know the message actually came from who it says it came from?
- How do you audit not just “a call was made” but “this specific identity made this call”?
DIDComm answers all of these. But the solution is built on a foundation you need to understand first: DIDs.
DIDs: The One-Minute Version
A DID (Decentralized Identifier) is a globally unique identifier that you control.
It looks like this: did:web:alice.example.com:agent
Behind every DID is a DID document — a public JSON file that contains:
- The entity’s public key(s)
- Service endpoints (where to reach them)
- Metadata about the identity
To resolve a DID, you look up its DID document. For did:web identifiers, this is a simple HTTPS fetch. The DID document lives at a predictable URL on the domain.
Here’s why this is powerful: the private key never leaves the entity that owns the DID. When that entity needs to prove who it is, it signs something with its private key. Anyone can verify the signature using the public key from the DID document. No shared secret. No central authority. No one who can revoke your identity without your participation.
This is different from every other authentication system you’ve used. OAuth requires an identity provider. API keys require an issuer. TLS certificates require a certificate authority. DIDs don’t require any of these. They’re self-sovereign — you generate them, you control them, and they travel with you wherever you go.
DIDComm: Messaging Built on Top of DIDs
DIDComm (DID Communication) is a messaging protocol built on the foundation of DIDs. It defines how two entities — each with a DID — can exchange messages that are:
End-to-end encrypted. Messages are encrypted using the recipient’s public key (from their DID document). Only the recipient can decrypt them. Not the transport layer. Not the routing infrastructure. Only the recipient.
Cryptographically authenticated. Messages are signed with the sender’s private key. The recipient can verify the signature using the sender’s public key. Not “this came from an IP address I recognize.” Actual cryptographic proof.
Transport-agnostic. DIDComm doesn’t care how the message gets delivered. HTTPS, WebSockets, email, Bluetooth, QR codes, sneakernet — the security model is the same regardless of transport. This is unusual and valuable: the trust travels with the message, not with the channel.
Addressed to identities, not locations. You send a DIDComm message to a DID, not to an IP address or URL. The routing infrastructure figures out delivery. This means you can move your service to a different server, change your domain, or switch hosting providers — and your DID stays the same. Nobody needs to update their configuration to find you.
What a DIDComm Message Looks Like
At its core, a DIDComm message is a JSON object with three things:
{
"type": "https://layr8.io/protocols/invoice/1.0/create",
"to": ["did:web:billing.acme.com:agent"],
"body": {
"amount": 4200,
"currency": "USD",
"reference": "PO-2026-441"
}
}The type field is a URI that identifies the protocol and message type. Think of it like a content-type header — it tells the recipient what kind of message this is and how to handle it.
The to field is a DID (or list of DIDs) — the recipient. Not a URL. Not an IP. A cryptographic identity.
The body is your payload. Whatever makes sense for the protocol.
That’s it. Your application code sends a plaintext message like this. The DIDComm library handles encryption, signing, and delivery. The recipient’s library handles decryption and verification. Your code never deals with keys.
Protocols: The Composability Layer
Here’s the part that makes DIDComm genuinely interesting for building systems.
DIDComm is a meta-protocol. It defines how to send messages securely, but it doesn’t define what messages mean. That’s left to protocols — higher-level conventions built on top of DIDComm.
A protocol is just a URI + a set of message types + a description of the interaction pattern. For example:
https://layr8.io/protocols/echo/1.0defines a simple request/response: send a message, get it echoed back.https://didcomm.org/basicmessage/2.0defines a simple one-way message with a text body.https://layr8.io/protocols/invoice/1.0could define a protocol for sending invoices and receiving acknowledgments.
You can define your own protocols. The type URI just has to be something you control. Want a protocol for your specific domain? Define it. Publish it. Anyone who fetches that URI can read the spec and implement against it.
This is DIDComm’s killer feature: every protocol inherits the security model. When you build a new protocol on DIDComm, you get encryption, authentication, non-repudiation, and transport independence for free. You don’t reinvent security every time you define a new interaction pattern.
Compare this to REST APIs: every new API requires its own authentication scheme, its own transport security configuration, its own audit logging. With DIDComm, those are solved at the protocol layer. You just describe the interaction.
The Three Operations You’ll Actually Use
In practice, working with DIDComm (via Layr8) boils down to three operations:
1. Send and wait for a response
const response = await agent.request({
to: "did:web:inventory.acme.com:service",
type: "https://layr8.io/protocols/inventory/1.0/check",
body: { sku: "A123", warehouse: "us-east" },
});
console.log(response.body.available); // 42This is the request/response pattern. You send a message, you wait for a correlated response. Like an HTTP GET, but addressed to an identity, encrypted, and signed.
2. Fire and forget
await agent.send({
to: "did:web:analytics.acme.com:collector",
type: "https://layr8.io/protocols/events/1.0/record",
body: { event: "order.created", orderId: "ORD-4421", ts: Date.now() },
});Send and move on. No response expected. Like a webhook, but the recipient verifies who sent it.
3. Continue a thread
// First message starts a thread
const first = await agent.request({
to: "did:web:partner.acme.com:agent",
type: "https://layr8.io/protocols/negotiation/1.0/propose",
body: { item: "widget-X", quantity: 100, price: 4200 },
});
// Follow-up on the same thread
await agent.send({
to: "did:web:partner.acme.com:agent",
type: "https://layr8.io/protocols/negotiation/1.0/counter",
thid: first.thid, // thread ID — correlates the messages
body: { price: 3800 },
});Thread IDs correlate messages in a conversation. A negotiation, a multi-step workflow, a back-and-forth — all of these are just messages with the same thread ID.
What You Don’t Have to Think About
This is the part worth pausing on.
When you use DIDComm via Layr8, you don’t deal with:
Key management. Your agent has a DID. The private key is managed by the Layr8 node. You never see it, never rotate it, never worry about it leaking.
Encryption. Every message is end-to-end encrypted before it leaves your node. You write plaintext JSON. The node handles encryption using the recipient’s public key from their DID document. You don’t touch crypto primitives.
Transport. You send to a DID. Layr8 resolves the DID document, finds the endpoint, and delivers the message. If the recipient’s endpoint changes, their DID document changes and Layr8 follows it. You don’t update configuration.
Authentication. Every inbound message has already been verified — Layr8 checked the signature before delivering it to your handler. You can trust that the message came from the DID it claims to be from.
Routing. If you and your recipient aren’t directly connected, Layr8 routes through intermediaries. You don’t know or care about the routing topology.
The result: your code is just business logic. The identity and security layer is handled by the infrastructure.
The Gotcha: Allow Lists
There’s one thing that surprises developers new to DIDComm: allow lists.
Each Layr8 node has a policy about who can send it messages. By default, a node won’t accept messages from arbitrary DIDs — the recipient’s node needs to trust the sender’s DID first.
This is a feature, not a bug. It’s the mechanism that makes “deny by default” work. But it means that if you try to send a message to an agent whose node doesn’t have your DID on its allow list, the message won’t be delivered.
For development, this is easy to manage — you add DIDs to your allow list explicitly. For production systems that need to accept messages from arbitrary parties, there are protocols for handling introductions and establishing trust before communicating.
Think of it like email spam filtering: the infrastructure doesn’t accept messages from everyone. You either have a prior relationship, or you go through a process to establish one.
Getting Started in 15 Minutes
The fastest path to running DIDComm:
# Install the SDK
npm install @layr8/sdk
# Or pip install layr8 for Pythonimport { Layr8 } from "@layr8/sdk";
const agent = new Layr8({
identity: process.env.LAYR8_DID, // Your DID from the developer portal
});
// Send a message to the Layr8 echo agent
const response = await agent.request({
to: "did:web:alice-sandbox.layr8.cloud:echo",
type: "https://layr8.io/protocols/echo/1.0/request",
body: { message: "Hello, DIDComm." },
});
console.log(response.body.message); // "Hello, DIDComm."That’s your first DIDComm round trip. From here, you can:
- Define your own protocol (just a URI and some JSON schemas)
- Connect to other agents on the network
- Host your agent on Layr8 with a persistent DID
The developer portal gives you a sandbox DID and a free tier to experiment with.
The Mental Model to Take Away
DIDComm is what you get when you ask: “What if instead of addressing messages to servers, we addressed them to identities?” Everything else follows from that question.
- Security travels with the message because it’s based on the identities involved, not the network path.
- Protocols compose because they all inherit the same security model.
- Key management simplifies because identity is separated from location.
- Audit trails become meaningful because every message is cryptographically tied to a specific identity.
For developers building AI agents, services that need to interoperate across organizations, or any system where “who sent this and can I trust it” matters — DIDComm is the foundation worth building on.
Ready to send your first DIDComm message? Request beta access → — we’re onboarding teams in waves.