Production-Ready | Render-Optimized | AI-Friendly
Complete bot template ready for Render deployment. Works with Cursor AI for easy customization.
Out of the box:
- Responds to "GM" with "GM back"
- Responds to @bot mentions
- Shares an example image when you mention it with words like "image" or "picture"
- Welcomes new users
- Tracks all events with logging
- Health check endpoint for monitoring
Ready to add with AI:
- User verification systems
- Message moderation
- Statistics & leaderboards
- Custom commands
- Games & rewards
- Database storage
Portal: https://app.alpha.towns.com/developer
- Click "Create New Bot"
- Set permissions: Read, Write, React, JoinSpace
- Copy credentials:
- APP_PRIVATE_DATA (copy ENTIRE value!)
- JWT_SECRET
Dashboard: https://dashboard.render.com
- New → Web Service
- Connect GitHub repo
- Configure:
- Build Command:
bun install && bun run build - Start Command:
bun run start
- Build Command:
- Add Environment Variables:
APP_PRIVATE_DATA = [paste full key from Towns] JWT_SECRET = [paste secret from Towns] PORT = 5123 - Deploy - Wait for "Your service is live 🎉"
- Copy your Render URL:
https://your-bot.onrender.com
IMPORTANT: Webhook setup only works AFTER bot is deployed and running on Render!
- Go to Towns Portal → Your Bot → Settings
- Webhook URL:
https://your-bot.onrender.com/webhook - Enable Forwarding: All Messages, Mentions, Replies
- Save - Should see "✅ Webhook verified"
- Open Towns app → Your Space
- Settings → Bots → Install Bot
- Select your bot → Install
- Test: Say "GM" in chat
CRITICAL: Your .env file must have the EXACT same values as Render environment variables!
# Create .env (copy from .env.sample)
cp .env.sample .envEdit .env with same values as Render:
APP_PRIVATE_DATA=exact_same_value_as_render
JWT_SECRET=exact_same_value_as_render
PORT=5123Then run:
bun install
bun run devTest: http://localhost:5123/health
src/
├── index.ts # Main bot logic
└── commands.ts # Bot commands (required for webhooks!)
package.json # Dependencies (auto-updates available)
.cursorrules # AI coding rules
check-versions.sh # SDK version checker
COMPLETE_KNOWLEDGE_BASE.md # All patterns & solutions
This template is optimized for Cursor + Claude/ChatGPT.
"Add response when users say 'wagmi'"
"Make bot react with 🚀 when someone says 'moon'"
"Add welcome message for new users"
"Add user verification system using ✅ reactions"
"Create message moderation for bad words"
"Add statistics tracking with database"
"Build leaderboard system with points"
"Send an image when users say 'show pic'"
AI references:
COMPLETE_KNOWLEDGE_BASE.md- All handlers, patterns, deployment.cursorrules- Latest SDK guidelines- Template code in
src/index.ts
npm run check-versions # Check for SDK updates
npm run update-sdk # Update to latest SDK
npm run setup # Install + check versions
npm run dev # Development mode
npm run build # Build for Render
npm run deploy # Verify build readyVersion Checker: Automatically checks npm for latest SDK versions.
{
"@towns-protocol/bot": "latest",
"@towns-protocol/sdk": "latest",
"@hono/node-server": "^1.14.0",
"hono": "^4.7.11"
}Keep your SDK updated:
bun run check-versions # Check for updates
bun run update-sdk # Update to latestawait handler.sendMessage(channelId, "Here's your visual", {
attachments: [
{
type: 'image',
url: 'https://example.com/cat.png',
alt: 'Cute cat',
},
],
})The bot fetches the URL, validates that it is an image, measures dimensions, encrypts the payload, and still delivers the text even if the attachment fails. Add multiple attachments by pushing additional objects into the array.
// src/commands.ts - MUST exist for webhook verification
export default [
{ name: 'help', description: 'Show help' },
]
// src/index.ts - MUST import and register
import commands from './commands.js'
const bot = await makeTownsBot(
process.env.APP_PRIVATE_DATA,
process.env.JWT_SECRET,
{ commands } as any // Required!
)bot.onMessage(async (handler, event) => {
if (event.userId === bot.botId) return // Prevents infinite loops!
// ... your code
})Success:
✅ 🔑 Key length: 596 characters
✅ 🤖 Bot ID: 0x...
✅ Your service is live 🎉
Failed - Missing Env:
❌ ERROR: APP_PRIVATE_DATA not set!
→ Add environment variable in Render dashboard
Failed - Incomplete Key:
RangeError: premature EOF
→ Re-copy FULL APP_PRIVATE_DATA from Towns Portal (no spaces!)
Failed - Webhook:
RegisterWebhook: (66:CANNOT_CALL_WEBHOOK)
→ Ensure { commands } as any in bot initialization
- Environment variables: Must be set in Render dashboard (not just .env)
- APP_PRIVATE_DATA: Copy full key, no spaces at start/end
- Webhook URL: Bot must be running on Render FIRST before setting webhook
- Commands: src/commands.ts must exist and be imported
- COMPLETE_KNOWLEDGE_BASE.md - Deployment, handlers, patterns, errors
- .cursorrules - AI guidelines for latest SDK
Render-Ready | Auto-Deploy on Git Push | Always Current