DEV Community

Dan Croak
Dan Croak

Posted on • Originally published at dancroak.com

Heroku to Slack with AWS Lambda

When my production app processes change state on Heroku, I want to be notified in Slack:

Screenshot of Slack notification

Other examples:

17:38:52 clock.1 `bundle exec ruby schedule/clock.rb` up
17:39:03 web.1 `bundle exec puma -p $PORT -C ./config/puma.rb` up
17:39:05 web.2 `bundle exec puma -p $PORT -C ./config/puma.rb` up
Enter fullscreen mode Exit fullscreen mode

Pager-notifying events:

17:38:52 queuenote.1 `bundle exec ruby queue/note.rb` crashed
Enter fullscreen mode Exit fullscreen mode

Heroku has webhooks for these events but their payloads aren't in the format needed for Slack incoming webhooks.

AWS Lambda is the perfect glue to transform the Heroku webhook's JSON payload into a useful JSON payload for Slack's incoming webhook.

Slack config

Create an incoming webhook. Copy the URL.

Lambda config

Create a Lambda function. AWS' supported runtimes include Node, Python, Ruby, and Go. You can alternatively implement a custom runtime.

Here's an example in Ruby:

require "json"
require "net/http"
require "time"
require "uri"

def lambda_handler(event:, context:)
  dyno = JSON.parse(event["body"])
  name = dyno["data"]["name"] || ""
  state = dyno["data"]["state"] || ""

  ignored_states = ["starting", "down"].include?(state)
  term_one_off = state == "crashed" && ["scheduler", "run"].any? { |p| name.include?(p) }

  if ignored_states || term_one_off || name.include?("release")
    return {
      statusCode: 200,
      headers: { "Content-Type": "application/json" },
      body: "ok"
    }
  end

  uri = URI.parse(ENV.fetch("SLACK_URL"))
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  req = Net::HTTP::Post.new(uri.request_uri)
  req["Content-Type"] = "application/json"

  req.body = {
    text: [
      Time.parse(dyno["created_at"]).getlocal("-07:00").strftime('%H:%M:%S'),
      name,
      "`#{dyno["data"]["command"]}`",
      state
    ].compact.join(" ")
  }.to_json

  res = http.request(req)

  return {
    statusCode: 200,
    headers: { "Content-Type": "application/json" },
    body: res.body
  }
end
Enter fullscreen mode Exit fullscreen mode

Paste the Slack incoming webhook URL as an environment variable, which is encrypted at rest.

Create an API Gateway to make the Lambda function accessible in the Heroku web UI.

Heroku config

Go to:

https://dashboard.heroku.com/apps/YOUR-APP-NAME/webhooks
Enter fullscreen mode Exit fullscreen mode

Create a webhook with event type "dyno". Paste the API Gateway URL as the Payload URL.

Modify to taste

Edit and save the code in Lambda's web-based text editor. Trigger a webhook to test the function. View the auto-created CloudWatch logs for each function call.

Top comments (0)