Webhooks¶
Note
Webhooks and endpoints can be created as code through the Scalr Terraform Provider.
Webhooks are very simple, yet very powerful way to integrate with external systems such as Slack, email, or any other system via an HTTP post request. The webhook, a simple application designed to handle incoming HTTP requests, will parse and process a payload from Scalr then integrate with the external systems when events occur during a Terraform run, such as:
Run complete - When the run is completed successfully
Run errored - When the run is prevented from continuing due to an error in the code, cancellation, or a policy failure.
Run needs attention - When someone will need to review a plan based on the workspace not having auto-approval or if a policy is override is required.
During any of these events Scalr will send a payload to an endpoint that the the administrator or workspace owner has defined.
Endpoint Configuration¶
Endpoints can be configured and assigned to environments. An endpoint is required as it is the URL that Scalr will send the payload to. To create an endpoint, click on integrations and then endpoints on the main toolbar. Enter the required information:

A secret key is generated. This key must be used in the webhook code to verify that Scalr is the source of the payload.
Webhook Configuration¶
The webhook configuration is where the endpoint and trigger events are defined. Webhooks can be created within the environments page at the account scope or in workspaces.
To create a webhook for an environment, go to the account scope, click on the environment that you are creating the webhook for, and then click on webhooks. If a webhook is assigned to an environment then all workspaces within that environment will inherit it.

To create a webhook at in a workspace, go to the main dashboard of that workspace, click on webhooks, and then new webhook.

On the webhook page configure it with the following settings:

After a Terraform run has triggered one of the events the payload that was sent to the webhook can be viewed.
Deliveries¶
All webhook events will be logged under the deliveries. These can be seen via Integrations in the environment or in the workspace deliveries tab:

Each delivery will also show the payload that was sent for that event. Note that any Terraform and Shell variables from the workspace are include in the variables block. Shell variables provide a means to pass custom data into the payload:
{
"environment": {
"id": "env-t3qeqbo97mdot6o",
"name": "CS",
"url": "https://my-account.scalr.io/#/16002/17702/dashboard"
},
"event_name": "run:needs_attention",
"payload_version": 1,
"run": {
"created_at": "2021-03-02T10:52:21",
"created_by": {
"email": "[email protected]",
"id": "user-stp8qjf0ck3bgu8",
"username": "[email protected]"
},
"id": "run-tbrplhl2fr42p58",
"message": "",
"source": "ui",
"status": "cost_estimated",
"updated_at": "2021-03-02T10:52:41",
"url": "https://my-account.scalr.io/#/workspaces/runs/dashboard?runId=run-tbrplhl2fr42p58"
},
"variables": {
"TEXT": "hello",
"aws_location": "us-east-1",
"size": "t3.small",
"svr_count": "1"
},
"webhook": {
"id": "wh-tbrpj9974aj2cto",
"name": "pg-webhook"
},
"workspace": {
"id": "ws-tad1pack04h4m90",
"name": "cli-test",
"url": "https://my-account.io/#/workspaces/dashboard?workspaceId=ws-tad1pack04h4m90"
}
}
Zapier Integration for Slack¶
For those of you who do not want to build or maintain a webhook server, you may want to use a service like Zapier, which is built for this exact use case. In the following example, we will show how to integrate Scalr with Slack using Zapier as the webhook service.
Create a new Zap, select Scalr as the application:
Press Continue:
Copy the webhook URL:
Configure the endpoint in Scalr with the following settings. The name can be anything and the URL is the Zapier URL copied from the previous step.
Configure the webhook in Scalr using the endpoint created in the previous step. Select which events should trigger the call to Zapier:
Trigger a run in Scalr and after one of the events has been fired - test your trigger:
Note
Sensitive data has been excluded from the screenshot.
In Zapier, select Slack as an action:
Select an event in Zapier. We are using Send Channel Message in this example:
Authorize and select your Slack account:
Set up an action, in this example we are configuring a simple message with run details:
Note
To see all data sources available for the message text, click in text field.
Test an action by pressing Test & Continue
Turn on the Zap by pressing Turn on Zap
You should now receive Slack messages according to your Zap configuration!
Example Webhook Server Code¶
Warning
This code is provide as an example only and assumes the reader has the necessary Python skills to properly implement a webhook server. This should NOT be used in production systems without full testing and validation and this code IS NOT supported by Scalr. Other languages can be used.
For those of you who want to build and maintain a webhook server, the following provides an example of doing so with Python. Instructions for installing on Centos are embedded in the code. For other operating systems please use the appropriate commands to install the packages and utilities.
This code defaults to listening on port 5018. You can change this on the last line.
Note
Code to make onward calls to other systems must be added at line 80 where an simple example is in the comments. In general some authentication will be needed, e.g. a bearer token.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | #!/usr/bin/env python3 # Disclaimer: Please don't use the following code in the production. # # install instructions for centos:7 # # yum update -y # yum install -y python3 # pip3 install flask python-dateutil pytz # vim server.py # # put content of this file in server.py # # export SCALR_SIGNING_KEY=your-secret-here # # # run a server # python3 server.py # # #use your webhook endpoint as: http://x.x.x.x:5018/webhook-endpoint/ from flask import Flask from flask import request from flask import abort from flask import jsonify import hmac import hashlib import logging import os from datetime import datetime import dateutil.parser import pytz # Configuration variables SCALR_SIGNING_KEY = os.getenv('SCALR_SIGNING_KEY', 'this-is-secret-key') SCALR_WEBHOOK = os.getenv('SCALR_WEBHOOK', 'webhook-endpoint') MAX_AGE_SIGNATURE = 300 def validate_request(request): """ Validate webhook authenticity """ if "X-Signature" not in request.headers or "Date" not in request.headers: logging.warning("Missing signature headers") return False if not SCALR_SIGNING_KEY: logging.warning("SCALR_SIGNING_KEY is not set") return False # Compute our signature date = request.headers["Date"] expected_signature = hmac.new( SCALR_SIGNING_KEY.encode(), request.data + date.encode(), hashlib.sha256 ).hexdigest() if expected_signature != request.headers['X-Signature']: logging.warning('Signature does not match') return False # Check for freshness (this still allows rapid replay attack) date = dateutil.parser.parse(date) now = datetime.now(pytz.utc) delta = abs((now - date).total_seconds()) if delta >= MAX_AGE_SIGNATURE: logging.warning("Signature is too old (%ds)" % delta) return False return True logging.basicConfig(level=logging.DEBUG) app = Flask(__name__) for var in ['SCALR_SIGNING_KEY', 'SCALR_WEBHOOK']: logging.info('Config: %s = %s', var, globals()[var] if 'PASS' not in var else '*' * len(globals()[var])) @app.route('/' + SCALR_WEBHOOK + '/', methods=['POST']) def webhook_listener(): logging.info(request) logging.info(request.headers) logging.info(request.data) if not validate_request(request): abort(403) out={"Status" : "ok"} logging.info(out) # your custom code that handles webhook can be here # This is a very simple sample of doing a POST to a 3rd party system # # import json # import requests # api_url = 'http://localhost:5000/create-row-in-gs' # create_row_data = {'id': '1235','name':'Joel','created_on':'27/01/2018','modified_on':'27/01/2018','desc':'This # is Joel!!'} # print(create_row_data) # r = requests.post(url=api_url, json=create_row_data) # print(r.status_code, r.reason, r.text) return jsonify(out) if __name__ == '__main__': app.run(debug=False, host='0.0.0.0', port=5018) |