A few years ago I sold all my stuff to explore the world, creating 12 startups in 12 months and building $1M+/y companies as an indie maker such as Nomad List and Remote OK. I'm also a big pusher of remote work and async and analyze the effects it has on society. Follow me on Twitter or see my list of posts. My first book MAKE is out now. Contact me
Subscribing you...
Subscribed! Check your inbox to confirm your email.
levels.io

How I hacked Slack into a community platform with Typeform

12 Startups in 12 Months, Entrepreneurship, Nomad List, Tutorials
Nov 23, 2014

In the last few weeks I built a Slack community around digital nomads called #nomads. It now has over 1,250 members that talk to each other daily. Slack was originally meant as a team communication platform, but it functions surprisingly well at large-scale as a community platform. Slack doesn’t allow people to sign up directly though. The team’s admin needs to invite people manually. Today, I’ll show how I combined a few services to fix that and transform Slack into a community platform.

Accepting new sign ups

As Slack doesn’t support this out of the box, we need to make a way for people to sign up. Luckily Typeform is perfect for this. Users sign up by clicking Join Us on #nomads‘s main page:

That button links them to this form. Typeform saves all the sign ups in an spreadsheet you can download.

Inviting them to Slack

Until now every day I had to go sign in to Typeform, download all the sign ups and then go to Slack and copy-paste them into the invites box:

This becomes a hassle though and I started getting tweets of people that weren’t invited when I forgot to do it some days. I can’t be on the computer all day, guys/girls! 🙂

@nomadlist_ hey guys. I recently signed up for the slack channel. I haven't received a confirmation yet. My email is damirkotoric@gmail.com

— Damir Kotorić (@damirkotoric) November 19, 2014

Let’s automate it

I would have used Zapier for this, and although it appears to have a Typeform & Slack integration, it’s too limited to be of any use here. So let’s do it ourselves with a simple script.

Getting new signups from Typeform

First we have to make an account on Typeform (yes, that’s a referral link). Typeform has a Data API which allows you to get the contents of your forms in JSON with this url:

https://api.typeform.com/v0/form/FormUID?key=ApiKey&completed=true&offset=0

The &completed=true means you only want results from the form that are actually 100% complete. The &offset is there since the API limits requests to 1,000 responses. So if you have over 1,000 emails signed up, it will only show the first 1,000. So we need to somehow paginate that later.

Your Typeform form ID is not the one you find in the URL when you edit it in the admin panel:

https://admin.typeform.com/form/197596/fields/

It is the ID you find when users open your form though:

https://nomadlist.typeform.com/to/afaUYO

So the API URL becomes:

https://api.typeform.com/v0/form/afaUYO?key=5de631f0dd3&completed=true';

Inviting new sign ups on Slack

This is a bit more difficult. Slack has a great API, but if you search for “invite”, you only find these two methods:


There’s channels.invite and groups.invite, but we need a team.invite method. It’s not there.

So it ends here? No. Let’s sniff the POST data when we invite people on Slack’s web interface:

It posts data to this URL:

https://hashtagnomads.slack.com/api/users.admin.invite?t=1416723927

So users.admin.invite is an undocumented method. The ?t= is a simple epoch or unix time. The POST data is here:

email:example@example.com channels:C02RWGV3X,C02S05WJA,C02SU0WLE,C02S2B5CH,C02RVB0CK,C02SPEMBY first_name:Example token:xoxs-255168432 set_active:true _attempts:1

Most of this is obvious. How the hell was I going to get a token though? Since this wasn’t the API anymore, it was the web interface. Right?

I tried using the API token anyway. You can generate a token for your team on the Slack API page:

I posted it and it returned:

Yay! To my sheer surprise, this worked 🙂

Coding it up

Now let’s make into a script that runs regularly to keep inviting new sign ups. Sorry it’s PHP, I know. Life happens.

First specify the config vars (don’t worry I faked all the API keys and tokens in this post, you can stop tweeting me now haha!)

