Security
HMAC verification for events and product reports
Klavi defaults to using the secretkey and SHA-256 HMAC algorithm to sign the payload.
The following parameter will be included in the request header that Klavi POST to you.
X-Klavi-Signature: 11fad26ccd04a59085a738b8e20be5f4e01887a3c5cdc88cd37bf431e843083e
X-Klavi-Timestamp: 1740716924We recommend that you verify the signature of the webhook. Tips for Best Practice:
Create a SHA-256 HMAC of the request body using your secretKey as the key
Compare it to the signature included on the X-Klavi-Signature header. If the two are equal then the request is valid, otherwise, it is spoofed.
The X-Klavi-Signature and X-Klavi-Timestamp header gets added to every event and product reports.

Here is an example of signature verification in Node.js:
const crypto = require('crypto');
const partnerSecret = '{{SECRET-KEY}}';
router.use('/webhook-handler', (request, res) => {
const body = request.body;
const signature = crypto
.createHmac('sha256', partnerSecret)
.update(JSON.stringify(body))
.digest('hex');
if (request.get('X-Klavi-Signature') !== signature) {
throw new Error('Spoofing detected, rejecting webhook');
}
});Allowed IP Addresses
To ensure webhook notifications reach your webhook listener server, you must add the following Klavi IP addresses to your firewall’s allowlist:
Environment | IP Address |
|---|---|
Sandbox | 18.231.92.86 |
Testing | 18.231.92.86 |
Production | 18.230.43.17 |
No matter how you receive shared data, the export IP addresses for Klavi's various environments are as shown above. If partners have stricter security requirements, you can only allow access to the above IP addresses.
Data encryption
Klavi supports two methods: plaintext and AES 256 algorithm for sensitive information. Partners can choose according to your actual situation in the Console. The decryption key for AES is the secretKey generated by Klavi for you.
AES 256 encryption example:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import base64
def aes_encrypt(plain_text, key):
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_data = cipher.encrypt(pad(plain_text.encode('utf-8'), AES.block_size))
encrypted_message = base64.b64encode(iv + encrypted_data).decode('utf-8')
return encrypted_messageAES 256 decryption example:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import base64
def aes_encrypt(plain_text, key):
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_data = cipher.encrypt(pad(plain_text.encode('utf-8'), AES.block_size))
encrypted_message = base64.b64encode(iv + encrypted_data).decode('utf-8')
return encrypted_messageWe can also customize and develop data encryption methods according to your requirements.