Why standard webhooks quietly expose your agents' internal network — and a simpler way to route messages between them using a gateway that can forward a message without ever being able to read where it's actually going.
Status: work in progress. This is an early write-up of something I'm actively building, not a finished product announcement. I'm sharing it to get feedback before taking it further. See the last section for exactly what kind of feedback would help most.
The problem, in one picture
Say you run a private practice out of a home office and you want clients to send you confidential documents. Would you:
- A) Publish your home address, gate code, and floor plan on a public billboard so any delivery driver can walk straight to your office door, or
- B) Rent a P.O. box, where the postal depot forwards your mail to your internal drop-box without ever knowing who you are or what's inside the envelope?
Everyone with anything confidential to protect picks B.
Yet almost every multi-agent framework today defaults to Option A. To let one AI agent message another across a company boundary or a different cloud, the common pattern is a public HTTP webhook — which means exposing a real IP address, a real port, and often the name of the internal service sitting behind it. Beyond the privacy leak, most agents don't even have a stable public address to expose: they sit behind corporate firewalls, cloud VPCs, or NATs, with no inbound port open at all.
The other common fix — routing everything through a central message broker — solves the connectivity problem but creates a new one: that broker sees every sender, every recipient, and the shape of who talks to whom, even if the message content itself is encrypted.
We wanted neither. What we built is a gateway that can deliver a message to exactly the right internal destination without ever learning what that destination is.
The idea: a double-sealed envelope
Think of each message as two envelopes, one inside the other.
- The outer envelope carries a routing instruction that only the gateway can open. It reveals just enough for the gateway to forward the message to the right internal queue — nothing about the recipient's real server, IP, or internal architecture.
- The inner envelope is the actual message, end-to-end encrypted so that only the recipient agent can ever read it. The gateway forwards these bytes without being able to open them at all.
The outer envelope is what we call a JIT (just-in-time) routing token: a small, single-use, encrypted packet built fresh for every message. Under the hood, building one takes four steps:
- The sender generates a brand-new, one-time keypair — used for this message only, then thrown away.
- It combines that key with the gateway's public key to derive a shared secret (a standard Diffie–Hellman exchange).
- That shared secret is expanded into an encryption key.
- The sender uses that key to encrypt the recipient's real (but still opaque) destination ID — the only thing inside the token that says where this message is actually going.
Because a fresh keypair is generated for every single message, even if the gateway's own key were ever compromised, nobody could go back and decrypt yesterday's routing tokens. Old messages stay unreadable regardless of what happens to future keys.
What actually happens when a message is sent
Two things are worth noticing:
- Discovery never hands out a real address. Looking up a recipient returns an opaque routing ID — not an IP, not a hostname, not a hint about which cloud or server the recipient actually runs on.
- The gateway is stateless. It holds no database of users, no message history, and no persistent record of who talked to whom. It decrypts a routing instruction, forwards some bytes, and forgets everything the instant that's done.
Trying it yourself
The easiest way to see this in action is through our live demo portal at https://lianxi.io/experiment and the public gateway (https://gateway.lianxi.io).
1. Create a discoverable identity
- Go to https://lianxi.io/experiment and create a test account.
- In Account & Settings → People → Manage Profiles, click Make Discoverable.
- Copy your newly created DID (e.g.
did:twin:z83ca0ce5dfbeb303c0d699b1844f3718df29ed00439e453e7b72d9152a843fa5).
2. Inspect the DID Document
You can now query the Public Gateway to see what the public directory actually reveals about your identity:
curl -s "https://gateway.lianxi.io/did/<your-did>" | jq .
Live Response:
{
"id": "did:twin:z83ca0ce5dfbeb303c0d699b1844f3718df29ed00439e453e7b72d9152a843fa5",
"verificationMethod": [
{
"id": "did:twin:z83ca0ce5dfbeb303c0d699b1844f3718df29ed00439e453e7b72d9152a843fa5#key-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:twin:z83ca0ce5dfbeb303c0d699b1844f3718df29ed00439e453e7b72d9152a843fa5",
"publicKeyBase64": "g8oM5d++swPA1pmxhE83GN8p7QBDnkU+e3LZFSqEP6U="
}
],
"service": [
{
"id": "did:twin:z83ca0ce5dfbeb303c0d699b1844f3718df29ed00439e453e7b72d9152a843fa5#messaging",
"type": "MessagingService",
"serviceEndpoint": {
"uri": "https://gateway.lianxi.io/ingress",
"routing_did": "did:twin:gateway_local",
"target_id": "spPWV7dxhdWkjfKTW1iAN83m1JY4wZS9CEC4mp7s5Vf7XZtIJDkmHZTcbCcw3wgB0gf1Le6Np+gZRTfApSlc7zPTv+WVO6y8nLEMIIUki0Q4b8jbdxluq5c+hSFBsrRhvZemSOH9LJd7E7cugs37WBph990qPjFgBo0uLueSc9hZSxESCux6ii4+iejhS8/+2Mf8tizTREXUlpV8Jy5CA7Z0Y5FX9FijxnAjlFh+5qJ9"
}
}
]
}
Understanding the DID Document:
-
id: The unique cryptographic fingerprint representing the agent twin. -
verificationMethod: The public key used to verify signatures and establish end-to-end encrypted sessions with the agent. -
service.serviceEndpoint: The gateway entrypoint (https://gateway.lianxi.io/ingress) and thetarget_id. Notice that the target ID is completely opaque: no IP address, no port, no internal server name, and no queue name is revealed.
3. Delivering a message to a specific DID
When you send a message to that DID (e.g. from another agent or directly from the demo portal):
-
The sender's client library encapsulates the JIT token:
- Generates a one-time ephemeral X25519 keypair.
- Computes
SharedSecret = ECDH(EphemeralPrivate, GatewayPublic). - Encrypts the recipient's
target_idwithXChaCha20-Poly1305. - Packages
[Version (0x01) | EphemeralPubKey (32B) | Nonce (24B) | Ciphertext]into the base64 URL-safeX-Routing-Token.
The sender dispatches the message to the Public Gateway:
curl -i -X POST https://gateway.lianxi.io/ingress \
-H "Content-Type: application/json" \
-H "X-Routing-Token: <ephemeral-jit-token-wrapping-target_id>" \
-d '{
"version": "didcomm/v2",
"id": "msg_88a91bc0",
"type": "https://didcomm.org/messaging/2.0/message",
"body": {
"ciphertext": "U2FsdGVkX19+vX4Wk910... (End-to-End Encrypted Payload)"
}
}'
Response:
HTTP/1.1 202 Accepted
A 202 Accepted means the gateway decrypted the ephemeral X-Routing-Token in under 2ms, forwarded the still-sealed message to the recipient's internal queue (v1.<node>.didcomm.<target>), and immediately discarded the routing context.
If you use the interactive UI at https://lianxi.io/experiment, the demo portal handles this token derivation for you automatically and you can watch incoming messages appear in real time on your activity feed with zero network topology exposure.
Security guarantees
| Guarantee | What it means |
|---|---|
| Topology stays hidden | Internal server addresses and queue names never appear outside an encrypted token. |
| Gateway never reads content | The message body is end-to-end encrypted; the gateway only ever touches the routing instruction. |
| Forward secrecy | Every message uses a fresh, one-time keypair, so a future key compromise can't unlock past messages. |
| Nothing persists at the edge | The gateway keeps no database — there's no historical record to leak or subpoena. |
The takeaway
- Public webhooks leak more than you think — not just a message, but your entire internal network shape.
- Separate routing from content. A gateway only needs to know where, never what or who.
- Make every routing instruction single-use and self-expiring, so even a compromised key can't unlock the past.
The gateway still gets your message where it needs to go. It just never learns enough to draw a map of how you're built.
I'd like your feedback
This is still a work in progress, and I'd like to hear from anyone who reads this — agent builders, security folks, or just the curious. A few things I'm genuinely unsure about:
- The core idea: Does hiding network topology from the routing layer solve a real problem for you, or is it solving something you don't actually worry about?
- The explanation: Did the double-envelope analogy hold up once the JIT token mechanics came in, or did it get muddled?
- The gaps: What would you need to see before trusting this for production traffic — rate limiting, abuse prevention, key rotation, something else?
- The demo: If you tried the flow yourself, did discovery and delivery work the way it's described here?
Comments here are the easiest way to reach me. If you spend five minutes poking holes in this, I'll read every one of them.



Top comments (0)