Skip to content

It is important to note that not all webhook payloads are encrypted - this depends on your configuration. If you wish to use encryption you must provide a public key for your webhook configuration within the console. For specific information on the webhook payload schema and fields, you can view the specific detail under the following pages:

Table of Contents ​

Our encryption Process ​

Here are our encryption steps which will be beneficial to help you understand what needs to be done for the decryption steps:

  1. Generate random AES-256 key (32 bytes)

  2. Generate random IV (16 bytes) for AES-CBC

  3. Encrypt payload data with AES-256-CBC using the key and IV

  4. Encrypt the AES key with RSA-OAEP-SHA256 using your public key

  5. Encode all components as Base64 strings

  6. Return encrypted JSON payload containing encryptedKey, data, and iv

Payload Structure ​

When using encryption, the payload will always follow the following structure:

plaintext
{
  "encryptedKey": "base64-encoded-encrypted-aes-key",
  "data": "base64-encoded-encrypted-payload-data", 
  "iv": "base64-encoded-initialization-vector"
}

Field Descriptions ​

FieldDescriptionEncodingSize
encryptedKeyAES-256 key encrypted with RSA-OAEP-SHA256Base64~344 chars (2048-bit RSA)
dataPayload data encrypted with AES-256-CBCBase64Variable (depends on payload size)
ivInitialization Vector for AES-CBCBase6424 chars (16 bytes)

Decryption Specifications ​

Step 1: RSA Decryption (AES Key Recovery) ​

Purpose: Decrypt the AES key using your RSA private key

Algorithm: RSA with OAEP padding
Hash Function: SHA-256
MGF: MGF1 with SHA-256
Label: None (empty)

Process:

  1. Base64 decode the encryptedKey field

  2. Use RSA-OAEP-SHA256 decryption with your private key

  3. Result: 32-byte AES-256 key

Step 2: AES Decryption (Data Recovery) ​

Purpose: Decrypt the payload data using the recovered AES key

Algorithm: AES-256-CBC
Key Size: 256 bits (32 bytes)
Block Size: 128 bits (16 bytes)
Padding: PKCS#7 (also known as PKCS#5 in some implementations)

Process:

  1. Base64 decode the data and iv fields

  2. Validate IV is exactly 16 bytes

  3. Use AES-256-CBC decryption with the recovered key and IV

  4. Convert resulting bytes to UTF-8 string

Implementation Examples ​

JavaScript (Node.js) ​

plaintext
const crypto = require('crypto');

function decryptWebhookPayload(encryptedPayload, privateKeyPem) {
  // Step 1: Decrypt AES key with RSA-OAEP-SHA256
  const aesKey = crypto.privateDecrypt({
    key: privateKeyPem,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: 'sha256'
  }, Buffer.from(encryptedPayload.encryptedKey, 'base64'));
  
  // Step 2: Decrypt data with AES-256-CBC
  const iv = Buffer.from(encryptedPayload.iv, 'base64');
  const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey, iv);
  
  let decryptedData = decipher.update(encryptedPayload.data, 'base64', 'utf8');
  decryptedData += decipher.final('utf8');
  
  return decryptedData;
}

Python ​

plaintext
from cryptography.hazmat.primitives import hashes, padding, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding as asym_padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import base64
import json

def decrypt_webhook_payload(encrypted_payload, private_key_pem):
    # Load private key
    private_key = serialization.load_pem_private_key(
        private_key_pem.encode(), password=None
    )
    
    # Step 1: Decrypt AES key with RSA-OAEP-SHA256
    encrypted_aes_key = base64.b64decode(encrypted_payload['encryptedKey'])
    aes_key = private_key.decrypt(
        encrypted_aes_key,
        asym_padding.OAEP(
            mgf=asym_padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    
    # Step 2: Decrypt data with AES-256-CBC
    iv = base64.b64decode(encrypted_payload['iv'])
    encrypted_data = base64.b64decode(encrypted_payload['data'])
    
    cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv))
    decryptor = cipher.decryptor()
    
    padded_data = decryptor.update(encrypted_data) + decryptor.finalize()
    
    # Remove PKCS7 padding
    unpadder = padding.PKCS7(128).unpadder()
    data = unpadder.update(padded_data) + unpadder.finalize()
    
    return data.decode('utf-8')

