Harish Kumar
war-storygaparchitect-mindset

Building a Serverless Slack Bot Framework Without a Slack App — Google Apps Script + Reverse-Engineered Slack API

Built a Slack bot without a Slack App — reverse-engineering the internal browser API and routing through Google Apps Script to ship in a day instead of waiting weeks for IT approval.

June 1, 20248 min

The request came in through unofficial channels: can we get Slack notifications for the team leaderboard? Nothing formal. No ticket. No initiative. Just a product need that engineering had the capacity to solve.

The obvious path: register a Slack App. Write the OAuth flow. Get it approved by IT. Wait for security review. Wait for admin sign-off. Write the integration. Ship it.

That process takes weeks in an enterprise. Sometimes months. The need would be forgotten by then, and so would whatever energy existed to solve it.

So I asked a different question.

What I considered

  • Register a Slack App — the documented path. OAuth scopes, security review, IT admin approval. Weeks minimum, sometimes months. The idea dies in the queue.
  • Incoming Webhooks — simpler than a full Slack App but still requires a Slack admin to create the webhook URL. Same approval dependency, smaller surface area.
  • Lightweight server-side bot — run a small Node server, handle Slack events, post messages. Works, but requires cloud infrastructure: a server to provision, a deploy pipeline, ongoing cost to justify. Too much overhead for internal tooling with no budget.
  • Google Apps Script + Slack's internal API — no server, no provisioning, no approval. GAS runs serverless in Google's infrastructure, free tier, timed triggers built-in. Slack's internal API is observable from DevTools. If the browser can post messages, so can a script.

The actual problem

I wanted to build two things:

  1. A team leaderboard — tracking developer contributions to internal knowledge bases (ops books, playbooks, runbooks). Visibility as incentive. Show people who's contributing, so more people contribute.

  2. A JIRA cycle time estimator with proactive Slack notifications — something that pulls ticket data, estimates cycle time per team, and surfaces bottlenecks before they become blockers. Not a dashboard you have to remember to open. Something that pings you.

Both of these need to post to Slack. That's the whole point — the insight has to reach people where they already are.

Getting a Slack App approved at a company like BrowserStack isn't impossible. It's just slow. It requires a formal request, a security review of the OAuth scopes requested, admin approval. The idea goes into a queue. The queue is not organized by urgency or impact — it's organized by whoever is managing it that week.

That's not a technical problem. That's an organizational one.


How Slack actually sends a message

Here's the thing about Slack: it's a web app. When you type a message and hit Enter, your browser makes an HTTP request.

That HTTP request is observable.

Open Slack in your browser. Open DevTools. Go to the Network tab. Send a message to any channel. Watch what happens.

There it is — Slack's internal API endpoint, the exact payload format, the authentication token from your logged-in session. Everything the Slack web client uses to post a message is visible, copy-pasteable, and replicable.

The "you need a Slack App" constraint comes from Slack's intended API surface — the public Bot API they document and support and want you to use. That API requires app registration, OAuth, admin approval. It's designed for integrations that external developers publish.

The browser API has none of those requirements. It exists because the browser needs to post messages too.

Once you see that, the path forward is clear: replicate the browser calls. Use your own session token as authentication. Post messages as your personal account. No app registration. No OAuth. No admin required.


The reverse engineering process

The DevTools approach worked cleanly. The steps:

  1. Open Slack web in Chrome or Firefox
  2. Open DevTools → Network tab
  3. Send a message in any channel
  4. Filter requests by XHR / Fetch
  5. Find the API call that fired on send — look for a chat.postMessage-style endpoint in the internal API path
  6. Inspect the request: URL, headers, payload structure
  7. Copy the session token from the Authorization header

The internal API uses a different token format than the Bot API. It's your browser session token — the one Slack issues when you log in. This token is tied to your account, not to a registered application.

Replicating the call from Google Apps Script is straightforward: UrlFetchApp.fetch(endpoint, options) with the same headers and payload. The message posts. It comes from your personal account, not a named bot. But it posts.

That's the whole trick.


The serverless stack

Once the Slack messaging question was solved, I had to decide on the rest of the infrastructure. The constraints were self-imposed and strict: no deployment pipeline, no cloud account provisioning, no database setup, no ongoing cost. This was a side project building internal tooling — not a product with budget.