// date_default_timezone_set('America/Los_Angeles'); mb_internal_encoding("UTF-8"); $typeformApiKey='5de631f0dd3'; $typeformFormId='afaUYO'; $typeformEmailField='textfield_2133129'; $typeformNameField='textfield_2133430'; $previouslyInvitedEmailsFile=DIR.'/previouslyInvitedEmails.json'; // your slack team/host name $slackHostName='hashtagnomads'; // find this when checking the post at https://nomadslack.slack.com/admin/invites/full $slackAutoJoinChannels='C02RWGV3X,C02S05WJA,C02SU0WLE,C02S2B5CH,C02RVB0CK,C02SPEMBY'; // generate token at https://api.slack.com/ $slackAuthToken='xoxp-2551684328'; //

Your Typeform API key ($typeformApiKey) can be found here:

I mentioned above how to find your Typeform form ID ($typeformFormId).

The rest is kinda obvious, $slackAuthToken is your API token. $slackAutoJoinChannels you can sniff from the POST call when you invite users through the web interface. That’s the channels the ne user is invited into automatically. Remember if you remove a channel from Slack, but it will still be here, the invite will fail with “Error: channel_not_found”.

For the email and name field ID you need to do an API call first though:

The textfield_xxxxxxx value is the ID of the respective text field in your Typeform.

We use a JSON text file to keep track of who we have invited already, to avoid useless API calls to Slack. This filename is specified in $previouslyInvitedEmailsFile. The ?offset= for the Typeform API uses the $previouslyInvitedEmails count and lets you only requests the new sign ups.

We continue.

// if(@!file_get_contents($previouslyInvitedEmailsFile)) { $previouslyInvitedEmails=array(); } else { $previouslyInvitedEmails=json_decode(file_get_contents($previouslyInvitedEmailsFile),true); } $offset=count($previouslyInvitedEmails); $typeformApiUrl='https://api.typeform.com/v0/form/'.$typeformFormId.'?key='.$typeformApiKey.'&completed=true&offset='.$offset; if(!$typeformApiResponse=file_get_contents($typeformApiUrl)) { echo "Sorry, can't access API"; exit; } $typeformData=json_decode($typeformApiResponse,true); $usersToInvite=array(); foreach($typeformData['responses'] as $response) { $user['email']=$response['answers'][$typeformEmailField]; $user['name']=$response['answers'][$typeformNameField]; if(!in_array($user['email'],$previouslyInvitedEmails)) { array_push($usersToInvite,$user); } } // typeform emails>

We go through Typeform API’s response to see which emails we already invited before and which are new. We save the new ones in $userToInvite.

Then we hit Slack’s API:

// $slackInviteUrl='https://'.$slackHostName.'.slack.com/api/users.admin.invite?t='.time(); $i=1; foreach($usersToInvite as $user) { echo date('c').' - '.$i.' - '.""".$user['name']."" - Inviting to ".$slackHostName." Slack\n"; // $fields = array( 'email' => urlencode($user['email']), 'channels' => urlencode($slackAutoJoinChannels), 'first_name' => urlencode($user['name']), 'token' => $slackAuthToken, 'set_active' => urlencode('true'), '_attempts' => '1' ); // url-ify the data for the POST $fields_string=''; foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; } rtrim($fields_string, '&'); // open connection $ch = curl_init(); // set the url, number of POST vars, POST data curl_setopt($ch,CURLOPT_URL, $slackInviteUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch,CURLOPT_POST, count($fields)); curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string); // exec $replyRaw = curl_exec($ch); $reply=json_decode($replyRaw,true); if($reply['ok']==false) { echo date('c').' - '.$i.' - '.""".$user['name']."" - ".'Error: '.$reply['error']."\n"; } else { echo date('c').' - '.$i.' - '.""".$user['name']."" - ".'Invited successfully'."\n"; } // close connection curl_close($ch); array_push($previouslyInvitedEmails,$user['email']); // $i++; } // to slack>

The $fields array is an exact replica of the POST call Slack’s web interface made before. The echo calls are simply to log everything and show me what’s going on. If Slack’s API replies ok=true, an invite has been successful. After each invite, we add the email to $previouslyInvitedEmails which we then save:

file_put_contents($previouslyInvitedEmailsFile,json_encode($previouslyInvitedEmails));

I save this script as autoInviteFromTypeformToSlackWorker.php.

Run it

This is the output:

2014-11-23T08:14:19+00:00 - "Austin" - Inviting to hashtagnomads Slack 2014-11-23T08:14:21+00:00 - "Austin" - Invited to hashtagnomads

Scheduling the script

We want this script to run regularly to invite users as fast as possible.

