Self-hosting (WebHook)

SmartApps may either be hosted and executed on AWS Lambda, or hosted on the platform of your choosing. This document describes the steps you must take when creating a self-hosted (or WebHook SmartApp).

Steps

When creating a self-hosted SmartApp, follow these steps:

  1. Create a web services application that can receive incoming HTTP POST requests to a publicly accessible endpoint via HTTPS. You can write and host your application using the language and tools of your choice.
  2. When creating your SmartApp, specify the endpoint URL of your app as the target URL, and select WEBHOOK as the app type. Your SmartApp must be reachable by SmartThings at this time, as it will be called with the PING lifecycle to verify its existence and health.
  3. Store the public key returned after creating your SmartApp. This will be used to verify incoming requests are sent from SmartThings.
  4. Ensure that all subsequent incoming requests are verified as coming from SmartThings, as documented below.

NOTE

Ensure that your WebHook has an HTTPS URL. You can use free tools such as ngrok or Let’s Encrypt to enable your WebHook with HTTPS (SSL/TLS).

HTTP signature verification

All requests sent by SmartThings are signed in accordance with the IETF HTTP Signatures specification Draft 3 specification. A full discussion of HTTP signature verification is beyond the scope of this document; consult the specification referenced above, or see Joyent’s page on http signing for more information.

The signature data is found on the authorization request header:

{
  "authorization": "Signature keyId=\"\/SmartThings\/80:34:9f:9f:51:84:7d:6d:40:21:63:44:9c:b1:78:44\",signature=\"AhTJ9uHjYbjMTWbToZ0ueRWu6gM52ueBRUSEm2H4xEyr4+cQ7zVN3IcZYsva3Kx0HcXU+2qiBlTmu4KgGW4PcANNV7bI8DUz\/cP6DNNyuGIveYQDe2XiG\/gnphyxfJHbPoGGmWAZaJfHaiutAW7GnzOmjGOfvsNX0xT\/PMOsUbyuJxMNw5btLrIYeenXQN4trD9GfudTljxaFRuFp\/an3A23qC0hxmweGaek3Tzn65\/iceWnok1gLDSo9IZCvGbjPc7UUZFzEdHNDsIOSHXMSwxLQntMl0UvfMytejT4p2X\/yJXlqHlBd\/GNwFGp4P8kj3iylMCXDEhbImx7dJ2Fnw==\",headers=\"(request-target) digest date\",algorithm=\"rsa-sha256\""
}

The public key you receive when creating your SmartApp is used to verify the request.

Various open-source libraries for different languages exist to provide HTTP signature verification, including:

Example

const httpSignature = require('http-signature');
const publicKey = fs.readFileSync('./config/smartthings_rsa.pub', 'utf8');

app.post('/smartthings', function (req, response) {
  // We don't yet have the public key during PING (when the app is created),
  // so no need to verify the signature. All other requests are verified.
  if (req.body && req.body.lifecycle === "PING" || signatureIsVerified(req)) {
    handleRequest(req, response);
  } else {
    response.status(401).send("Forbidden");
  }
});

function signatureIsVerified(req) {
  try {
    let parsed = httpSignature.parseRequest(req);
    if (!httpSignature.verifySignature(parsed, publicKey)) {
      console.log('forbidden - failed verifySignature');
      return false;
    }
  } catch (error) {
    console.error(error);
    return false;
  }
  return true;
}

function handleRequest(req, response) {
  // handle all lifecycles from SmartThings
}
from httpsig.verify import HeaderVerifier, Verifier
from flask import Flask, request, jsonify

app = Flask(__name__)

def get_public_key():
with open('../resources/smartthings_rsa.pub', 'r') as f:
 return f.read()

@app.route('/', methods=['POST'])
def execute_app():
  content = request.get_json()
  if (content.get('lifecycle') != 'PING':
    hv = HeaderVerifier(headers=request.headers, secret=get_public_key(), method='POST', path='/')
    try:
      if (hv.verify()):
        print 'verified - ok'
        # Signature verified, can now handle all SmartThings lifecycle  requests
        return  '', 200
      else:
        print 'verified - forbidden'
        # Invalid signature, return 403
        return '', 403
    except Exception as e:
      print(e)
      return '', 403