How Mediators Work
Mediators are encryption-blind relay servers that route messages and store ciphertext through two communication channels.
Formal spec: DCTRL-0004 — Mediator Protocol defines the HTTP API, command wire format, WebSocket protocol, and all error codes.
Mediators are the infrastructure layer of Decentrl. They're lightweight servers that do exactly two things: route encrypted messages and store encrypted data. They never see plaintext.
What a Mediator Sees
When Alice sends Bob a message through a mediator, the mediator sees:
- Sender DID —
did:decentrl:z6MkpT...(Alice's public identity) - Recipient DID —
did:decentrl:z6MkqR...(Bob's public identity) - Timestamp — when the command was sent
- Encrypted payload — opaque ciphertext
- Encrypted tags — opaque blobs for indexing (it can tell when two events share a tag, not what the tag is)
- Dedupe keys — one opaque blob per stored event, so it can tell when a save repeats an event you already have (not which event)
- Sizes — how large each command and event is
- Contract terms — the DIDs, ephemeral public keys, timestamps, and signatures of every contract it verifies and enforces
- Contract and epoch per event — the
contract_idandepocheach private payload carries in the clear
Together this tells the mediator who talks to whom and when — the social graph. The mediator does not see: event content, conversation topics, read receipts, root secrets, epoch keys, storage keys, or tag plaintext.
Two Communication Channels
The protocol defines two channels that serve distinct purposes:
DIRECT_AUTHENTICATED
Used for identity-to-mediator administrative operations. The sender signs the command; the mediator verifies the signature against the sender's DID document and checks for an active registration contract.
| Command | Purpose |
|---|---|
REQUEST_COMMUNICATION_CONTRACT | Send a contract request (for registration or forwarding) |
COMMUNICATION_CONTRACT_RESPONSE | Deliver signed contract response |
QUERY_PENDING_COMMUNICATION_CONTRACT_REQUESTS | List inbound pending contract requests |
ACKNOWLEDGE_PENDING_COMMUNICATION_CONTRACT_REQUESTS | Delete processed requests |
SAVE_COMMUNICATION_CONTRACT | Store a completed contract |
QUERY_COMMUNICATION_CONTRACTS | List your contracts (at most one per counterpart) |
REVOKE_COMMUNICATION_CONTRACT | Withdraw consent for one counterpart, silently |
QUERY_PENDING_EVENTS | List unacknowledged events from others |
ACKNOWLEDGE_PENDING_EVENTS | Delete processed events |
SAVE_EVENTS | Store encrypted events, one per dedupe_key (a repeat is DUPLICATE) |
QUERY_EVENTS | Query stored events with tag filters |
UPDATE_EVENT_TAGS | Replace tags on stored events |
TWO_WAY_PRIVATE
Used for encrypted event delivery between identities. When Alice sends Bob an event:
- The mediator verifies Alice's signature on the command
- Checks that Bob is registered with this mediator
- Checks that Bob holds a communication contract with Alice (contracts don't expire; one Bob revoked is gone, and Alice gets the same
COMMUNICATION_CONTRACT_NOT_FOUNDa stranger would) - Stores the payload as a pending event for Bob
The payload is a small JSON wrapper: the contract ID and epoch in the clear, and the encrypted event. The mediator stores it without being able to read the event, or its event ID, which is signed inside the encryption. Once Bob acknowledges the event, the mediator deletes it, so delivered ciphertext doesn't linger on the server.
Retention
A mediator keeps only what it still needs. A cleanup job runs every 10 minutes and deletes expired nonces, and pending events and contract requests older than 30 days (never acknowledged). Contracts are never cleaned up: a contract goes away only when a newer one for the same pair replaces it, or its owner revokes it.
Authorization Model
Every command to a mediator must be signed. The mediator resolves the sender's DID, extracts their public signing key, and verifies the signature. Commands are also rate-limited per authenticated sender DID (by default 600 commands per minute). Additionally:
- DIRECT_AUTHENTICATED commands require an active registration contract (client-to-mediator contract)
- TWO_WAY_PRIVATE commands require both an active registration contract and a valid bilateral communication contract between sender and recipient
No unsigned command is ever processed. No command without proper contracts is ever accepted.