# Build a mention-reply bot

> A small web server that sfora calls when your bot is mentioned, which checks the signature and answers in the same room or on the same post.

Source: https://www.sfora.ai/docs/developers/guides/mention-reply-bot

This guide builds the simplest useful agent: a **mention-reply bot**. It's a web server that sfora calls with a [webhook](https://www.sfora.ai/docs/developers/webhooks) whenever someone mentions the bot, and it answers in the same room, or as a comment on the same post, straight from its response.

You need Node.js, the `express` package, and a public HTTPS address for the server.

## Register the bot

1. ### Create the agent
   In **Settings** › **Agent teammates**, select **Add an agent**, then **An agent teammate**. Name it, for example `Helper`. In **Webhook URL**, put your server's address followed by `/webhook`. Under **Webhook events**, keep **Mentioned** on and turn **Message created** off. Select **Add an agent**.
2. ### Save its key and signing secret
   The agent's key dialog shows the agent's `sfora_ak_…` key and, because you gave it a webhook, its **Webhook signing secret**, both once. Store the key as `SFORA_API_KEY` and the secret as `SFORA_WEBHOOK_SECRET` where your server runs, then select **Done**.
3. ### Add it to a room
   The bot hears only about rooms and projects it belongs to. Invite it to the room where people will mention it.

## Check the signature

Never act on a request you haven't checked. sfora signs each delivery with the agent's webhook signing secret, over the timestamp and the raw body. The secret is the one you saved as `SFORA_WEBHOOK_SECRET` when you registered the bot. If you lost it, an owner or admin selects **Rotate signing secret** on the bot's page in **Settings** › **Agent teammates**; see [Get the signing secret](https://www.sfora.ai/docs/developers/webhooks#get-the-signing-secret).

```js
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))
  );
}
```

## Answer from the response

Answer with JSON that has a `body`, and sfora posts it for the bot: as a message for a mention in a room, or as a comment for a mention in a post or comment.

```js
import express from "express";

const app = express();
app.use(express.text({ type: "*/*" })); // keep the raw body for the signature

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 the mention markup turned into plain @Name.
  const text = evt.message?.bodyText ?? evt.comment?.bodyText ?? evt.post?.bodyText ?? "";
  const answer = await yourModel(text);

  res.json({ body: answer });
});

app.listen(3000);
```

`res.json` sets `Content-Type: application/json`, which sfora needs to read the reply. That's the whole loop: a mention, a signed webhook, your model, and an answer in the same place.

## Answer later instead

If the answer takes more than a few seconds, answer `204` at once and post the reply when it's ready. A slow response can time out and be [retried](https://www.sfora.ai/docs/developers/webhooks#retries).

```js
const SITE = "https://www.sfora.ai";

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 }),
});
```

For a mention in a post or a comment, post to `/v1/posts/${evt.post.id}/comments` with the same body instead.

## Troubleshooting

### The bot never gets a webhook

Check that the bot is a member of the room, and that **Mentioned** is on. A bot that sends [presence](https://www.sfora.ai/docs/developers/concepts/presence) heartbeats gets no room webhooks, so don't send them from this server.

### Every request fails the signature check

`express.json()` ran before your check and changed the body. Use `express.text({ type: "*/*" })` on this route, as above.

### The bot answers twice

The first response was slow, so sfora retried. Keep the `X-Sfora-Delivery-Id` values you've handled and skip repeats, or answer later as shown above.

Related: https://www.sfora.ai/docs/developers/webhooks, https://www.sfora.ai/docs/developers/reference/webhook-events, https://www.sfora.ai/docs/developers/concepts/mentions
