The heyemoji bot (Slack-facing name: HeyBitovi) is a self-hosted slack reward system that allows team members to recognize eachother for anything awesome they may have done. This is accomplished with the /heybitovi give slash command, naming a @username along with a pre-configured reward emoji and an optional reason for what they did. The emoji points bestowed to users can be tracked via leaderboards.
/heybitovi give @michael.bolton :star: Great job filling out those TPS reports
/heybitovi give @michael.bolton @samir :star: Thanks for coming in this weekend
/heybitovi give @petergibbons :star: :trophy: :clap: :clap: Found my red stapler!
give can be run from any channel, private channel, or a DM with the bot - it doesn't matter where you type it. The public recognition message always posts to one fixed announcement channel (HEY_ANNOUNCE_CHANNEL_ID), and a separate copy also posts in the channel you ran it from (skipped for DMs, and when you ran it in the announcement channel itself). You also always get a DM confirming the points were recorded. In the announcement channel, multiple gives to the same person on the same day thread onto that person's first announcement of the day rather than posting separately; the source-channel copy is always a plain top-level message. Posting into a public channel the bot hasn't joined needs the chat:write.public scope; for a private channel, /invite the bot first.
| Name | Description |
|---|---|
/heybitovi give @user :emoji: [reason] |
give someone recognition, with an optional reason |
/heybitovi leaderboard <day|week|month|quarter|year|all> |
see the top point earners for a period |
/heybitovi points |
see how many emoji points you have left to give |
/heybitovi help |
get help with how to send recognition emoji |
heyemoji talks to Slack over Socket Mode, so it needs a Bot User OAuth Token (xoxb-...) and an app-level token (xapp-...) β no public URL or Request URL is required to run it locally.
The fastest way is from a manifest, which sets up the bot user, slash command, scopes, and Socket Mode all at once.
-
Go to api.slack.com/apps and click Create New App > From an app manifest.
-
Pick the workspace you want to test in (use a personal/sandbox workspace or a dedicated test workspace if you don't want this showing up for everyone yet).
-
Choose YAML, paste the manifest below (rename
name/display_nameif you're creating more than one test app), and click Next > Create.display_information: name: HeyBitovi description: Give teammates recognition with emoji points background_color: "#4A154B" features: app_home: home_tab_enabled: false messages_tab_enabled: true messages_tab_read_only_enabled: false bot_user: display_name: HeyBitovi always_online: true slash_commands: - command: /heybitovi description: Give recognition, check the leaderboard, and more usage_hint: "give @user :emoji: [reason] | leaderboard <day|week|month|quarter|year|all> | points | help" should_escape: false oauth_config: scopes: bot: - chat:write - chat:write.public - commands - users:read settings: org_deploy_enabled: false socket_mode_enabled: true token_rotation_enabled: false
Don't want to use a manifest? Click through it manually instead: Slash Commands > create
/heybitovi(leave Request URL blank β Socket Mode delivers it); OAuth & Permissions > Scopes > add thechat:write,chat:write.public,commands, andusers:readBot Token Scopes; Socket Mode > toggle it on; App Home > Show Tabs > enable Messages Tab and check "Allow users to send Slash commands and messages from the messages tab" (without this, DMing the bot shows "Sending messages to this app has been turned off" and you can't type anything into the DM, slash commands included).Renaming an app you already created? Basic Information > Display Information > update App Name; App Home > Your App's Presence in Slack > update the bot's Display Name; Slash Commands > edit the existing command's Command field from
/heyemojito/heybitovi(Slack lets you rename a command in place, no need to delete and recreate it).
- App-level token (for
HEY_SLACK_APP_TOKEN): Basic Information > App-Level Tokens > Generate Token and Scopes. Give it any name, add theconnections:writescope, click Generate, and copy thexapp-...token. - Bot token (for
HEY_SLACK_BOT_TOKEN): OAuth & Permissions > Install to Workspace (or Install App, if this is a reinstall) > Allow. Copy the Bot User OAuth Token (xoxb-...) from the top of that page.
- Create a
.envfile in theheyemojiroot folder:(user/password areHEY_SLACK_BOT_TOKEN=xoxb-... HEY_SLACK_APP_TOKEN=xapp-... DATABASE_URL=postgres://<user>:<password>@postgres:5432/heyemoji?sslmode=disableheyemoji/heyemoji, matching the Postgres service already defined indocker-compose.yamlβ no separate setup needed.DATABASE_URLis the one setting without aHEY_prefix: in production it's injected by the platform's Postgres dependency under this exact, fixed name.) - Run
docker-compose up --build. The app will run its Postgres migrations on startup and connect to Slack over Socket Mode. - In Slack, invite the bot to a channel you want to test in:
/invite @HeyBitovi. - Try it out:
/heybitovi help, then/heybitovi give @someone :star: nice work, then/heybitovi leaderboard.
Each teammate testing separately should create their own app this way (or share one app's tokens) β a Slack app, its slash command, and its tokens are scoped to whichever workspace you installed it into in step 1.
A small read-only web UI (leaderboard + recognition feed) is served from the same container as the bot, at whatever port HEY_HTTP_PORT is set to (default 8080). It's gated by OIDC sign-in against the Bitovi Platform Keycloak (not Slack), which brokers Google and only lets bitovi.com accounts through. The web server simply doesn't start until all four of OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, and HEY_SESSION_SECRET are set (you'll see web UI disabled: ... in the logs until then) β the Slack bot itself works fine either way.
In production you don't set the OIDC_* variables yourself: the sso dependency in deploy/values.yaml has the platform create the Keycloak client heyemoji-sso (redirect URI {HEY_BASE_URL}/auth/callback) and inject all three. Note they have no HEY_ prefix, like DATABASE_URL, because the platform sets those names.
For local testing without a Keycloak client, set HEY_WEB_AUTH_DISABLED=true β this starts the web UI with no sign-in required, reachable by anyone who can reach the port. It logs a loud warning on startup so it can't accidentally stay on unnoticed. Never use this for anything other than local testing.
You need a Keycloak client whose redirect URIs include http://localhost:8080/auth/callback (ask the platform operator; a staging overlay can add one with extraRedirectUris). Then add to your .env:
OIDC_ISSUER_URL=https://keycloak.bitovi-tools.com/realms/bitovi
OIDC_CLIENT_ID=...
OIDC_CLIENT_SECRET=...
HEY_SESSION_SECRET=some-long-random-string
HEY_SESSION_SECRET just needs to be a long random string (e.g. openssl rand -hex 32) β it signs the login session cookie.
Restart (docker-compose up --build), then visit http://localhost:8080 β it redirects to Keycloak, then Google, and lands on the leaderboard once you're signed in.
| ENV Var | Default | Required | Note |
|---|---|---|---|
| HEY_BOT_NAME | HeyBitovi | No | The display name of the bot (currently unused by the app itself; the name shown in Slack is whatever you set on the Slack app) |
| DATABASE_URL | Yes | Postgres connection string. No HEY_ prefix β in production this is injected by the platform's Postgres dependency under this exact, fixed name. |
|
| HEY_SLACK_BOT_TOKEN | Yes | The Bot User OAuth Token for the Slack API | |
| HEY_SLACK_APP_TOKEN | Yes | The app-level token used for Socket Mode | |
| HEY_SLACK_EMOJI | star:1 | No | Comma delimited set of emoji "name:value" pairs |
| HEY_SLACK_DAILY_CAP | 5 | No | The max number of emoji points that can be given out in a day |
| HEY_ANNOUNCE_CHANNEL_ID | No | Slack channel ID (e.g. C0123456789, not a channel name) every /heybitovi give announcement posts to and threads under, regardless of which channel or DM the command was run from (a separate copy also posts in the source channel). The bot must be a member of it. If unset, gives are still recorded but no public announcement is posted - the giver still gets a DM confirmation. |
|
| HEY_MAX_LEADER_ENTRIES | 10 | No | Max number of entries contained in the leaderboards |
| HEY_TEST_MODE | false | No | When true, disables the daily give cap entirely (everyone has unlimited points). Every place that would normally show the cap as a number instead says "unlimited (test mode)". Not for production use. |
| HEY_HTTP_PORT | 8080 | No | Port the web UI listens on |
| HEY_BASE_URL | http://localhost:8080 | No | Public URL the app is reachable at; used to build the sign-in redirect URI ({HEY_BASE_URL}/auth/callback) |
| HEY_SESSION_SECRET | Only for web UI | Random string used to sign the web UI's login session cookie | |
| OIDC_ISSUER_URL | Only for web UI | Keycloak realm URL, e.g. https://keycloak.bitovi-tools.com/realms/bitovi. Platform-injected in production. |
|
| OIDC_CLIENT_ID | Only for web UI | Keycloak client ID. Platform-injected in production. | |
| OIDC_CLIENT_SECRET | Only for web UI | Keycloak client secret. Platform-injected in production. | |
| HEY_WEB_AUTH_DISABLED | false | No | When true, the web UI starts with no sign-in required at all. For local testing only - never for anything reachable by anyone but you. |
The HEY_SLACK_EMOJI setting lets you specify multiple different reward emoji as well as different point values for each. So, if you wanted the following emoji and reward values:
| Emoji | Value |
|---|---|
| β | 1 |
| π | 2 |
| π | 3 |
You would specify the HEY_SLACK_EMOJI as: star:1,clap:2,trophy:3
heyemoji runs on Bitovi's internal Kubernetes platform (GitOps via Argo CD). The whole app is described by deploy/values.yaml β workload, its Postgres dependency, and its domain (heybitovi.bitovi-tools.com). Secrets (Slack tokens, session secret) live in the bitovi-platform/heyemoji-secrets 1Password item, synced in via External Secrets Operator; see the comments at the top of deploy/values.yaml for the exact field labels it expects.
Pushing a vX.Y.Z tag runs .github/workflows/deploy.yml: tests, then builds and pushes the image, then writes the new tag back into deploy/values.yaml, which Argo CD picks up and rolls out. Every pull request against main runs .github/workflows/test.yml.
The web UI used to sign in with Google directly; it now signs in through the platform Keycloak (the sso dependency in deploy/values.yaml). Because deploy/values.yaml goes live as soon as it merges to main, but the image only changes when a v* tag is pushed, roll it out in this order:
- Merge the Keycloak PR. Argo CD applies the new
deploy/values.yaml. The still-running v0.3.5 image keeps signing in with Google (itsHEY_GOOGLE_*fields are still in 1Password), andHEY_GOOGLE_ALLOWED_DOMAINis still set, so it stays restricted to bitovi.com. Check that the platform created the client and its Secret:kubectl get oidcclient heyemoji-sso -n heyemoji # READY True kubectl get secret heyemoji-sso-oidc -n heyemoji - Tag a release (the next
vX.Y.Zafter v0.3.5). The deploy workflow builds the image and writes the tag back intodeploy/values.yaml, and Argo CD rolls it out. Then:- Visit https://heybitovi.bitovi-tools.com. It should redirect to
keycloak.bitovi-tools.com, then to Google, and land on the leaderboard. - The pod log shows no
web UI disabledline. - If the pod sits in
CreateContainerConfigError, theheyemoji-sso-oidcSecret doesn't exist yet. Check theOIDCClientfrom step 1.
- Visit https://heybitovi.bitovi-tools.com. It should redirect to
- Clean up once sign-in works:
- Remove
HEY_GOOGLE_ALLOWED_DOMAIN(and its comment) fromdeploy/values.yaml. The Keycloak build doesn't read it. - Delete the
HEY_GOOGLE_CLIENT_IDandHEY_GOOGLE_CLIENT_SECRETfields from thebitovi-platform/heyemoji-secrets1Password item. - Delete the old Google OAuth client (redirect URI
.../auth/google/callback) in the Google Cloud console.
- Remove
Rollback: until step 3, set image.tag in deploy/values.yaml back to v0.3.5 and merge that. The old image signs in with Google again, because its credentials and HEY_GOOGLE_ALLOWED_DOMAIN are still in place. The leftover OIDC_* env and sso client do nothing to it. After step 3, rolling back also means restoring the Google client and those fields.