Slack setup

SkillCommunication

Set up or verify Slack in Backoffice for bot-token API calls, sending messages, Events API webhooks, app mentions, URL verification, signing-secret validation, or checking whether a Slack event arrived.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Slack setup skill

What this skill tells your AI

The instructions your AI receives, as published by rejot-dev/fragno in apps/backoffice/content/marketplace/slack-setup/versions/1.0.0/skills/slack-setup/SKILL.md and read by ahel’s review.

Set Slack up as one handshake: connect → verify → receive → prove.

1. Inspect existing API connections

The API capability is available automatically for the current scope. Inspect the existing outbound API connections before creating or updating the Slack connection.

Complete when the existing outbound connections are known.

2. Collect missing secrets

Collect secrets through a durable Backoffice UI rather than chat. Define a workflow and return this exact shape from a completed step.do, including only the controls for secrets still missing:

await step.do("request Slack credentials", async () => ({
  $ui: {
    version: 1,
    state: { response: { botUserOAuthToken: "", signingSecret: "" } },
    spec: {
      root: "form",
      elements: {
        form: {
          type: "Stack",
          props: { gap: "md" },
          children: ["bot-token", "signing-secret", "submit"],
        },
        "bot-token": {
          type: "TextInput",
          props: {
            label: "Bot User OAuth Token",
            description: "Slack token beginning with xoxb-.",
            value: { $bindState: "/response/botUserOAuthToken" },
            required: true,
            secret: true,
          },
          children: [],
        },
        "signing-secret": {
          type: "TextInput",
          props: {
            label: "Signing Secret",
            description: "Slack App → Basic Information → App Credentials.",
            value: { $bindState: "/response/signingSecret" },
            required: true,
            secret: true,
          },
          children: [],
        },
        submit: {
          type: "WorkflowEventButton",
          props: {
            label: "Connect Slack",
            eventType: "slack.credentials-submitted",
            payload: { $state: "/response" },
          },
          children: [],
        },
      },
    },
  },
}));

const credentials = await step.waitForEvent("Slack credentials", {
  type: "slack.credentials-submitted",
});

Use credentials.payload.botUserOAuthToken and credentials.payload.signingSecret only inside later step.do calls. Keep them out of workflow output, tool summaries, and prose.

Complete when the workflow has received every missing secret through slack.credentials-submitted.

3. Connect the Slack bot

Use an existing active API connection whose slug is slack. Otherwise use the submitted Bot User OAuth Token to create this connection:

await api.createConnection({
  slug: "slack",
  name: "Slack",
  baseUrl: "https://slack.com/api",
  auth: { type: "bearer", token: botUserOAuthToken },
});

Treat tokens as secrets: use the exact user-supplied value only in the tool call and omit it from results and prose. Verify the connection with api.getAuthStatus, then call Slack's POST /auth.test with an empty JSON body. Slack API calls can return HTTP 200 for failures, so require both HTTP 200 and body.ok === true.

If verification fails, report Slack's error value and stop. Never replace a missing token with a placeholder or test credential.

Complete when authentication is active and auth.test returns ok: true.

4. Send messages

Send with POST /chat.postMessage and a JSON body containing channel and text:

await api.request({
  slug: "slack",
  method: "POST",
  path: "/chat.postMessage",
  headers: { Accept: "application/json", "Content-Type": "application/json" },
  json: { channel, text },
  timeoutMs: 30000,
});

Use the channel identifier exactly as the user supplied it. Make discovery calls such as conversations.list only when the user asks to resolve or browse channels. Require body.ok === true and report Slack's error otherwise.

Complete when Slack confirms the message with ok: true.

5. Configure inbound Slack events

Inspect before writing:

const existing = await api.getWebhookEndpoint({ endpointId: "slack" });

Use this Slack challenge configuration:

const verification = {
  type: "challenge",
  method: "POST",
  when: {
    type: "equals",
    source: { type: "jsonBodyPath", path: ["type"] },
    value: "url_verification",
  },
  response: {
    type: "echoText",
    source: { type: "jsonBodyPath", path: ["challenge"] },
  },
};
const deliveryIdentity = { type: "jsonBodyPath", path: ["event_id"] };

When the endpoint is absent, create it as a draft. createWebhookEndpoint replaces the complete resource, so use it only in this branch:

await api.createWebhookEndpoint({
  endpointId: "slack",
  name: "Slack",
  status: "draft",
  verification,
  deliveryIdentity,
  auth: { type: "none" },
});

When the endpoint exists, preserve its stored secrets and unrelated configuration. Use updateWebhookEndpoint and include only fields that must change. In particular, omit auth when the existing authConfig is already Slack-compatible HMAC; omission preserves the stored signing secret:

await api.updateWebhookEndpoint({
  endpointId: "slack",
  verification,
  deliveryIdentity,
});

A Slack-compatible existing HMAC configuration has all of these values:

  • type: "hmac" and algorithm: "sha256"
  • signature header x-slack-signature, hex encoding, and prefix v0=
  • timestamped body prefix v0:, header x-slack-request-timestamp, delimiter :, and a 300-second tolerance
  • at least one stored secretRef

Collect a Signing Secret only when creating the endpoint or replacing absent/incompatible HMAC. Activate or repair it with one patch containing the necessary changed fields and this auth value:

await api.updateWebhookEndpoint({
  endpointId: "slack",
  status: "active",
  verification,
  deliveryIdentity,
  auth: {
    type: "hmac",
    secret: signingSecret,
    algorithm: "sha256",
    signature: {
      location: "header",
      name: "x-slack-signature",
      encoding: "hex",
      prefix: "v0=",
    },
    signedPayload: {
      type: "timestampedBody",
      prefix: "v0:",
      timestampHeader: "x-slack-request-timestamp",
      delimiter: ":",
      toleranceSeconds: 300,
    },
  },
});

Re-read the endpoint after every write. Give its publicUrl to the user for Slack App → Event Subscriptions → Request URL. Tell the user to subscribe to the app_mention bot event, grant app_mentions:read and chat:write, reinstall the Slack app when scopes change, and retry Request URL verification.

Complete when the re-read endpoint is active with Slack challenge handling, event_id delivery identity, and timestamped HMAC verification. Never report an existing setup as verified from status alone.

6. Prove event delivery

When the user asks whether a Slack message arrived, call:

await hooks.list({ fragment: "api", pageSize: 10 });

Find the newest completed onWebhookReceived entry whose payload has endpointId: "slack". Confirm receipt from its parsed body.event, including event type, channel, and message text when present. Keep authorization tokens, signatures, raw headers, and unrelated payload fields out of the response.

A received event proves ingestion only. State clearly when no automation exists yet to reply. Sending a reply requires chat.postMessage, normally using body.event.channel; use body.event.ts as thread_ts only when the requested reply should be threaded.

Complete when the newest matching delivery is identified, or the absence of one is reported.

Signals

GitHub stars
61
Forks
6
Last commit
Sep 2026

ahel review

  • S4info
    community integration — published by rejot-dev, not slack

Automated review, not a security audit. Ruleset v1.

Advanced
Catalog kind
skill
Gateway key
slack-setup
Source
github.com/rejot-dev/fragno