Webhook Payload Decryption Guide

This page provides technical specifications for decrypting the encrypted webhook payloads. We use a hybrid encryption approach, RSA-OAEP and AES-256-CBC to securely handle large payloads while maintaining strong cryptographic security.

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:

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

Field Descriptions

Field

Description

Encoding

Size

encryptedKey

AES-256 key encrypted with RSA-OAEP-SHA256

Base64

~344 chars (2048-bit RSA)

data

Payload data encrypted with AES-256-CBC

Base64

Variable (depends on payload size)

iv

Initialization Vector for AES-CBC

Base64

24 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)

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

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:

{
  "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):

-----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.

{
  "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.