The stack I landed on:

Google Apps Script — serverless compute, free tier, runs in Google's infrastructure. Supports timed triggers (run this script every Monday at 9am). No server to maintain, no Docker image, no CI/CD. Write the script, set the trigger, it runs.

Google Sheets — rows as records, sheets as tables. No database needed. For the use cases here — leaderboard rankings, JIRA ticket metadata, cycle time calculations — a spreadsheet is sufficient. It's queryable from Apps Script natively, it's auditable, and anyone on the team can open it and read it.

Slack internal API — the reverse-engineered endpoint. Messages post from my account. Good enough for internal tooling.

The combination gives you a fully operational reporting pipeline: data collection from external sources, computation and storage in Sheets, delivery via Slack — all without registering anything with anyone.


What got built on top of it

Team leaderboard

The internal knowledge base at BrowserStack lives in various places — ops books, playbooks, runbooks, team wikis. Contribution is uneven, which is normal. Most people don't write documentation by default. They need a reason.

Visibility is a reason.

The leaderboard pulls contribution data, ranks team members by activity over a rolling window, and posts the current standings to Slack on a set schedule. Not a one-off report — a recurring push. The kind of thing you see every Monday and start thinking about.

The goal wasn't gamification in the pejorative sense. It was signal. If you're contributing, people see it. If you haven't contributed in three weeks, you see that too.

JIRA cycle time estimator with proactive notifications

Cycle time is the time a ticket spends in a given state — in-progress, in-review, in-QA. When a ticket sits in one state too long, it's usually a signal: blocked, forgotten, or scope-creeping.

Most teams learn this retrospectively. Stand-ups catch some of it. Retrospectives catch the rest. But by then the sprint is over and the information is historical.

The cycle time estimator pulls JIRA data, calculates time-in-state per ticket and per team, identifies outliers, and posts them proactively. Before the sprint ends. Before the ticket becomes a problem. The message goes out: "This ticket has been in review for 6 days — expected cycle time for this type is 2 days."

That's actionable. That's the kind of thing a well-run team already knows but doesn't always have the tooling to surface automatically.


What this is really about

The framework — GAS + Sheets + reverse-engineered Slack API — is reproducible. That was the design intent. Any new internal reporting use case plugs into the same pattern: define the data source, write the computation logic, configure the Slack message format, set the trigger. Two use cases in production now. More could follow without rebuilding anything.

But the more interesting part is the decision that made the framework possible at all.

Enterprise environments come with layers of approval, process, and organizational overhead. Some of it exists for good reasons — security reviews matter, audit trails matter, access control matters. Some of it is inertia. Some of it is just queue backlog.

When you hit a bureaucratic constraint, the first-instinct question is usually: "How do I get through this process?" That's reasonable. But it's not always the right question.

The better question is often: "Why does this constraint exist, and does it actually apply here?"

Getting a Slack App approved exists to control external integrations that have broad access to workspace data. An internal tooling script that posts messages to a channel from a personal account is not that threat model. The constraint exists for a different class of problem.

Understanding the actual technical mechanism — Slack is making HTTP calls, those calls are observable, those calls are replicable — let me route around a process that wasn't designed for this use case. The result was a working framework in hours instead of weeks.

First principles isn't just a thinking style. It's a shortcut to the part where things actually work.


Where this breaks

Worth naming the failure modes honestly.

The session token is tied to an active browser login. If the token expires, message delivery stops silently — no error surface, no retry, just a stopped report. Slack's internal API has no stability contract: they can change endpoints, payload shapes, or auth mechanisms between web client releases without documenting it, because it's not a public API. Silent breakage is the real risk — the script stops working and you find out when someone notices the Monday report didn't arrive.

This approach is defensible for internal tooling used by a small team, where a broken report triggers a quick fix and not a production incident. At the point where this becomes a reliability-critical workflow, a proper Slack App is the right path. The constraint then is real, and the bureaucratic process exists for legitimate reasons. The reverse-engineering route is a pragmatic shortcut, not a permanent architecture.


The leaderboard is running. The JIRA cycle time notifications are live. The framework exists for the next thing that needs it.

And no Slack App was registered.


Recognition

Darsan Tatineni recognizing Harish for the cycle time estimator and ops leaderboard notifications