All of our webhook calls contain a signature that you can use to verify the validity of the event contents.
Our POST request to your subscription URL will contain an HTTP header called X-JWT-SIGNATURE with a jwt token signed by us using the RS512 algorithm.
The JWT has the following structure:
{
"iss": "[SIGNING_KEY_ID]",
"iat": iat,
"digest": "[REQUEST_BODY_DIGEST]"
}
- SIGNING_KEY_ID can be either
lirium-sandboxorlirium-productiondepending on the environment where the webhook call was sent from. - iat is the time when the JWT was generated, it's an
intrepresenting the seconds from POSIX time. You can use this value to verify that the JWT is recently generated. - REQUEST_BODY_DIGEST is the hex representation of the sha256 digest of the request body bytes.
The following python code example can be used to validate the signature of a request received using flask from our sandbox environment:
from jose import jwt
from hashlib import sha256
import flask
token = flask.request.headers.get("X-JWT-SIGNATURE")
decoded = jwt.decode(token, signing_public_key, "RS512")
digest = sha256(flask.request.data).hexdigest()
assert decoded.get("digest") == digest
assert decoded.get("iss") == "lirium-sandbox"
The corresponding public keys for each of our environments can be found here: Webhooks Public Keys
As an example, this JWT
{
'iss': 'lirium-sandbox',
'iat': 1646758802,
'digest': '075b83598e4e147f240f5b15694b42063741b677548c89c9a4f100b5a36263f5'
}
Would yield the following JWT Signature
eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsaXJpdW0tc2FuZGJveCIsImlhdCI6MTY0Njc1ODgwMiwiZGlnZXN0IjoiMDc1YjgzNTk4ZTRlMTQ3ZjI0MGY1YjE1Njk0YjQyMDYzNzQxYjY3NzU0OGM4OWM5YTRmMTAwYjVhMzYyNjNmNSJ9.ONcsg4ZWPKVuXC_nGWk7zfKuoqWgfV4edxDeweL7S4cSkj9SdnxZnxSBdPFMzeIaPAETnFNc-6Pxcd919pYPIx0M155yEBP4SHwmsflh93vrjdoUZYB2sD3XL6xeiCQ0KuwRp_yHpICO9JzCto4XsbvZ_wmhI1wIl-hDnlPDfdbL--rV-usbx1EPXoS67v3oTV2YtdvtoO5QZAy7relxyA9hLEpsKv_Duf_RYDiNkGEl_aZrKu8fQEFf0CUZkbqsbv1Xt-VBavk1A3muGXJfOHjBxbT5er0_Mkn7pj5T1o4XiRLlz-WhYjwsKaewpvsdoFVt_ggFPOwcBYaOLKPx5Xfapt7WtsxSH3gds9PzUGj9mi4lIm-wCYjIn7tb9iimwfhkNm_6himIflLGCQ1eu3n1UzirnX5OvlmBOutXkHddWxzp1xBn5PuWWRMMHGWg-tZcUl5x4cV9kkkse9FmheHkstjf0Ham3ePCydlMp5r0vjrgsXbHaC3iLSf_3al7wpkLXogWiVVbqAkoLYpy3nCTZ_i0btnn5GwcRW2Gx5-ILNoxLe49lMD_le-iSEb9VaC7n8lyZYw3WTXdN1a0RAAO3RbkT8UahqTxfH_iqWbbRcr1Uep92OKTt3xbaCqvyGMOSLSO-nlnXrLgyZHllG0K59wUwQjp2GcMVOljoNU
