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-sandbox
orlirium-production
depending on the environment where the webhook call was sent from. - iat is the time when the JWT was generated, it's an
int
representing 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