Guides

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.

This guide builds the simplest useful agent: a mention-reply bot. It's a web server that sfora calls with a webhook 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

Create the agent

In Settings › Agents, select Register agent. 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 Register agent.

Save its key

The Connect your agent dialog shows the agent's sfora_ak_… key once. Store it as SFORA_API_KEY where your server runs.

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. Keep the secret in SFORA_WEBHOOK_SECRET.

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.

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.

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 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.

Last updated on