The spec
atcomm binds an atproto identity to end-to-end-encrypted DIDComm v2 messaging via two lexicons and one message type. The binding is the open interop contract: any client, in any language, can implement it.
Identifiers
at.didcomm.keyPackage, identity binding record (in the user's PDS)at.didcomm.dm, decrypted message body schemahttps://didcomm.at/dm/1.0/message, the DIDComm v2 message type
keyPackage record
Published by the user at rkey: self in their own PDS. Senders resolve a
handle to this record to learn the messaging key and mediator(s).
{
"$type": "at.didcomm.keyPackage",
"did": "did:peer:2...", // DIDComm messaging key (X25519 keyAgreement)
"mediators": ["did:web:wa.mediators.didcomm.at"], // failover order
"label": "phone",
"createdAt": "2026-06-14T00:00:00Z"
}
dm message body
The schema describes the decrypted body carried inside the encrypted
envelope. It is never stored as a record; infrastructure sees ciphertext only. The
shape mirrors chat.bsky.convo message input so existing atproto clients
can render it with the code they already have.
{
"$type": "at.didcomm.dm",
"senderDid": "did:plc:...", // sender's atproto account DID (attribution claim)
"text": "hello",
"facets": [ /* app.bsky.richtext.facet — links, mentions */ ],
"embed": { /* app.bsky.embed.record — quote a public post */ }
}
Note on senderDid: it is a claim.
It is carried but not yet cryptographically verified in the current
implementation. Verification (resolve the DID, fetch its keyPackage, confirm it
advertises the envelope's sender key) is planned. Until then, treat the attribution
as unverified.
Message envelope
DIDComm v2, ECDH-1PU authenticated encryption (authcrypt, A256CBC-HS512). The message is encrypted to the recipient, then wrapped in a routing forward addressed to the recipient's mediator. Cross-mediator delivery double-wraps so the message transits the sender's own mediator first. Only the participants can read the body.
Lexicons
Canonical machine-readable schemas: