Slappyhour – A love letter to Slack's webhooks

Spoiler alert: This blog post isn’t about keyboards. – Jesse

#slappyhour example for blog post

Keyboardio is a very small company, but we all come from a culture of using online chat to get business done. We’ve used everything from MIT Zephyr to IRC to Lotus SameTime. (Heck, I even worked on early IETF standardization efforts for chat systems.) When we started Keyboardio, it was pretty much a nobrainer that we’d need some sort of chat system.

We surveyed the field. In the end, it was down to Zulip and Slack. And then Dropbox bought and sunsetted Zulip. Our choice was easy. We’ve been incredibly happy with Slack. But there was one thing we were missing: the online community that larger organizations develop. We could wait until we’re a larger organization but we’re both a little impatient.

We’ve got a ton of friends who are also using Slack at work. It seemed like it’d be neat if we could set up a shared channel between all our friends’ companies.

After a bit of hemming and hawing, I finally sat down in September to see how hard it would be to write a tool to bridge a channel across multiple Slack instances. I spent a little bit of time poking at a variety of hip, modern web development platforms before stepping back to properly consider the problem at hand. After a brief perusal of the Slack API as well as Slack’s webhooks implementation, I realized that pretty much everything I hoped to do would be easy to achieve with, essentially, some webhooks and a for loop.

I needed a simple, stateless way to accept an incoming HTTPS request, massage the data and send out some new HTTPS requests of its own. I didn’t need cookies, OAuth, streaming data, asynchronous anything or even the tiniest bit of UI. In less time than it’d have taken me to get this month’s flavor of node.js dev stack up and running, I built working Slack-to-Slack gateway.

As a CGI script. It was 14 lines of code. Sometimes the old tools aren’t wrong just because they’re old.

Presented below is a fully-working, if somewhat light on features and error-handling, version of Slappyhour. It’s intended only to illustrate just how easy Slack makes it to build integrations. Please don’t use this version. If you’re going to set up a Slappyhour bridge, use the version on GitHub. It has a bunch more features and is much, much better code. Please send pull requests and bug reports.

We are, of course, not the first folks to think of something like this. If you’d rather just use a commercial service that’s somewhat more featureful than Slappyhour, you should check out It’s a third-party service that’s not affiliated with us or Slack, but they totally did the Slack-to-Slack thing first. They’ve also got an opensource version in go, but it’s really only set up to bridge a channel between two Slack instances.

Oh, about the name. It’s a reference to what Slack called the happy hour they held at XOXO 2014.

use CGI;
use JSON;
use LWP::UserAgent;

my $peers = { asdf => '__INCOMING_WEBHOOK_TOKEN_URL' };
my $q  = CGI->new();
my $ua = LWP::UserAgent->new();

print "Content-type: text/plain\n\n";
die "Proxying bots == infinite loop"
if ($q->param('user_id') =~ /slackbot/i); $ua->post($peers->{$_}, Content => encode_json({
username => $q->param('user_name')."@".$_,
text => $q->param('text') }))
for grep {$_ ne $q->param('team_domain')} keys %$peers;

Disclosure: So yeah, we <3 Slack. If you sign up for Slack by clicking through any of the links on this blog post, we’ll get $100 in service credit and so will you.

Back to blog