# How to collect feedback for a Slack app

> A Slack app collects feedback by sending a request from its bot backend to the Escuta Produto REST API. Add a slash command that replies with the hosted form link, post each report with fetch, and store the workspace id in metadata so you know which team sent it.

Source: https://escutaproduto.com/resources/collect-feedback-slack-app
Last updated: 2026-10-11

## Why the Slack app sends feedback from its backend

A Slack app sends feedback from the server that runs your bot, not from inside Slack. Slack delivers a slash command or a button click to your backend over HTTPS, and your backend calls the Escuta Produto REST API with the product's public key. The widget script does not fit here, because Slack messages and modals are not pages you control, so there is no place to load a remote script.

The REST API takes a JSON body with `key`, `message` and `metadata`, and returns `201` with the new item `id` when the report is saved. Every field is listed in the [REST API reference](/docs/api). The hosted form covers the cases where a user wants to write more than a short line.

## Reply to the slash command with the hosted form

Slack gives your app three seconds to acknowledge a command, so the handler should respond quickly and do the slow work after that. A good flow looks like this:

1. Create the slash command in your Slack app settings and point its request URL at your backend.
2. Acknowledge the request right away, then read the text the user typed after the command.
3. If the text is too short to be useful, reply privately with the hosted form link.
4. If the text is usable, send it to the REST API and confirm the result in a private reply.

Replies to a slash command should be ephemeral, so only the person who typed it sees them. Public replies would put every report in the channel where it was typed.

```js
const { App } = require("@slack/bolt");

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

app.command("/feedback", async ({ ack, command, respond }) => {
  await ack();
  const message = command.text.trim();

  if (message.length < 2) {
    await respond({
      response_type: "ephemeral",
      text: "Add a short note after /feedback, or write a longer one here: https://escutaproduto.com/f/your-product-slug?lang=en",
    });
    return;
  }

  const result = await sendToEscuta({
    message: message,
    kind: "other",
    workspaceId: command.team_id,
    userId: command.user_id,
    channelId: command.channel_id,
  });

  await respond({
    response_type: "ephemeral",
    text: result.ok ? "Thanks, the team has your feedback." : "We could not save that. Please try again in a minute.",
  });
});
```

The `kind` field defaults to `other`. A slash command has no type buttons, so pick `bug` or `idea` only when the command has a separate subcommand for each one.

## Post the report with the workspace id as metadata

The helper below sends the request. It keeps the public key in an environment variable, although the key is safe to expose, and it returns a small result object instead of throwing, so the command handler can reply in every case.

```js
async function sendToEscuta({ message, kind, workspaceId, userId, channelId }) {
  const response = await fetch("https://escutaproduto.com/api/v1/feedback", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      key: process.env.ESCUTA_PUBLIC_KEY,
      message: message,
      kind: kind,
      metadata: { workspaceId: workspaceId, channelId: channelId, userId: userId },
    }),
  });

  if (response.status === 201) {
    const data = await response.json();
    return { ok: true, id: data.id };
  }
  return { ok: false, status: response.status };
}
```

The workspace id is the most useful field. A Slack app is often installed in many companies, and the workspace id tells you which install a report came from without asking the user. The user id is a personal identifier, so include it only if your team needs to reply to a specific person, and mention it in your privacy notice. Keep metadata small. The whole object must stay under 4 KB.

## What happens when one workspace sends too much?

The API allows 10 requests per minute per IP address and product. Every request from your bot server leaves from the same few addresses, so the limit is shared by all workspaces, not per user. A busy launch day can use it up quickly, and the extra requests return 429.

Two habits keep the bot working. First, give each user a short cooldown, so one person cannot send ten messages in a row. Second, send through a queue that spaces requests out. Ten requests per minute works out to one request every six seconds. For example, if twelve people send feedback in the same minute, the last two wait in the queue and go out a few seconds later. When a 429 arrives anyway, wait and retry once, then tell the user the report is queued.

## Which metadata helps a Slack app team?

Metadata explains the report without a follow-up question. Use keys that you can filter or read at a glance:

| Field | Source | Why it helps |
| --- | --- | --- |
| workspaceId | command.team_id | Shows which installation sent it |
| channelId | command.channel_id | Shows where the user was working |
| appVersion | your package version | Separates bugs from releases |
| command | the command name | Tells `/feedback` apart from other entry points |

The inbox filters by status and type, not by metadata. Read the metadata while you triage, and add an internal note with the decision. The CSV export keeps the metadata as a column if you want to group reports in a spreadsheet.

## Where do Slack app reports show up for the team?

Escuta Produto sends alerts to one Slack or Discord incoming webhook per product. Each new item posts its type, the product, the rating if there is one, the sender, a 500-character excerpt and a link to the dashboard. Use a separate channel for these alerts, so the bot's own replies and the feedback alerts do not mix. The [notifications guide](/docs/notifications) covers the webhook setup.

The team then works the queue the same way as any other feedback. The article on [how to triage customer feedback](/resources/how-to-triage-customer-feedback) describes a simple routine, and the [developer tools article](/resources/collect-feedback-developer-tools) shows how to route errors from a technical product.

## Where Escuta Produto fits in a Slack app

Create one product for the Slack app and copy its public key into your bot's environment. Add the slash command, the ack and reply flow above, the cooldown and the queue. Send one test report from a test workspace, then check that the workspace id appears in the item's metadata. Add the webhook last, so the first alerts go to the right channel.

If you want a general starting point before you build the command, the guide on [how to collect customer feedback](/resources/how-to-collect-customer-feedback) explains which questions to ask and when to ask them.

## Frequently asked questions

### Can a Slack app load the Escuta Produto widget script?

No. Slack messages and modals are not web pages you control, so there is nowhere to load the widget. Send feedback from your bot backend through the REST API, and use the hosted form link for longer messages.

### Why do Slack app reports sometimes return 429?

The API allows 10 requests per minute per IP address and product. A bot server sends every request from the same few addresses, so a busy day can reach the limit. Queue the requests, send them at a steady pace and retry once after a 429.

### Which Slack id should I store in the metadata?

Store the team id as the workspace id, because it identifies the installation. The channel id helps you see where people were working. Store the user id only when your team needs it and your privacy notice covers it.
