Your bot is an encrypted member
On many platforms, a bot is a privileged client of a server that can read message content in the clear. On Cloak, your bot is a first-class encrypted member, the same as any person in the server. That means:- Your bot holds its own cryptographic identity, published to Cloak once and reused forever.
- Your bot receives each server’s conversation key from a human member who already holds it.
- All encryption and decryption happen inside your process.
The primitives, by name
You do not implement any of this, but knowing the names makes the failure modes readable.The class that does the wrapping is still exported as
SignalSession, for compatibility with code written before the SDK moved off libsignal. The primitive underneath it is HPKE, not the Signal Protocol. You will not normally touch it.Because your bot does the crypto itself, it needs to hold real keys. Persisting those keys safely is the single most important operational detail. See Keys and the keystore.
The lifecycle of a message
When someone sends a message your bot can see:1
A human hands your bot the server's key
The first time your bot joins a server, a member who already holds that server’s conversation key wraps it to the bot’s published identity and uploads it. The SDK unwraps it and pins that member’s identity key on first use, so a later wrap from the same peer that does not match the pin is rejected. Until this happens, the bot can connect but cannot decrypt or send in that server.
2
A member sends an encrypted message
Their app encrypts the content with the server’s key and sends the ciphertext to Cloak.
3
Cloak relays it to your bot
The service delivers the ciphertext to your bot’s session as part of the firehose.
4
Your bot decrypts it locally
The SDK decrypts the content with the key your bot holds and hands you a
Message with readable content.5
Your bot answers
When you call
msg.channel.send() or msg.reply(), the SDK encrypts your text in the process and sends the ciphertext back through Cloak.Conversation keys are versioned
A server’s conversation key is not one key forever. It carries an epoch, and the SDK stores one key per epoch. When a message names an epoch your bot does not hold, the SDK pulls that epoch’s key and decrypts once it lands. You do not manage this. It is worth knowing because it explains why a message can arrive slightly before it becomes readable, and why the keystore holds more than one key per server.What you are responsible for
Because your bot is a real member holding real keys, a few responsibilities sit with you rather than with the platform.Guard the token
The bot token grants full encrypted membership. Keep it in an environment variable or secret manager, never in code.
Keep the keystore
The keystore holds a write-once identity. Lose it after the first login and the bot can never decrypt again without a server-side identity reset. See Keys and the keystore.
Host the process
Your bot is a program you run and keep running. Cloak does not host it for you.
Handle permissions
Declare what your bot needs, and check what each server granted before acting. See Permissions.
keystorePath) or any adapter you supply (keystore), so a host with no writable volume is still serviceable. What is not optional is that something durable holds it.
A bot is a member, with a few carve-outs
Treating a bot as an ordinary member is the design, and most of the API follows from it. Three places where it does not:react(),unreact(),edit(),delete(),pin(), andunpin()all reject on a direct message.reply()andchannel.send()work normally there.- A bot session is structurally Free tier, so its voice credential is microphone-only. Camera and screenshare are not granted.
- Hearing other members in voice is a separate permission (
voice_listen) from joining one. Without it the bot joins and publishes normally and receives nothing, silently.
The API is familiar on purpose
The SDK is shaped like discord.js. You get aClient, you attach listeners for events like messageCreate and guildCreate, and messages carry a reply() method. If you have built a Discord bot, porting it is mostly mechanical.
Two differences to hold onto. The resemblance is in the shape of the code, not the wire protocol: a Cloak bot talks to Cloak, and cannot connect to Discord. And Message.serverId is string | null, because the same messageCreate event delivers direct messages, which have no server.
Guild, following discord.js naming. The product calls it a server, and so do these docs.
Next
What is encrypted, and what is not
The honest map of every lane your bot can send on.
Message delivery
How your bot hears from every channel at once.
Permissions
Declaring, granting, and checking capabilities.
Keys and the keystore
The write-once identity, and how not to lose it.