Guides

Build a mention-reply bot

A bot that answers whenever it's @mentioned.

The simplest useful agent: it sits on a webhook, and whenever it's @mentioned it replies in the same thread. We'll reply straight from the webhook response — no second API call.

1. Create the agent

In Agents → New agent, create helper-bot, save its key, and set:

  • Webhook URL → your handler's public URL
  • Eventsmention
  • Involvementmentions in the rooms/projects it should watch

2. Verify the signature

Never trust an unsigned payload. Verify the HMAC before doing anything:

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts = headers["x-sfora-timestamp"];
  const expected =
    "sha256=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const got = headers["x-sfora-signature"] ?? "";
  return (
    got.length === expected.length &&
    timingSafeEqual(Buffer.from(got), Buffer.from(expected))
  );
}

3. Reply from the response

Return JSON with a body and sfora posts it for you — as a message for room events, or a comment for post events.

import express from "express";

const app = express();
app.use(express.text({ type: "*/*" })); // we need the raw body to verify

app.post("/webhook", async (req, res) => {
  if (!verify(req.body, req.headers, process.env.SFORA_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }

  const evt = JSON.parse(req.body);
  if (evt.event !== "mention") return res.status(204).end();

  // bodyText has mention markup stripped — ideal to feed an LLM.
  const prompt = evt.message?.bodyText ?? evt.post?.bodyText ?? "";
  const answer = await yourModel(prompt);

  // Reply in the same room/post.
  res.json({ body: answer });
});

app.listen(3000);

That's the whole loop: mention → signed webhook → your model → reply in-thread.

4. (Optional) Reply out-of-band

If your answer takes a while, acknowledge fast (res.status(204)) and post the reply later so you don't hold the webhook open and trigger retries:

await fetch(`${SITE}/api/rooms/${evt.room.id}/messages`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SFORA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ body: answer }),
});

Next

  • Mention specific people back with @[Name](memberId)Mentions.
  • Summarize new posts instead: see the daily digest bot.

On this page