One of the great applications for Serverless is using it as glue code between different services. You can spin up an endpoint to handle a webhook in seconds without bugging your company's Ops department.
Github has a very mature webhook integration where you can be notified of a wide range of events. You can run a linter when a pull request is opened, send a notification when an issue is created, or trigger a deploy when a pull request is merged.
In this tutorial, we'll show how to handle Github webhooks. We'll create a webhook that fires whenever our open-source repository is starred. Our handler for this event will post a celebratory message in a Slack channel.
The end result will look like this:
Let's get started!
Before you start
To complete this tutorial, you'll need:
- The Serverless Framework installed with an AWS account set up;
- A Github account, plus a repo where you have admin or owner permissions; and
- A Slack account where you have the ability to create apps.
Setting up your Slack incoming webhook
The first thing we'll do is set up an Incoming Webhook for a Slack channel. This will give us an HTTP endpoint to post messages that will be displayed in our Slack channel.
First, create a new channel in Slack where you want the messages to go. You probably don't want to spam your whole team in #general
. 😬
In your new channel, click the link to Add an app
:
Search for the "Incoming Webhook" application, and create a new one for your channel:
After you create it, it will display a Slack webhook URL. This is the URL where you will post data to show in the channel:
Save this URL as you will need it in your Serverless function.
Finally, you can customize the webhook display so it's nicer when it posts in the channel. Here, I change the name to "Github Stars" and use a star emoji as the icon:
Deploying our Serverless webhook handler
Now, let's move on to setting up our webhook handler. In our handler, we'll want to parse the given event for the information we want, then send a formatted message to Slack using our webhook URL from the previous step.
First, let's create a new directory for our Serverless service and initialize it with a package.json
:
$ mkdir stargazer
$ cd stargazer
$ npm init -y
We're going to be using the WatchEvent
from Github to get notifications of our repository being starred. We want to post a message that looks as follows:
For this message, we'll need:
- the repository being starred;
- the total number of stars for the repository;
- the username starring our repository; and
- the URL to the user's Github profile.
Github includes an example event structure, which is very useful. A truncated version is below:
{
"action": "started",
"repository": {
"id": 35129377,
"name": "public-repo",
"full_name": "baxterthehacker/public-repo",
...
"stargazers_count": 0,
...
},
"sender": {
"login": "baxterthehacker",
"id": 6752317,
"avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/baxterthehacker",
"html_url": "https://github.com/baxterthehacker",
...
}
}
In our service directory, let's create a handler.js
with our handler code:
'use strict';
const request = require('sync-request');
const WEBHOOK_URL = process.env.WEBHOOK_URL;
module.exports.stargazer = (event, context, callback) => {
const body = JSON.parse(event.body)
const { repository, sender } = body;
const repo = repository.name;
const stars = repository.stargazers_count;
const username = sender.login;
const url = sender.html_url;
try {
sendToSlack(repo, stars, username, url);
} catch (err) {
console.log(err);
callback(err);
}
const response = {
statusCode: 200,
body: JSON.stringify({
message: "Event processed"
}),
};
callback(null, response);
};
const sendToSlack = (repo, stars, username, url) => {
const text = [
`New Github star for _${repo}_ repo!.`,
`The *${repo}* repo now has *${stars}* stars! :tada:.`,
`Your new fan is <${url}|${username}>`
].join('\n');
const resp = request('POST', WEBHOOK_URL, {
json: { text }
});
// Use getBody to check if there was an error.
resp.getBody();
}
Let's walk through this handler code.
The exported stargazer
function is the handler for our Lambda. This is what will be called when our function is triggered. In that function, we pull out the necessary elements of the webhook event, then use our sendToSlack
function to assemble the message and post it to Slack.
We should look at two other things before moving on. First, notice how we're getting the webhook URL from a WEBHOOK_URL environment variable. This is something we'll need to inject with our serverless.yml
.
Second, we're using a third-party NPM package, sync-request
(yes, I use sync-request because I don't like messing with callbacks 😬). We'll need to install this package locally with NPM, and then Serverless will include it in our deployment package. Let's install that now:
$ npm install --save sync-request
With our handler code written, let's move on to our serverless.yml
. This is our "infrastructure-as-code", where we configure our different functions, events, and additional configuration:
# serverless.yml
service: stargazing
provider:
name: aws
runtime: nodejs6.10
environment:
WEBHOOK_URL: "https://hooks.slack.com/services/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"
functions:
stargazer:
handler: handler.stargazer
events:
- http: POST stargazer
In our functions
block, we configure a single function, stargazer
. We provide a path to our handler file and the name of the function to be triggered. Then, we set up an http event to be triggered on a POST request to /stargazer
.
Notice in the provider
block that we've added the WEBHOOK_URL
environment variable under the environment
section. This matches with our handler code, which required our Slack webhook URL in the environment. Make sure to update that value with your URL from setting up your webhook.
Let's deploy our service! Run sls deploy
to send it to the cloud ⚡️:
Serverless will print out a URL for your function to be accessed. Copy that URL as you will use it to configure your Github webhook.
Setting up your Github webhook
We're ready for the last step -- creating the Github webhook to send to our function endpoint.
Navigate to a repository where you're an owner or an admin. Click "Settings" at the top, then the "Webhooks" tab on the left hand side. Then, click the "Add webhook button":
In the Add webhook screen, enter your function endpoint into the Payload URL input box and choose application/json
as the Content Type:
Then, go to the section to choose which events trigger the webhook. Click the "Let me select individual events" option, then choose the "Watch" event at the bottom of the list.
Click the "Add webhook" button, and you're ready to go! Github will immediately send a test event to your endpoint. There will be a section showing "Recent Deliveries" and the status code, so you can see if you're having any failures. If you are, check the logs in your console with sls logs -f stargazer
to find the errors.
Conclusion
You did it! Quick and fun notifications of anytime a user stars your Github project.
If you want the code used in this application, check it out here
Check out the full range of Github webhook events -- you can implement some really powerful workflows with webhooks + Serverless. Let us know what you build!