Getting started

This walk-through builds a complete app: a temperature converter. A user types /temp, enters a temperature, and gets a card they can post into the room.

Before you start

  • A Banga account. Developer tools are under Your apps once you sign in.
  • A server reachable over HTTPS from the internet, with a certificate from a public authority. The platform refuses webhook addresses that resolve to private, loopback or link-local networks.

1. Create the app

Open Your apps and choose New app. Give it a name (3 to 40 characters) and, optionally, a slug for its links (3 to 40 lower-case letters, digits and dashes). The slug is derived from the name when you leave it blank and cannot be changed later. An account may own up to 20 apps.

2. Write the manifest

The manifest is a JSON document that declares what your app is: its commands, the forms behind them, the permissions it asks for and where its webhook lives. Open the app's Manifest tab, paste this, change the webhook URL to your own server and choose Save draft. Every problem is listed at once, with the path of the field.

{
  "contract": 1,
  "name": "Temperature Converter",
  "description": "Convert a temperature from Fahrenheit to Celsius or back.",
  "scopes": ["post.cards"],
  "webhook": { "url": "https://apps.example.com/banga/temperature" },
  "commands": [
    {
      "id": "temperature.convert",
      "command": "/temp",
      "title": "Convert a temperature",
      "description": "Fahrenheit to Celsius or back",
      "flow": "convert"
    }
  ],
  "flows": {
    "convert": {
      "mode": "submit",
      "views": [
        {
          "id": "temperature",
          "title": "Convert a temperature",
          "submitLabel": "Convert",
          "blocks": [
            { "type": "input", "id": "value", "label": "Temperature", "input": "number",
              "required": true, "min": -459.67, "max": 1000000, "placeholder": "72" },
            { "type": "input", "id": "unit", "label": "It is in", "input": "select", "style": "chips",
              "required": true, "default": "f",
              "options": [
                { "value": "f", "label": "°F, convert to °C" },
                { "value": "c", "label": "°C, convert to °F" }
              ] }
          ]
        }
      ]
    }
  }
}

The command runs the convert flow, which has one form view with a number and a pair of chips. The post.cards scope lets the app offer its card to the room. See the manifest reference for every field.

3. Build the webhook

Every call is a signed POST. Your webhook checks the signature, answers the platform's health check (ping) with pong, and answers a command with a card. Here is the whole app in Node.js:

// npm install express
const crypto = require("crypto");
const express = require("express");

const SECRET = process.env.BANGA_WEBHOOK_SECRET; // the whole secret, including "whsec_"
const seenDeliveries = new Map(); // delivery id -> when it may be forgotten

function isAuthentic(req) {
  const ts = req.get("X-Banga-Timestamp");
  const delivery = req.get("X-Banga-Delivery");
  const presented = (req.get("X-Banga-Signature") || "").split(",").map((s) => s.trim());
  if (!SECRET || !ts || !delivery || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto.createHmac("sha256", SECRET).update(ts + ".").update(req.body).digest("hex");
  const matches = presented.some((s) => s.startsWith("v1=") && s.length === expected.length + 3
      && crypto.timingSafeEqual(Buffer.from(s.slice(3)), Buffer.from(expected)));
  if (!matches) return false;
  const now = Date.now();
  for (const [id, forgetAt] of seenDeliveries) if (forgetAt < now) seenDeliveries.delete(id);
  if (seenDeliveries.has(delivery)) return false;
  seenDeliveries.set(delivery, now + 10 * 60 * 1000);
  return true;
}

const round = (n) => String(Math.round(n * 100) / 100);

const app = express();
// The signature covers the raw bytes, so read them before any JSON parsing.
app.post("/banga/temperature", express.raw({ type: "application/json" }), (req, res) => {
  if (!isAuthentic(req)) return res.status(401).json({ type: "error", userMessage: "Bad signature" });
  const call = JSON.parse(req.body.toString("utf8"));
  if (call.type === "ping") return res.json({ type: "pong" });

  const value = Number(call.values.value);
  const fahrenheit = call.values.unit === "f";
  if (value < (fahrenheit ? -459.67 : -273.15)) {
    return res.json({ type: "error", userMessage: "That is colder than absolute zero", code: "below_zero" });
  }
  const from = round(value) + (fahrenheit ? " °F" : " °C");
  const to = fahrenheit ? round((value - 32) * 5 / 9) + " °C" : round(value * 9 / 5 + 32) + " °F";
  res.json({
    type: "card",
    visibility: "room",
    card: {
      blocks: [
        { type: "title", text: from + " = " + to },
        { type: "facts", items: [{ k: "From", v: from }, { k: "To", v: to }] }
      ],
      actions: [{ type: "open_flow", label: "Convert another", style: "primary", flowId: "convert" }]
    }
  });
});

app.listen(process.env.PORT || 8080);

The same checks in Python and Java are on the Webhooks page.

4. Publish and set the secret

On the Overview tab, choose Publish draft. The first time you publish a manifest with a webhook, the portal shows its signing secret, which starts with whsec_. It is shown only once, so store it now and give it to your server as BANGA_WEBHOOK_SECRET. If you lose it, rotate it on the Webhook tab.

5. Try it in the test console

The Test console tab sends a real, signed call to your webhook and shows the exact headers and body it sent and the raw reply. Choose the temperature.convert command and enter the values as JSON, keyed by input id:

{ "value": 72, "unit": "f" }

Console calls are recorded on the Activity tab like any other. The console can also check a reply you paste in, without calling your webhook, which helps while you shape a card.

6. Use it on your phone

You, as the owner, and your testers can use the app before anyone else can. Until the first version is published, you all get the draft. Open a room, type /temp, fill in the form and post the card.

7. Share it

The Testers & visibility tab decides who can find the app:

  • Private: you and up to 20 testers, added by their phone number.
  • Unlisted: anyone with the link or QR code. The install page is https://dev.banga.chat/a/your-slug.html, and banga://a/your-slug opens the app's details on a phone.
  • Public: listed in the app directory. Ask for it with Request review; a site administrator approves it. Until then the app stays as visible as it was. On the stage service, https://stage.banga.chat, a request is approved at once.

Users see your app's name, description and the scopes it asks for, and must accept all of them to install it.

Updating your app

  • Edit the manifest and save it as a new draft. Nothing changes for users until you publish it. A published version never changes.
  • Publishing makes the new version current, and installed phones are told to refresh.
  • Roll back brings the previous version back if a release goes wrong.
  • Suspend pauses the app for everyone until you resume it. You can delete an app once nobody has it installed. A site administrator can block an app, and only they can lift a block.