Slack Status Change Notification: The Events an App Gets, Timed
Slack does not tell anyone when you change your status. An app can find out within a second through the user_status_changed and user_change events. We changed a status 7 ways and timed each event, including statuses that expired.
On this page
Slack does not notify anyone when you change your status. Nobody gets an alert, and there is no setting to follow another person's status. An app can find out, though: Slack sends it a user_status_changed event and a user_change event, and in our tests both arrived less than a second after the change. We measured this on 4 October 2026 with a test app in a free-plan workspace. The app listened over Socket Mode, subscribed to both events with the users:read scope, and we changed our own status with users.profile.set.
What each status change sent
Each row is one users.profile.set call. The times run from the moment we sent the call to the moment the event reached our app:
| Change | user_status_changed | user_change |
|---|---|---|
Emoji only (:coffee:, no text) | 0.40 s | 0.81 s |
| Text only, no emoji | 0.55 s | 0.59 s |
| Text and emoji | 0.44 s | 0.46 s |
| The same text and emoji again | none | 0.79 s |
| Clear the status | 0.40 s | 0.38 s |
| A profile call that changed no status field | none | 0.41 s |
Three things in that table matter for an app:
- •
user_status_changedfires only when the status itself changes. Sending the same status again fired onlyuser_change. - •
user_changefires for any profile write, even one that changes nothing you can see. If you subscribe touser_changealone, you get many events that are not status changes. - • The two events do not arrive in a fixed order. Across the 8 status changes we logged, including the expiry runs below,
user_changecame first 3 times.
The text-only status came back with "status_emoji": ":speech_balloon:" in the event. Slack fills in the speech balloon when a status has text and no emoji, so do not treat that emoji as one the person chose.
What the event contains
Both events carry the whole user object, not a diff. This is the event part of our text-only change, cut down to the status fields:
{
"type": "user_status_changed",
"user": {
"id": "U0B7L4YK420",
"name": "sieun",
"tz": "Asia/Seoul",
"profile": {
"status_text": "sglab test 2 text only",
"status_emoji": ":speech_balloon:",
"status_expiration": 0,
"status_clear_on_focus_end": false,
"huddle_state": "default_unset"
},
"updated": 1791076558
},
"cache_ts": 1791076558,
"event_ts": "1791076558.049800"
}
There is no "previous status" field. To say "Ana changed her status from Lunch to In a meeting", your app has to store the last status it saw for each user and compare.
Never appear "away" on Slack again
Cloud-based. No downloads. Works 24/7 even when your laptop is off.
Expiring statuses send events too
We set a status to expire in 90 seconds, then in a second run in 180 seconds, and checked users.profile.get every 5 to 10 seconds while the app listened:
| Expiry set | Status cleared | Events |
|---|---|---|
| 90 s from now | about 23 s early | user_status_changed and user_change, at the moment it cleared |
| 180 s from now | about 32 s early | user_status_changed and user_change, at the moment it cleared |
So an expiry is a normal status change for an app: both events fire, with an empty status_text. Slack cleared both statuses before the time we set, which matches what we saw on longer durations when we tested why Slack keeps clearing a status.
The listener we ran
This is the core of our test app, a Python Socket Mode client with slack_sdk. It needs an app-level token with connections:write and a bot token with users:read. The manifest subscribes the bot to user_change and user_status_changed:
import json, os, time
from slack_sdk.socket_mode import SocketModeClient
from slack_sdk.socket_mode.response import SocketModeResponse
from slack_sdk.web import WebClient
def handler(client, req):
client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id))
if req.type != "events_api":
return
event = req.payload["event"]
if event["type"] == "user_status_changed":
p = event["user"]["profile"]
print(time.time(), event["user"]["id"], p["status_emoji"], p["status_text"],
p["status_expiration"])
client = SocketModeClient(app_token=os.environ["SLACK_APP_TOKEN"],
web_client=WebClient(token=os.environ["SLACK_BOT_TOKEN"]))
client.socket_mode_request_listeners.append(handler)
client.connect()
while True:
time.sleep(5)
The setup steps for a Socket Mode app and its token errors are on our Socket Mode in Python page. Setting a status from code is on set a Slack status with the API.
Polling instead of events
Without an event subscription, you can poll users.profile.get for each person. During our expiry runs we polled every 5 to 10 seconds, and the poll first showed the empty status 4 and 7 seconds after the event, which was simply the wait for the next poll. Polling once a minute would catch a change up to a minute late, and it costs one call per person per minute, while the event costs nothing per person. Use polling only when you cannot install an app with events.
If what you want is a ping when a coworker comes online rather than when their status text changes, that is a presence question with different API calls. We covered it on get notified when someone comes online.
Stop Jiggling Your Mouse.
Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.
Related Articles
@slack/web-api v8: A Tested Node.js and TypeScript Example
@slack/web-api 8.2.0 runs on Node 20 or newer and sends requests with fetch. We posted, paginated, caught a Slack error and a rate limit in TypeScript, and found that the v7 agent option for proxies is silently ignored in v8.
Slack API in Go With slack-go/slack: A Tested Example
slack-go/slack v0.29.0 is the Go client most people use for the Slack API. We ran a program that posts, reads back and updates a Block Kit message, a Socket Mode bot that answers a mention, and a loop that hit the rate limit, with the output and error types we got.
slack-ruby-client: A Tested Ruby Example, Errors and Limits
slack-ruby-client 3.2.0 is the Ruby gem for the Slack Web API. We installed it, posted, read back, reacted and paginated against a real workspace, and recorded the error classes it raised, including the rate-limit error that a rescue of SlackError does not catch.