So I set a cron job to run every 30 minutes (*/30). And save the output to a text file to log.

*/30 * * * * php -f /srv/http/hashtagnomads.com/workers/autoInviteFromTypeformToSlackWorker.php >> /srv/http/hashtagnomads.com/logs/autoInviteFromTypeformToSlackWorker.txt

Conclusion

That’s it! It seems obvious and it is. But this small thing transforms Slack from a team chat into a community platform. Slack is awesome and it’s already replacing email, hopefully now it can also replace horrible bulletin boards and help communities thrive, starting with #nomads

Oh and, sorry for the PHP. I promise, Node.JS, one day… ^o^

P.S. I'm on Twitter too if you'd like to follow more of my stories. And I wrote a book called MAKE about building startups without funding. See a list of my stories or contact me. To get an alert when I write a new blog post, you can subscribe below:

Subscribing you...
Subscribed! Check your inbox to confirm your email.

2022
18 Sep
This House Does Not Exist
2022
14 Jul
Sam Parr + Shaan Puri asked me about bootstrapping, open startups and lifestyle inflation (My First Million Podcast)
2022
16 May
Thinking and doing for yourself (Life Done Differently Podcast)
2022
10 May
Relocation of remote workers (Building Remotely Podcast)
2022
26 Jan
Money, happiness and productivity as a solo founder (Indiehackers Podcast)
2022
20 Jan
Bootstrapping, moving to Portugal and setting up Rebase (Wannabe Entrepreneur Podcast)
2021
25 Mar
Why I'm unreachable and maybe you should be too
2021
25 Mar
The next frontier after remote work is async
2021
19 Mar
List of all my projects ever
2021
08 Mar
Why coliving economics still don't make sense
2021
14 Feb
Inflation Chart: the stock market adjusted for the US-dollar money supply
2021
10 Jan
I did a live 4+ hour AMA on Twitch w/ @roxkstar74
2020
20 Dec
No one should ever work
2020
10 Dec
Normalization of non-deviance
2020
05 Dec
Copywriting for entrepreneurs: explain your product how you'd explain it to a friend
2020
30 Nov
Entrepreneurs are the heroes, not the villains
2020
12 Nov
The future of remote work: how the greatest human migration in history will happen in the next ten years
2020
05 Nov
Will millions of remote workers become location independent in 2021?
2020
11 Apr
5 years in startups with Abadesi
2020
11 Jan
Twitter giveaways can be hacked to win every time
2019
16 Oct
Lorn - The Slow Blade ✕ Hong Kong
2019
28 Sep
Most decaf coffee is made from paint stripper
2019
12 Sep
The odds of getting a remote job are less than 1% (because everyone wants one)
2019
08 Sep
In the future writing actual code will be like using a pro DSLR camera, and no code will be like using a smartphone camera
2019
29 Aug
Instead of hiring people, do things yourself to stay relevant
2019
28 Aug
Nobody cares about you after you're dead and the universe destroys itself
2019
28 Aug
The only real validation is people paying for your product
2019
05 Aug
Monitoring Bali's undersea internet cable
2019
29 Jul
Nomad List turns 5
2018
29 Jan
I'm Product Hunt's Maker of the Year again!
2018
28 Jan
Why Korean Jimjilbangs and Japanese Onsens are great
2018
24 Jan
Turning side projects into profitable startups
2018
03 Jan
What I learnt from 100 days of shipping
2017
28 Dec
As decentralized as cryptocurrency is: so will be the people working on it
2017
22 Oct
How to 3d scan any object with just your phone's camera
2017
09 Aug
In a world of outrage, mute words
2017
03 Aug
How to pack for world travel with just a carry-on bag
2017
26 Jul
Building a startup in public: from first line of code to frontpage of Reddit
2017
24 Jul
Facebook and Google are building their own cities: the inevitable future of private tech worker towns
2017
21 Jul
The TL;DR MBA
2017
12 Jul
We did it! Namecheap has introduced 2FA
2017
08 Jun
It's about time for a digital work permit for remote workers
2017
23 May
Using Uptime Robot to build unit tests for the web
2017
08 May
Namecheap still doesn't support 2FA in 2017 (update: they do now!)
2017
03 May
Taipei is boring, and maybe that's not such a bad thing
2017
16 Apr
What we can learn from Stormzy about transparency
2017
17 Feb
The ICANN mafia has taken my site hostage for 2 days now
2017
10 Feb
Most coworking spaces don't make money; here's how they can adapt to survive the future
2017
11 Jan
A society of total automation in which the need to work is replaced with a nomadic life of creative play
2017
07 Jan
Nomad List Founder
2016
12 Dec
Make your own Olark feedback form without Olark
2016
29 Oct
How to fix flying
2016
19 Oct
Robots make mistakes too: How to log your server with push notifications straight to your phone
2016
17 Oct
Hong Kong Express - 上海 (Shanghai)
2016
17 Oct
Choosing entrepreneurship over a corporate career
2016
13 Oct
"I can't buy happiness anymore. I've bought everything that I ever wanted. There's not really anything I want anymore."
2016
11 Oct
From web dev to VR: How to get started with VR development
2016
05 Oct
What I would do if I was 18 now
2016
22 Sep
Bootstrapping Side Projects into Profitable Startups
2016
27 Aug
Kids
2016
13 Aug
How I cured my anxiety (mostly)
2016
26 Jul
We have an epidemic of bad posture
2016
17 Jul
Fixing "Inf and NaN cannot be JSON encoded" in PHP the easy way
2016
26 Jun
My third time in a float tank and practicing visualizing the future
2016
15 Jun
How to add shareable pictures to your website with some PhantomJS magic
2016
29 May
My chatbot gets catcalled
2016
19 May
From web dev to 3d: Learning 3d modeling in a month
2016
09 Mar
My second time in a sensory deprivation chamber
2016
04 Mar
Day 30 of Learning 3d 🎮 Cloning objects 👾👾👾
2016
02 Mar
Day 29 of Learning 3d 🎮 Glass, reflectives, HD, coloring and more details
2016
29 Feb
Day 27 of Learning 3d 🎮 Details, details, DETAILS!
2016
25 Feb
Day 23 of Learning 3d 🎮 Filling up the street and adding shadows
2016
24 Feb
Day 22 of Learning 3d 🎮 Added rain, blinking lights, sound, textured menu sign and a VR web app
2016
23 Feb
Day 21 of Learning 3d 🎮 High res textures, physical rendering and ambient occlusion
2016
22 Feb
Day 20 of Learning 3d 🎮 Objects and camera perspectives 🙆
2016
19 Feb
My first time floating in a sensory deprivation tank ☺️
2016
12 Feb
Day 10 of Learning 3d 🎮 Making complex objects by combining shapes 🙆
2016
06 Feb
Day 4 of Learning 3d: @shoinwolfe visits the actual street I'm modeling 🏮😎🏮
2016
03 Feb
Day 1 of Learning 3d 🎮 I learnt how to make shapes, move, rotate and scale them + how to texturize, and add colored lights 💆
2016
02 Feb
I'm Learning 3d 🎮
2016
27 Jan
The things I have to do to read an email sent to me by my government
2016
12 Jan
How to use your iPhone as a better Apple TV alternative (with VPN)
2015
23 Dec
Here's a crazy idea: automatically pause recurring subscription of users when you detect they aren't actually using your app
2015
17 Dec
Stop calling night owls lazy, we're not
2015
16 Dec
We are the heroes of our own stories
2015
25 Oct
There will be 1 billion digital nomads by 2035
2015
21 Oct
Tobias van Schneider interviewed me about everything
2015
18 Oct
Why doesn't Twitter just asks its users to pay?
2015
17 Oct
Punk died the moment we learnt that the world WAS in fact getting better, not worse
2015
15 Oct
Stop being everyone's friend
2015
14 Oct
Vaporwave is the only music that fits the feeling futuristic Asian mega cities give me
2015
09 Sep
We live in a world built by dead people
2015
01 Sep
Why global roaming data solutions don't make any sense
2015
26 Aug
How to export your Slack's entire archive as HTML message logs
2015
24 Aug
How to play GTA V on your MacBook (and any other PC game)
2015
14 May
I uploaded 4 terabyte over Korea's 4G, and paid $48
2015
08 May
How I sped up Nomad List by 31% with SPDY, CloudFront and PageSpeed
2015
04 May
My weird code commenting style based on HTML tags
2015
01 May
Now is probably the time to make HTTPS the default on all your sites and apps
2015
17 Apr
Do the economics of remote work retreats make any sense?