Embed chat in dApps: the wallet-native chat widget
Embed chat in a dApp or website with Cherry's Chat Embed SDK: three wallet auth modes, seed-color theming, per-embed moderation, live in five minutes.
The Chat Embed SDK is the fastest way to embed chat in dApps and websites whose users already have wallets. Cherry (cherry.fun) is a wallet-to-wallet messenger and community app for crypto: people sign in with a wallet, DM any wallet address, and join token-gated, NFT-gated, and paid group chats. The embed puts one Cherry chat room inside your product as an iframe served from embed.cherry.fun, with wallet sign-in, theming, realtime delivery, unread badges, and moderation handled by Cherry. It is for developers who want a live room on the page without writing a chat backend. As of 10 September 2026 the package is @cherrydotfun/chat-embed-sdk 0.1.7 on npm, and the portal quickstart puts a public room live in about five minutes.
What the Chat Embed SDK does on Cherry
One package, one iframe, one room per embed. The facts as of 10 September 2026:
- Install from npm (ESM, CommonJS, and TypeScript types included) or from a pinned jsDelivr
<script>tag that exposeswindow.CherryEmbedSDK. - Mount inline on any element, or float the panel left or right with a built-in launcher bubble and unread badge (SDK 0.1.7 or newer).
- Three auth modes:
wallet-only,app-trusted+wallet, and zero-signatureapp-trusted. - Theming from two to four seed colors into about 60 CSS variables with contrast floors; transparency and blur included.
- Host identity: your app’s display names and avatars in place of
.solnames and shortened addresses. - Moderation rules per embed, enforced on the server.
- Every embed is also a community inside Cherry, so your users are reachable in the Cherry web and mobile apps too.
How it works
The SDK creates an iframe on your page, the two talk over postMessage, and everything else happens on Cherry’s side:
- Create the embed in the portal. Sign in at portal.cherry.fun with a wallet, create a project, and make an embed. Copy its ID (your
appId) and add your origin under Allowed origins. - Load the SDK and mount. Pass
appId, aroomId, and either acontainer(inline) or aposition(floating).mount()resolves once the iframe reports ready. - Sign the visitor in. In the wallet-backed modes the iframe fetches a challenge, the wallet signs it, and Cherry verifies the Ed25519 signature before issuing a session. After that the iframe re-establishes its session from a rotating refresh token on every page load.
- Messages flow. Realtime delivery, reactions, images and GIFs, and mentions run inside the iframe; the SDK emits
unreadState,authStateChange, andmessageevents to your page.
The three auth modes
| Mode | Backend | Wallet signature | What Cherry verifies | Use when |
|---|---|---|---|---|
wallet-only | none | yes | the Ed25519 signature on a challenge | public rooms, no backend, the five-minute quickstart |
app-trusted+wallet | yes | yes | your HS256 JWT (sub = wallet, app_id, about 5 minutes, unique jwtid) plus the signature | chat identity tied to your logged-in users; the most common mode for logged-in products |
app-trusted | yes | no | your JWT alone; sub comes from your session | internal apps and trusted partners; runs with server-side restrictions |
The token for the two backend modes is minted on your server with the app secret from the embed’s settings, and the secret never reaches the browser:
// POST /api/cherry-embed-token, on your server
import jwt from 'jsonwebtoken';
import { randomUUID } from 'node:crypto';
const token = jwt.sign(
{ sub: walletAddress, app_id: process.env.CHERRY_APP_ID },
process.env.CHERRY_APP_SECRET,
{ algorithm: 'HS256', expiresIn: '5m', jwtid: randomUUID() },
);
// On the page: mount with the token and a sign handler
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
const chat = new CherryEmbed({
appId: 'YOUR_EMBED_ID',
container: '#cherry-chat',
roomId: 'YOUR_ROOM_ID',
token,
walletAddress,
signChallengeHandler: async (message) => {
const { signature } = await provider.signMessage(message, 'utf8');
return signature; // Uint8Array, signed as-is
},
});
await chat.mount();
Zero-signature app-trusted mode trusts whatever sub your backend asserts, so derive the wallet from your own authenticated session, never from the request body. Because the app secret alone can mint a session for any wallet, the mode runs under server-side restrictions: a fail-closed room allowlist, about 20 messages per minute per user, moderation actions from the iframe off by default, and embed sessions of up to 15 minutes that refresh on their own. A 403 or 429 from those rules is policy, not a broken integration.
Display, launcher, and theme
Inline embeds size themselves to the container you give them. Floating embeds omit the container, pin to a viewport corner, and can start collapsed. Set chatBubble: true and the SDK renders the launcher and hangs the unread badge on it ('dot', 'count' capped at 99+, or 'off'); counters accrue while the chat is hidden or collapsed.
<div id="cherry-chat" style="height: 600px"></div>
<script src="https://cdn.jsdelivr.net/npm/@cherrydotfun/chat-embed-sdk@0.1.7/dist/index.global.js"></script>
<script>
new window.CherryEmbedSDK.CherryEmbed({
appId: 'YOUR_EMBED_ID',
roomId: 'YOUR_ROOM_ID',
position: 'floating-right',
collapsed: true,
chatBubble: true,
chatBubbleBadge: 'count',
theme: { mode: 'dark', primaryColor: '#9162FF', backgroundColor: '#0D0416', accentColor: '#FF57C1' },
}).mount();
</script>
The theme engine takes seeds, not a hand-built palette. primaryColor drives your own bubble and the send button, backgroundColor drives surfaces and text polarity, accentColor drives links and mentions, and an optional incomingBubbleColor recolors received messages. From those it derives roughly 60 variables and enforces contrast floors: body and incoming text at 4.5:1, the send icon at 3:1. An alpha backgroundColor plus backgroundBlur (0 to 40 px on the iframe element) frosts the panel. The live builder
offers presets and a “Show the integration snippet” button that emits the config, and the portal’s embed editor previews a theme live and emits it too. A separate layout object toggles the header, its title, the member count, and the composer (showInput: false makes a read-only feed).
Your users’ names
By default the widget labels people by wallet identity: a .sol domain when they own one, otherwise a shortened address. Flip Who your users appear as on the embed and the widget asks for your names instead, through a resolveUsers callback on the page (up to 50 wallets per call) or through a profile endpoint on your backend that the iframe calls directly (POST {url}/resolve, GET {url}/search, CORS allowed for https://embed.cherry.fun, no cookies, an optional bearer token via setIdentityToken()). searchUsers points @-autocomplete at your directory. Cherry stores none of the names, the wallet remains the author of every message, and every name is sanitized before it renders (48 characters, lookalike characters stripped, http(s) avatars only).
Moderation per embed
The Moderation tab in the embed editor is yours; the rules run on Cherry’s servers, so a visitor editing client code cannot skip them. All rules are off on a new embed.
| Rule | What it does | Limits (10 September 2026) |
|---|---|---|
| Minimum wallet age | Blocks posts from wallets younger than N days, measured from the first on-chain transaction; a wallet with no transactions has age zero | 0 to 365 days |
| Blocked words | Shadow-drops a message that contains one: the sender sees it, nobody else does, so a spammer gets no signal to retry | up to 200 words, 1 to 100 characters each, case-insensitive |
| Links | Allow all, allowlist, or block all; an empty allowlist blocks every link | up to 100 bare hosts |
| Images and GIFs | Two independent toggles | on by default |
A separate, read-only Embed policy card shows the limits Cherry sets for embed sessions: whether kick, ban, and mute work from the iframe, message length, and message rates.
What is different from a general chat SDK
The difference is who the identity belongs to. In a general chat SDK your backend invents a user ID and mints a token for it; in Cherry the wallet is the identity. Here’s how the identity models compare, as of 10 September 2026:
| Sendbird | Stream Chat | XMTP | Cherry Chat Embed | |
|---|---|---|---|---|
| Identity | a user_id your backend creates through Sendbird’s server API | a user ID you define, validated against your own system | any public DID such as an ENS name or a Bluesky handle, or a device-bound passkey | the wallet |
| Who proves it | your server issues an access or session token | your server signs a JWT with the API secret; a client cannot mint one | the user’s keys, client-side | the wallet signs a challenge in the iframe; optionally your backend also signs a JWT |
| Backend required | yes | yes | no, but you build the app | no in wallet-only; yes in the two app-trusted modes |
| What you ship | their UI kit or your own UI | their UI components or your own | your own UI on the protocol | a hosted iframe widget, themed |
| Where users also appear | inside your app | inside your app | any XMTP client | your page and the Cherry app, because the embed is a Cherry community |
Sendbird and Stream fit products where identity is an email or phone login. XMTP is an end-to-end encrypted protocol: it gives you the transport and leaves the UI, the rooms, and the moderation to you.
The Cherry room is the same kind of community people join at chat.cherry.fun, alongside token-gated and paid rooms, and its members can DM each other wallet to wallet in the Cherry app. Builders who would rather run inside Cherry use mini apps instead; both share the same wallet identity.
Who uses it and for what
Three common integration shapes:
A DEX or launchpad front end with a room next to the chart. Wallet-only mode, no backend: the visitor already connected to trade signs one challenge and is in the room. A seven-day minimum wallet age keeps fresh-wallet spam out, and a link allowlist of your own domains removes phishing links.
A game or marketplace with a room per match or order. Your backend calls POST https://api.cherry.fun/api/v1/apps/groups with a title and initialMembers, stores the returned roomId with the match, and mounts a single-mode embed with that id on the match page. Use app-trusted+wallet so chat identity equals the logged-in player, and delete the room when the match ends.
A wallet or portfolio app with its own accounts. Users are already logged in, so app-trusted removes the wallet layer from the chat: the backend mints the token from its session and host identity shows the app’s usernames and avatars. On mobile the same flow runs in a WebView host page with no wallet plumbing at all.
Limits and honest caveats
- One room per embed. Room switching (
list,external-controlled,setRoom()) is documented ahead of runtime support and behaves likesingletoday, so a multi-room product mounts one embed per page. - Group rooms are not end-to-end encrypted, by design, so moderation rules and bots can read what they moderate. Wallet-to-wallet DMs in the Cherry app are end-to-end encrypted; the embed renders a room.
- A thin “Powered by Cherry.fun” band sits under the composer, and the built-in launcher’s labels are English only (
Open chat/Close chat). Draw your own launcher if you need localized text. - Sign-in inside the widget is a Solana wallet signature (Ed25519); the embed authentication docs cover each mode’s wallet flow.
- The app secret is the whole trust model of the backend modes. Keep it server-side and rotate it from the embed settings in the portal.
- Server-to-server moderation through the Apps API is a separate path from the embed’s own rules.
- There is no native mobile SDK. Pointing a WebView straight at embed.cherry.fun fails because the bridge requires a parent frame, and a native sign request times out after 60 seconds.
- An embed loads only on allow-listed origins and only while it is enabled; anything else returns
401, as of 10 September 2026.
How to embed chat in dApps and websites
- Open https://portal.cherry.fun , press Connect Wallet, approve the SIWS signature, and press Create Project.
- In the project, open Chat embeds, press New embed, and name it. The name is the chat room’s visible title, so choose what visitors see.
- Copy the embed ID, add
http://localhost:3000(or your dev origin) and your production origin under Allowed origins, and confirm the embed is enabled. - Leave the auth mode on
wallet-onlyfor a public room, or pickapp-trusted+walletand copy the app secret to your server’s environment. - Install the SDK with
npm install @cherrydotfun/chat-embed-sdk, or paste the jsDelivr script tag pinned to@0.1.7. - Add
<div id="cherry-chat" style="height: 600px"></div>and mount:
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
const chat = new CherryEmbed({
appId: 'YOUR_EMBED_ID',
container: '#cherry-chat',
roomId: 'YOUR_ROOM_ID',
mode: 'single',
theme: { mode: 'dark', primaryColor: '#FF5BA8' },
});
await chat.mount();
- Theme it in the live builder or the portal editor and paste the emitted config over the
themeblock. - Open the embed’s Moderation tab, set a minimum wallet age and a link policy, and press Save.
- Open the same room in the Cherry web app and invite your first members.
The portal quickstart walks the same flow.
FAQ
Can I add chat to a website without a backend?
Yes. In wallet-only mode the embed needs an appId and a public room id. Visitors connect their own wallet and sign a challenge inside the iframe; Cherry verifies the signature and runs the session. There is no token endpoint to build.
How does wallet auth work in the chat widget?
The iframe fetches a challenge from Cherry, the visitor’s wallet signs it, and Cherry checks the Ed25519 signature before issuing a session. In app-trusted+wallet mode your backend also mints a five-minute HS256 JWT for the wallet, and both proofs are verified.
Can the widget show my app’s usernames and avatars instead of wallet addresses?
Yes. Turn on the per-embed switch in the portal, then answer with resolveUsers from your page or point the embed at a profile endpoint on your backend. Names are a visual overlay: the wallet stays the author of every message and Cherry stores none of the names.
Is the Cherry chat widget white-label?
Mostly. Two to four seed colors drive about 60 CSS variables, and every slot can be overridden, but a thin “Powered by Cherry.fun” band sits under the composer and the built-in launcher’s labels are English only.
Does the Chat Embed SDK work in React Native or Flutter?
Through a WebView. The SDK is browser-only, so you load a small host page in react-native-webview or webview_flutter and bridge the wallet signature to the native layer: Mobile Wallet Adapter on Android, a deeplink wallet on iOS. The SDK repo ships runnable examples for both.
What stops spam in an embedded chat?
Per-embed rules enforced on Cherry’s servers: a minimum wallet age of up to 365 days, up to 200 blocked words that shadow-drop a message, a link policy (allow all, allowlist up to 100 hosts, or block all), and image and GIF toggles. Cherry also sets message-rate and message-length limits for every embed.
Do my users need a Cherry account?
No. Their wallet is the account. Signing the challenge inside the widget signs them in, and because every embed is also a community inside Cherry, they can open the same room in the Cherry web or mobile app later.
Is the Chat Embed SDK free?
Yes. The Chat Embed SDK, the Cherry API, bots and Cherry Portal are all free. Cherry’s only charges are a share of paid-community payments, the paid-DM fee and a referral share on in-chat swaps, and none of them apply to an embed.
Sources
- Chat Embed SDK quickstart
- Chat Embed SDK authentication modes
- Chat Embed SDK display modes
- Chat Embed SDK API reference
- Chat Embed SDK theming
- Per-embed moderation rules
- Your users’ names and avatars in the embed
- Chat Embed SDK in React Native and Flutter
- Guide: a room per game, match or order
- @cherrydotfun/chat-embed-sdk on npm
- Chat Embed SDK source and examples on GitHub
- Cherry embed live builder
- Sendbird docs: create a user
- Stream Chat: tokens and authentication
- XMTP documentation
- About Cherry
Build on Cherry
Add chat to your dApp, ship a mini app, or run bots and rooms through the API. Self-serve at the developer portal.