Signing Webhooks

Pipe automatically signs the webhook requests so you can (optionally) verify that the requests are coming from Pipe and not a third party. This is useful if your application exposes sensitive data, but it is not required. It just adds an additional layer of protection.

Pipe adds an additional HTTP header with the webhook POST requests, X-Pipe-Signature, which contains the specific signature for the request. To verify this request, you will need to generate a signature using the same key that Pipe uses (the one in your account webhook tab) and compare the value obtained with the value sent via the header.

Getting your webhook key

In the webhook tab of your account, a key is automatically generated. You can use this one or reset it to generate a new one at any time. Pipe will automatically use the newest one.

Generating your own signature for comparison

In the code that receives and processes the webhook request:

  1. Create a string of the webhook’s URL, exactly as you entered it in Pipe. Pipe will always sign the webhook requests with the exact URL, so any small difference will prevent the signature from validating.
  2. Append the JSON data received via webhook:
    • the actual request body with Content-type: application/json webhooks
    • the value of the payload key with Content-type: application/x-www-form-urlencoded webhooks
  3. Hash the resulting string with HMAC-SHA1, using the webhook key from your account, and generate a binary signature. Pipe uses binary signatures so be careful not to generate a hexadecimal signature.
  4. Base64 encode the binary signature.
  5. Compare the generated signature with the one provided by X-Pipe-Signature HTTP header.

Example of a PHP implementation of the steps above

// Generates a base64-encoded signature for a Pipe webhook request.
// @param string $key the webhook's key
// @param string $url the webhook url
// @param string $jsonData the data in JSON format

function generateSignature($key, $url,  $jsonData){
  $data_to_sign = $url . $jsonData;
  return base64_encode(hash_hmac('sha1', $data_to_sign, $key, true));
}

$key = "";
$url = "";
if($_SERVER['CONTENT_TYPE']=="application/json"){
  $data=file_get_contents("php://input");
}else if ($_SERVER['CONTENT_TYPE']=="application/x-www-form-urlencoded"){
  $data=$_POST["payload"];
}
echo generateSignature($key,$url,$data);

Example of a Python (using Flask) implementation of the steps above

import base64
import hashlib
import hmac
import json
from flask import Flask, request

def generateSignature(key: str, url: str, jsonData :str) -> str:
    """
    Generates a base64-encoded signature for a Pipe webhook request.

    :param str key: the webhook's key
    :param str url: the webhook url
    :param str jsonData the data in JSON format
    :return: the signature of the request
    :rtype: str
    """
    data_to_sign = url + jsonData
    sign = base64.b64encode(hmac.new(key.encode(), data_to_sign.encode(), digestmod=hashlib.sha1).digest()).decode()
    return sign

webhook_key = "key"
webhook_url = "https://website.com/custom-route"

app = Flask(__name__)
# Define a POST route on "/custom-route" to get all POST request comming to our webhook_url
@app.route('/custom-route', methods = ['POST'])
def customFunction():
    sign = ""
    contentType = request.headers.get('Content-Type')
    # If content type is application/json the data is in the body as a JSON
    if contentType == 'application/json':
        # Remove extra spaces and newlines when we convert JSON to string
        data = json.dumps(request.json, separators=(',', ':'))
        sign = generateSignature(webhook_key, webhook_url, data)
    # If content type is application/x-www-form-urlencoded the data is in the POST from with the key "payload"
    elif contentType == 'application/x-www-form-urlencoded':
        data = request.form['payload']
        sign = generateSignature(webhook_key, webhook_url, data)
    print(sign)
    return sign