Testing Your Implementation ​

Use this test payload to verify your implementation:

Test Payload:

plaintext
{
  "encryptedKey": "nWjkuaqpfvUKUwUfTO97iMjBl03Mtl7AfWIjt06vrBg+vSfHdIoR2kBtFv3Pm5BljBmTCz5FLM4gVC5SaME40zSNhpOK1oT2M/vw7j+OzgxF8k5ZePECbtDvaljNDOXkkIW6P4LcjY7uUbm1A0vkcgkG+RdGLpyWd4jdu3sf7cSz+pdjv0mJwtEbk65yYW00Q3mFhcjKl+hR8TISzkJDxS73gkEZMQZZs51wQYj4drmddHxxZchE+tM2z99bFt8q5cZZVBhqrukBkHptQGBpdGh2vWaOqRsWiapw2gkq/R5wi0aH/hkLCP2lDoni1ZBEiNG8RuYMb3s1pSWZZoU0Og==",
  "data": "Uey9MVc0RaXms1i7GfbTuR0LwrdFARsYb++hwUDwBPyA/TGLpHiUGW9e/NPeaFaFyLiPjH6JVgoIvOo285H1InZaiEYfBIlU21h47tapJPCpOxOX8IC/1dRVHIXX2CdPk4+jyNA4IL4MpVHEAJWXTECa6sYt3z1ePM8ppHLV88CzRkbPJ5TzuHfd2+8tehe5egEudC+i8dYlbU2fEywTfuff3U36ON3SgdmW3k26RI+9oWff7bEq9lnuB3D3rai4UWXX4+v0wa1iJvZsDPumA+GHGpGSs6/HktH39dlcVB/bhdtRsNQeRtnjV1P5I/X/YT/ZzmaXvh7+hKCwby2DkukzIEIO3tTtOK6sJWrxkm0uE03tiv9517XFUHO45gII",
  "iv": "u8JKSIVAqtYXRjQvfKqvqg=="
}

Test Private Key (Demo Key Only - Do Not Use in Production):

plaintext
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDR7rACQBtqJvNa
wWoR20owygssGgeGCAF5t6vdXeXhnz/Sgq6xXb9hVU6zDg1AxoIYMS9zWGQGO9YU
gqUd11KFJeM1sAc8GqQMgHzFf6XYmEWDUBupiJ6k9FNIzo33UGErCZQtB8Tv50fZ
azPtgyE8V07cirAD3o3j4PVgZoF3DHVLLf8SMUKZGu2zJzWokT3W/C7Ih1s5s+tb
rQb5pKLBJd9d0RBc46+BEZyVGjelTcScVmhMhHoLYAGeq8vZvEWi0JaUqfu2uhRA
/ZjDYdHb0t3vt7pPMHl1KSay4kSm/upFcTejlCT7U8ujcOb0osW6HuQySHS8A19g
1KqwXPy3AgMBAAECggEAOBYB1ggUiu6vE28bDHw+vPwtsfGeUvOumTs14mVktjua
jH43d9FpAPMOnI3DgqvGM/poB/P0fSndTwt8W5WcSVBO/Jfzxt4DOAfXzdgFkfFU
mPl8+nUylTVlTs1IZmuGGj5r5P/vYHy02GRosirXZUyaGBfBYLdDxz7hr4iTTB9+
ye9qtVnDEZQHn+VxvC2T0u6eUOlQRLUDoF5U6rJPQGtIaNqe2vEiB22yEjdUxiZk
7h6/lpey6SL9/PKedGO8rdBHfRDfHrY4WuVEHZ9QuDtfFZf8dAAX2nnY462iv4iJ
Lq9XT6AHa0ffwgoyyk1j/LnORK7UXvUDZXDSTuZTmQKBgQD4LoaTud68vcrUZWYD
RGX9KFXo0pv49N0AbI4TH2lpQbau01dY3diWfg6uLYWJaq8JBL3XWNHlRFtuB7SB
TEFW7rfjMP8lzrx1gkE+oHv4azmlinTQSqSn/acbraB85y4NrFWdTmPCUbKI8mZV
R6kFmRnbXYX31XIWx+E8+rn2nQKBgQDYi7KnS3Lufp0tlY7CdUGjMQEcOXHlGQfd
NsPBwqTvgS5ZBkz7CYx61UBnzfOWAm3Lgck6tkU1VWYrk5ClQvQyzOE5W2L0rMy5
8ENGZkesgFgUMcAZUSrHy0MBoEp5O1SSgo30RHFj0+jjc4f57lu40RbjCwvXRsh1
UQ5Vv+u2YwKBgQDWRh0tGFJFtLuVK8Kq2XgQ3a1mqce+Z3MT9aWovvscxH68bH3C
nS9tp4J3QZhEr5ZV/AEIF4iJIL6rvf1LkRHsw4iCn//6nYa7Ee3Q63B9Z95KncMD
Px7wZ6DK1dp1XIe3iG4WteKNmEnD0T0nLgqxaaEYc6PB+UhagNJn+rVqRQKBgQCX
kbCKyJS5uoXp0jUKh2haAYL1bn/6C3slq595j/RCb/kw9ugLDrP1hi5+efl1ll2j
hESFg+8NVakd9zvXcPXcWOuMahrph8oEm9d6/zxL0cnU2L3M+rMnBW7xRHDcSlr3
8cnKn91+MmVgTw1uMxigVDng0WqIVFMbSpDuFFe4YQKBgBASvEVfxeH3EvhX6sx2
t8tvjY3BnjyEh6MX4radKoJZKB1syp9VZf6xdawiUcXuBTaets5/ELY4Xo50Rixc
uOcR+baFN7nNYb3Itj5rP6JWbxmpUWQSa8R/S5aWYhh1FH6R6XNX3GZxV7e6xXC4
3TKD4TIyz7Gs0F+TPLrmBNG3
-----END PRIVATE KEY-----

