Skip to content

Webapp Examples

Practical patterns for webapp commands and URL generation. Copy, adapt, ship.


JSON API (webapp)

Webapps have no user context — design APIs around params, db.bot, or tokens you validate yourself.

let id = params.id
if (!id) {
  return res.status(400).json({ error: "id required" })
}

let record = await db.bot.get("item:" + id)
res
  .set("Access-Control-Allow-Origin", "*")
  .json({ ok: true, record: record || null })

Browsers calling your API

Set CORS headers when a front-end on another origin fetches your webapp. See Headers & Status.


HTML dashboard via res.render()

Separate logic from layout — handler command fetches data, template command displays it.

Handler command (showDashboard):

let stats = {
  visitors: await db.bot.get("visitors") || 0,
  updated: Date.now()
}
res.render("dashboard.html", { data: { stats } })

Template command (dashboard.html):

<!DOCTYPE html>
<html>
<body>
  <h1>Stats</h1>
  <p>Visitors: <%= stats.visitors %></p>
</body>
</html>

let url = Webapp.getUrl("dashboard", {
  params: { ref: "telegram", lang: "en" }
})

await Api.sendMessage({
  chat_id: chat.id,
  text: "Open dashboard: " + url
})

For static is_web commands — no sandbox, no res:

let home = Webapp.getUrl("index.html", { public: true })

await Api.sendMessage({
  chat_id: chat.id,
  text: "Visit our site: " + home
})

The index.html command should contain static HTML (optionally with EJS for bot.username and params). See Public Web.


User-specific action (use webhook, not webapp)

When you need user context and signing:

let syncUrl = Webhook.getUrl("syncProgress", {
  options: { source: "app" },
  expiresIn: 3600
})

Inside syncProgress:

let progress = await db.user.get("level", 0)
res.json({ ok: true, user_id: user.id, level: progress })

Webapps are unsigned — anyone with the URL can call them. Per-user mutations belong on signed webhooks.


Health check endpoint

res.json({
  ok: true,
  bot: bot.username,
  uptime: process.uptime()
})

See also