| name | alice-and-bot |
| description | Build chat bots and integrate messaging into apps using the Alice&Bot platform. Use when the user wants to create a chat bot, send/receive encrypted messages, set up webhooks for incoming messages, manage identities, or build on the Alice&Bot messenger. Triggers on "alice and bot", "aliceandbot", "chat bot on alice", "send a message via alice", or any task involving the Alice&Bot messaging API. |
Alice&Bot — Encrypted Messaging for Bots and Humans
Alice&Bot is a developer-first, end-to-end encrypted chat platform. Bots are
first-class citizens — create identities, send/receive messages, show typing
indicators and progress bars, all via a TypeScript API.
Package: @alice-and-bot/core on JSR
Install
deno add jsr:@alice-and-bot/core
npx jsr add @alice-and-bot/core
Quick start — echo bot
import {
createIdentity,
handleWebhookUpdate,
sendMessageWithKey,
setAlias,
setWebhook,
} from "@alice-and-bot/core";
const credentials = await createIdentity("Echo Bot");
await setAlias({ alias: "echo-bot", credentials });
await setWebhook({ url: "https://my-bot.example.com/webhook", credentials });
const handleIncoming = async (webhookBody) => {
const { conversationId, message, conversationKey } =
await handleWebhookUpdate(webhookBody, credentials);
if (message.type !== "text") return;
await sendMessageWithKey({
conversationKey,
conversation: conversationId,
credentials,
message: { type: "text", text: `You said: ${message.text}` },
});
};
Users can chat with the bot at
https://aliceandbot.com/chat?chatWith=<publicSignKey> or by searching for its
alias in the app.
Core concepts
Identity — A public/private key pair. No passwords, no tokens. Identity =
possession of private keys. Create as many as you want.
Credentials — { publicSignKey, privateSignKey, privateEncryptKey }
(RSA-2048 keys, base64-encoded DER). Persist these — they ARE the identity.
Encryption — Hybrid E2E: AES-256-GCM symmetric key per conversation,
encrypted per-participant with RSA-OAEP. The server never sees plaintext.
Webhook — The server POSTs a WebhookUpdate to your URL when a message
arrives. You decrypt it with handleWebhookUpdate.
API reference
Identity
createIdentity(name, alias?)
Create a new identity. Returns Credentials.
const credentials = await createIdentity("My Bot", "my-bot");
setAlias({ alias, credentials })
Set a human-readable alias (unique handle).
await setAlias({ alias: "my-bot", credentials });
setName({ name, credentials })
Update the display name.
await setName({ name: "My Awesome Bot", credentials });
setWebhook({ url, credentials })
Register a webhook URL for incoming messages.
await setWebhook({ url: "https://my-bot.example.com/webhook", credentials });
Messaging
sendMessage({ credentials, conversation, message })
Send a message. Automatically fetches the conversation key.
await sendMessage({
credentials,
conversation: conversationId,
message: { type: "text", text: "Hello!" },
});
sendMessageWithKey({ conversationKey, conversation, credentials, message })
Send a message with a cached conversation key (faster — skips a round trip).
await sendMessageWithKey({
conversationKey,
conversation: conversationId,
credentials,
message: { type: "text", text: "Quick reply" },
});
handleWebhookUpdate(webhookBody, credentials)
Decrypt an incoming webhook payload.
const { conversationId, message, conversationKey, messageId } =
await handleWebhookUpdate(webhookBody, credentials);
Message types
{ type: "text", text: "Hello", attachments?: Attachment[] }
{ type: "edit", editOf: messageId, text: "Corrected text", attachments?: Attachment[] }
{ type: "spinner", text: "Thinking...", active: true, elementId: "unique-id" }
{ type: "progress", text: "Uploading...", percentage: 50, elementId: "unique-id" }
Maximum text length: 10,000 characters.
Conversations
createConversation(publicSignKeys, title, credentials)
Create a conversation between participants.
import { createConversation } from "@alice-and-bot/core";
const result = await createConversation(
[botCredentials.publicSignKey, userPublicSignKey],
"Support Chat",
botCredentials,
);
getConversations(publicSignKeys)
Find conversations involving these participants.
import { getConversations } from "@alice-and-bot/core";
const conversations = await getConversations([credentials.publicSignKey]);
getConversationInfo(conversationId)
Get conversation metadata and participant profiles.
import { getConversationInfo } from "@alice-and-bot/core";
const info = await getConversationInfo(conversationId);
getDecryptedMessages({ conversationId, credentials, limit?, before? })
Fetch and auto-decrypt messages from a conversation. Convenience wrapper that
fetches the conversation key and decrypts all messages automatically.
import { getDecryptedMessages } from "@alice-and-bot/core";
const result = await getDecryptedMessages({ conversationId, credentials });
Supports pagination via before (timestamp cursor) and limit (default 50, max
200).
getMessages({ conversationId, credentials, limit?, before? })
Lower-level version that returns raw encrypted payloads.
import { getMessages } from "@alice-and-bot/core";
const result = await getMessages({ conversationId, credentials });
Profiles
getProfile(publicSignKey)
Look up a user's profile.
import { getProfile } from "@alice-and-bot/core";
const profile = await getProfile(publicSignKey);
aliasToPublicSignKey(alias)
Resolve an alias to a public sign key.
import { aliasToPublicSignKey } from "@alice-and-bot/core";
const result = await aliasToPublicSignKey("some-user");
publicSignKeyToAlias(publicSignKey)
Reverse lookup — key to alias.
import { publicSignKeyToAlias } from "@alice-and-bot/core";
const result = await publicSignKeyToAlias(key);
Typing indicators
sendTyping({ conversation, isTyping, publicSignKey })
Show/hide the typing indicator.
import { sendTyping } from "@alice-and-bot/core";
await sendTyping({
conversation: conversationId,
isTyping: true,
publicSignKey: credentials.publicSignKey,
});
Files and attachments
uploadAttachment({ credentials, conversationId, conversationKey, file })
Encrypt and upload a file. Returns an Attachment to include in a message.
import { uploadAttachment } from "@alice-and-bot/core";
const attachment = await uploadAttachment({
credentials,
conversationId,
conversationKey,
file: myFile,
});
if ("error" in attachment) throw new Error(attachment.error);
await sendMessageWithKey({
conversationKey,
conversation: conversationId,
credentials,
message: { type: "text", text: "Here's the file", attachments: [attachment] },
});
File size limits: images 10MB, audio 25MB, video 100MB, other files 25MB.
downloadAttachment({ url, conversationKey })
Decrypt and download an attachment.
import { downloadAttachment } from "@alice-and-bot/core";
const arrayBuffer = await downloadAttachment({
url: attachment.url,
conversationKey,
});
Live UI updates (spinners & progress bars)
Bots can show spinners and progress bars in the chat without sending actual
messages, via the UI update endpoint.
import { buildUiUpdateUrl } from "@alice-and-bot/core";
const elementId = crypto.randomUUID();
const url = buildUiUpdateUrl(elementId);
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "spinner",
text: "Processing...",
active: true,
}),
});
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "progress",
text: "Uploading...",
percentage: 75,
}),
});
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: "spinner", text: "", active: false }),
});
Utility
chatWithMeLink(publicSignKey)
Generate a link that opens a chat with this identity.
import { chatWithMeLink } from "@alice-and-bot/core";
const link = chatWithMeLink(credentials.publicSignKey);
Common patterns
Persist credentials
Credentials ARE the identity. If you lose them, the identity is gone. Store them
securely (env vars, secret manager, encrypted file).
import { createIdentity } from "@alice-and-bot/core";
const credentials = await createIdentity("My Bot");
await Deno.writeTextFile("credentials.json", JSON.stringify(credentials));
const credentials = JSON.parse(await Deno.readTextFile("credentials.json"));
Bot with webhook (Deno server)
import {
type Credentials,
handleWebhookUpdate,
sendMessageWithKey,
setWebhook,
type WebhookUpdate,
} from "@alice-and-bot/core";
const credentials: Credentials = JSON.parse(
Deno.env.get("BOT_CREDENTIALS")!,
);
await setWebhook({ url: "https://my-bot.deno.dev/webhook", credentials });
Deno.serve(async (req) => {
if (new URL(req.url).pathname !== "/webhook") {
return new Response("Not found", { status: 404 });
}
const body: WebhookUpdate = await req.json();
const { conversationId, message, conversationKey } =
await handleWebhookUpdate(body, credentials);
if (message.type === "text") {
await sendMessageWithKey({
conversationKey,
conversation: conversationId,
credentials,
message: { type: "text", text: `Echo: ${message.text}` },
});
}
return new Response("ok");
});
Show thinking indicator while processing
import {
buildUiUpdateUrl,
handleWebhookUpdate,
sendMessageWithKey,
} from "@alice-and-bot/core";
const handleMessage = async (webhookBody, credentials) => {
const { conversationId, message, conversationKey } =
await handleWebhookUpdate(webhookBody, credentials);
if (message.type !== "text") return;
const elementId = crypto.randomUUID();
const uiUrl = buildUiUpdateUrl(elementId);
await fetch(uiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "spinner",
text: "Thinking...",
active: true,
}),
});
const reply = await generateReply(message.text);
await fetch(uiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: "spinner", text: "", active: false }),
});
await sendMessageWithKey({
conversationKey,
conversation: conversationId,
credentials,
message: { type: "text", text: reply },
});
};
Links