Skip to content
Ed Mozley edited this page Aug 7, 2026 · 2 revisions

Slack

Turn a message in a Slack channel into a ticket, and answer it from the FreeITSM inbox without either party leaving the thread.

Set up under System β†’ Integrations β†’ Slack.


What it is β€” and what it is not

Slack sits under Integrations because that is where people look for "connect FreeITSM to X". Underneath it is not a third issue tracker. Jira and Azure DevOps share an engine because they are the same shape of thing: raise a work item, watch its state, pull comments back. Slack has no work items and no states.

It is a messaging channel β€” the same engine as WhatsApp and Web chat β€” so its connections live in messaging_channels, not integration_connections, and it reuses the existing webhook endpoint, ingest and reply path.

That split is why the provider registry carries a kind. Anything asking "can we raise a work item in it?" must use integrationsTrackerProviders(), or Slack gets offered as an escalation target and fails at the moment somebody tries.


πŸ”‘ Bring your own app

There is no FreeITSM-hosted Slack app, and there will not be one. You create an app in your own workspace from a manifest FreeITSM generates, and it talks straight to your server.

This is a deliberate rejection of the publish-one-app model that a SaaS product would use. Publishing one would mean this project ran a server that every customer's Slack traffic passed through β€” permanently, on the project's liability β€” and became a data processor for other people's companies. For a free, self-hosted product that is the wrong trade.

The cost is about five minutes of setup per install. What you get back:

  • your ticket contents never touch anyone else's infrastructure
  • no central service that can be down, rate-limited or discontinued
  • nothing to trust beyond Slack and your own server

The catch, stated plainly: for Slack to send anything, it must reach your install over the internet on https://. An install that is only reachable inside your own network can post out to Slack (that already works through webhooks) but cannot receive. messaging_channels.ingress_mode has a relay value reserved for solving that later; it is not built.


Setting it up

The in-app guide at System β†’ Integrations β†’ Slack β†’ How to set up Slack is the step-by-step version, written for someone who has never opened Slack. The shape of it:

  1. Add the workspace in FreeITSM first. The manifest embeds the row's own webhook address, so the row has to exist before the app can be created.
  2. Create the Slack app from the generated manifest (api.slack.com/apps β†’ From a manifest).
  3. Copy the signing secret and bot token back.
  4. /invite the app to the channel it should watch.

⚠️ The two traps

Reinstall after creating from a manifest. Slack grants an app its permissions at install time. An app created from a manifest holds a token missing most of its scopes until you click the yellow Reinstall to Workspace banner. Nothing appears broken β€” tickets still arrive, they just say "Slack user @U0A1B2C3" instead of a name, because reading profiles was one of the permissions that never made it. A requester's name now corrects itself on their next message once the lookup starts working.

An app cannot read a channel it is not in. No error, no warning β€” messages simply never arrive. /invite @YourApp in the channel.

Scopes, and why each one

The Slack admin approving the app sees this list, and an unexplained scope is a reason to refuse.

Scope Why
channels:history Read messages in public channels the app is invited to β€” this is how a message becomes a ticket
groups:history The same, for private channels
im:history The same, for direct messages to the app
chat:write Post the analyst's reply back into the thread
users:read Turn a Slack user id into a person's name on the ticket
users:read.email Match that person to an existing FreeITSM user
files:read Download a shared screenshot and attach it

Deliberately absent: channels:manage, users:write, and anything that writes files. Those belong to features that do not exist.


How it behaves

A thread is a ticket. This is the one place Slack deliberately differs from WhatsApp. On WhatsApp a person has one running conversation with the service desk, so the sender identifies it. In Slack the same person can have five unrelated threads open at once β€” so the thread is the conversation. A new message in the channel starts a new ticket; a reply in a thread lands on the existing one.

Only watch one channel. Leave the setting blank and every channel the app is invited to raises tickets, which on a busy channel means a ticket per message. Name a dedicated #it-help channel instead.

Identity does not depend on an email match. Halo's Slack integration requires a person's Slack email to equal their Halo email, which fails for guests, contractors and anyone whose Slack is a personal address. FreeITSM looks the email up and uses it when it matches an existing user; when it does not, it falls back to a named requester β€” "Sam Okafor (Slack)", or "Slack user @U0A1B2C3" if even the name is unavailable. Never a blank requester, never a silent failure.

No 24-hour window. That is a WhatsApp Business rule. You can reply to a Slack thread from last year.

Multi-company works as it does for any channel: pin the workspace to one company, or leave it shared and let the sender decide. See Multi-Tenancy.


πŸ“ The files

Colour key: πŸ”Œ provider Β· πŸ“₯ inbound Β· πŸ“€ outbound Β· πŸ–₯️ UI Β· πŸ—„οΈ schema Β· πŸ“„ docs

🎨 File What it does
πŸ”Œ includes/messaging/SlackProvider.php The whole Slack side: signature check, event parsing, posting, profile lookup, file download
πŸ”Œ includes/messaging/slack_manifest.php Generates the app manifest and the scope list; warns if this install's URL cannot work
πŸ”Œ includes/messaging/messaging.php messagingProvider() dispatch, and normaliseChannelIdentifier() β€” which is channel-aware for a reason, see below
πŸ“₯ api/messaging/webhook.php The public endpoint. Per-channel, signature-verified, handles Slack's one-time URL challenge
πŸ“₯ includes/messaging/ingest.php Message β†’ ticket. Holds the thread-is-a-conversation rule and resolveSlackRequester()
πŸ“€ api/messaging/send_message.php The analyst's reply, posted back into the thread
πŸ–₯️ system/integrations/slack.php The settings page: workspaces, the manifest, the tests
πŸ–₯️ system/integrations/help_slack.php The in-app setup guide
πŸ–₯️ api/messaging/slack_manifest.php Serves the manifest to that page
πŸ—„οΈ messaging_channels provider='slack', channel_type='slack', channel_ref = the workspace id, credentials encrypted
πŸ“„ lang/en/system.php, lang/pt-BR/system.php The slack_* keys

πŸ› Traps worth knowing (all found by doing it, not by testing)

Slack's read methods do not accept a JSON body. chat.postMessage does; users.info does not β€” given JSON it parses no arguments at all and answers user_not_found for a user that plainly exists. Nothing errors. Every call is form-encoded, which every method accepts. This survived a full unit-test suite because auth.test takes no arguments.

The setup deadlock. Slack verifies the request URL the instant the app is created β€” which is also the instant the signing secret first exists. Requiring a signature on that handshake makes setup impossible, not merely strict: no verified URL β†’ no secret β†’ no verified URL. The handshake is answered unsigned only while the channel has no stored secret. A real event_callback always requires a valid signature, so nothing can be injected through the gap.

The old sender normaliser was phone-shaped. It stripped every non-digit, turning U08ABCDEF into +08 β€” five distinct Slack users collapsed onto two requesters and would have read each other's tickets. Any future non-phone channel must pass its channel type.

Bot messages come back as events. Ingesting them makes the ticket talk to itself forever. Three separate guards, because Slack marks bot messages inconsistently across subtypes.

The webhook URL used to double the app path when the configured public base URL already contained it β€” producing /freeitsm-app/freeitsm-app/…. That string is what gets pasted into Slack, and the 404 fails verification with an error that mentions nothing about a path. It affected WhatsApp too.


Not built

Message shortcuts (raise a ticket from any message via the … menu), /ticket slash commands, ticket cards with action buttons, incident channels created from a ticket, and any Slack chatbot surface. The shortcut and a /find card are the strongest candidates for the next slice; both need a second interactivity endpoint.


Related

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally