Original post

Back in 2014, I wrote a post describing a simple payload server
for GitHub webhooks, using Python 3. That server could be deployed to any VPS
listening on a custom port.

Now it’s 2019, and deploying servers to VPSs doesn’t make me feel hip enough.
All the cool kids are into serverless now, so I decided to rewrite the same
payload server in and deploy it as a Google Cloud Function. This brief post
can serve as a basic tutorial on how to do it.

I assume you already have a GCP account (there’s a free tier), and the
gcloud command-line tool is configured to authenticate with your account and
project name.

You can see the full code here,
but this is the important part:

func Payload(w http.ResponseWriter, r *http.Request) {
  body, err := ioutil.ReadAll(r.Body)
  if err != nil {
    log.Println(err)
    return
  }
  log.Println("Header:n---------")
  fmt.Println(r.Header)

  if !validateSignature(body, r) {
    m := "Signature validation failed"
    log.Println(m)
    w.Write([]byte(m))
    return
  }

  fmt.Println("Body:n---------")
  log.Println(string(body))
}

Payload is a standard http.HandlerFunc, and its signature should be
familiar to anyone who has written HTTP servers in Go. GitHub sends its payload
as a POST request (hence our reading from r.Body) with some special headers
for validation. The validation code runs a SHA1 HMAC to ensure that GitHub
knows a secret key shared with the application (this helps keep intruders away
from your payload server).

The full code sample
has this validation code, as well as a simple unit test for Payload. It
doesn’t actually attempt to create a properly signed message, but checks that
Payload is alive and returns a valid HTTP response. In general, it is highly
recommended to unit-test these handlers locally, because cloud function
deployment takes many seconds and isn’t very convenient for short edit-test
cycles.

To deploy this function, we’ll go to the directory where payloadserver.go
lives, and run:

$ gcloud functions deploy payloadserver 
      --entry-point Payload 
      --runtime go111 
      --trigger-http 
      --set-env-vars HOOK_SECRET_KEY=<your secret key>

This prints out a URL for your function; it looks something like:

httpsTrigger:
  url: https://<region>-<project-name>.cloudfunctions.net/payloadserver

Which is what you’ll point the GitHub webhook to. Configure the webhook to send
all events, and then test it by creating or modifying some issue in your
repository. The webhook management page on GitHub should now show the event
in “Recent deliveries”. You can also check the logs of your cloud function,
either from the GCP control panel, or from the command-line:

$ gcloud functions logs read payloadserver

If everything ran successfully, you’ll see the headers and the body of the
payload emitted to the log. That’s it!

Jokes about hipness aside, once you get the hang of it cloud functions seem like
a particularly easy way to deploy simple web servers and apps for specific
needs. There’s a lot of powerful functionality behind the simple facade – for
example, resources will automatically scale with the load. That said, be aware
of the costs if you’re doing it for anything high-volume.