Expected Decrypted Result: ✅ Success Indicator: If your implementation is working correctly, you should see the decrypted JSON payload below when you decrypt the test payload using the provided private key.

plaintext
{
  "event": "account_connected",
  "accounts": 1,
  "customer_id": "customer177",
  "journey_key": "bce79918-6af1-4b8c-93df-f68091d3a5c2",
  "date": "2025-10-27T16:17:53.261Z",
  "url": "http://localhost:3001/consumer/066dd6b6-809f-4f75-8aa1-7ed9e306e16e",
  "journey_name": "Journey 6 (Embedded journey)"
}

Security Considerations ​

Key Management ​

  • Never hardcode private keys in your application code

  • Use secure key storage solutions (HSM, key vaults, encrypted storage)

  • Ensure proper access controls and key rotation policies

  • Store keys in environment variables or secure configuration systems

Memory Security ​

  • Clear sensitive key material from memory after use

  • Be cautious about logging decrypted content

  • Validate input parameters before processing

Validation Requirements ​

  • Verify RSA key size: Minimum 2048 bits recommended

  • Validate field presence: Ensure all required fields exist

  • Check data lengths: AES key (32 bytes), IV (16 bytes)

  • Handle errors gracefully: Don't leak cryptographic information in error messages

Troubleshooting ​

Common Issues ​

"OAEP decoding error" or "RSA decryption failed" ​

  • Cause: Wrong private key or mismatched padding/hash settings

  • Solution: Verify you're using the correct private key and RSA-OAEP-SHA256

"Invalid initialization vector" ​

  • Cause: IV is not exactly 16 bytes after Base64 decoding

  • Solution: Verify IV Base64 decoding and length validation

"Bad decrypt" or "Padding error" ​

  • Cause: Incorrect AES key or corrupted encrypted data

  • Solution: Ensure AES key was decrypted correctly from RSA step

"Invalid key length" ​

  • Cause: AES key is not exactly 32 bytes (256 bits)

  • Solution: Verify RSA decryption produced correct key length

Algorithm Reference ​

RSA-OAEP-SHA256 Parameters ​

  • Padding: OAEP (Optimal Asymmetric Encryption Padding)

  • Hash Function: SHA-256

  • Mask Generation Function: MGF1 with SHA-256

  • Label: None (empty/default)

AES-256-CBC Parameters ​

  • Algorithm: AES (Advanced Encryption Standard)

  • Key Size: 256 bits (32 bytes)

  • Mode: CBC (Cipher Block Chaining)

  • Padding: PKCS#7 (equivalent to PKCS#5 for AES)

  • IV Size: 128 bits (16 bytes)

For additional support or questions about webhook payload decryption, please contact our technical support team.