Verify the webhook signature

Morta by default will sign every webhook with a signature in the Morta-Signature header. This allows you to verify that webhook data is from Morta, and the data has not been tampered with.

Before you can verify signatures, you'll need to retrieve your signing secret for the project. This will located in your Project -> Settings -> Developer page.

 Verifying the signature manually

The Morta signature includes two pieces of information, the timestamp of the event, and HMAC of the body of the POST request.

The signature header is formatted as:

Morta-Signature: t=1234567890,v1=5ab0811a0d362f7becff116ce7b232c3ef61400d01911c50c03183a44d4cb00c

The timestamp is prefixed with t= and the signature is prefixed with v1=. The signature is generated using HMAC with SHA256 digest.

Step 1: Extract the timestamp and the signature

Pull out the timestamp and the signature from the header.

Step 2: Create the message used to sign the payload

The signature is based on concatenating:

  • The timestamp
  • The full stop charater .
  • The JSON request body

Step 3: Create a signature from the message

Using the message created above, use HMAC with SHA256 and your secret signing key to generate the signature.

Step 4: Compare the generated signature with the signature in the header

Finally compare the signature you've generated with the signature that is present in the request. If they are equal, you've verified that the webhook is valid.

Sample verification code

Here is sample code to verify the signature using Python and the Flask web framework

import hmac

from flask import Flask, request, abort

app = Flask("example")


@app.route("/webhook", methods=["POST"])
def index():
    body = request.data.decode("utf8")

    signing_secret = bytes("YourSigningSecret", "utf8")
    sig_header = request.headers["Morta-Signature"]
    ts_kv, sig_kv = sig_header.split(",")
    ts = ts_kv.split("=")[1]
    sig = sig_kv.split("=")[1]

    message = bytes(f"{ts}.{body}", "utf8")

    h = hmac.new(signing_secret, message, digestmod="sha256")
    valid = hmac.compare_digest(h.hexdigest(), sig)

    if not valid:
        abort(400, "Signature is not valid")

    return "OK"


app.run(host="0.0.0.0", port=4500, debug=True)