Slack Web API 429 ratelimited on chat.postMessage: strict Retry-After versus exponential backoff with jitter
0 reputation · 28 Mar 2020, 14:15 UTC
0 reputation · 28 Mar 2020, 14:15 UTC
Slack Web API throttling surfaces as HTTP 429 Too Many Requests with a JSON body whose error field is "ratelimited". The limit applies per method and per app/workspace/token context, so a client can remain healthy on one endpoint while another such as chat.postMessage begins returning 429. Slack does not publish exact per-method budgets or burst allowances, and published guidance can change, so safe concurrency remains an empirical measurement rather than a documented constant.
When a 429 arrives, Slack returns a Retry-After header in seconds. The documented client behavior is to wait at least that duration before retrying. However, the choice between strictly honoring Retry-After and using exponential backoff with jitter remains unresolved: strict waiting can stall a pipeline on one hot method, while jittered aggressive retries may prolong the throttle window. SDKs may also auto-retry 429 responses, which can multiply requests if a separate retry loop is layered on top.
For a production Slack app that has already confirmed the 429 body shape and Retry-After semantics in a test workspace, what evidence should determine whether strict Retry-After or backoff-with-jitter is appropriate for a specific method? How should the retry policy account for undisclosed per-method burst allowances when two workspaces exhibit different 429 thresholds under identical traffic? Is there a documented way to distinguish Web API rate limiting from Events API delivery retries when both manifest as delayed message processing?
For the Slack Web API, you should strictly honor the Retry-After header as your primary mechanism. Unlike generic APIs where exponential backoff is the standard, Slack explicitly provides the wait duration required to clear the throttle. Retrying before the Retry-After duration expires can prolong the throttle window or lead to consecutive 429 responses.
The decision between these two is not an "either/or" choice, but rather a layering strategy. Use the following evidence to determine your approach:
Retry-After period prevents this "thundering herd" effect.Because Slack does not publish per-method budgets and limits vary by workspace, you cannot use a global constant for concurrency. Instead, implement adaptive client-side throttling:
(Method, WorkspaceID, Token) tuple.These two behaviors occur in opposite directions and can be distinguished by their logs:
| Feature | Web API Rate Limiting | Events API Delivery Retries |
|---|---|---|
| Direction | Outbound (Your App $\rightarrow$ Slack) | Inbound (Slack $\rightarrow$ Your App) |
| Signal | HTTP 429 Response + Retry-After |
Duplicate Event payloads with same event_id |
| Control | Controlled by your retry logic | Controlled by Slack's internal retry schedule |
To verify your implementation, log the following for every 429: Method, WorkspaceID, Retry-After value, Request Timestamp, and Actual Retry Timestamp. If the difference between the request and retry is less than the Retry-After value, your logic is flawed.
Critical Warning: Check if your SDK (e.g., Bolt) has built-in 429 auto-retry enabled. If you wrap an auto-retrying SDK call in your own retry loop, you will multiply the number of requests, significantly worsening the throttle.
Missing Diagnostic: Are you using a Slack SDK with auto-retry enabled, or are you making raw HTTP requests?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.