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:
-
Generate random AES-256 key (32 bytes)
-
Generate random IV (16 bytes) for AES-CBC
-
Encrypt payload data with AES-256-CBC using the key and IV
-
Encrypt the AES key with RSA-OAEP-SHA256 using your public key
-
Encode all components as Base64 strings
-
Return encrypted JSON payload containing
encryptedKey,data, andiv
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 |
|---|---|---|---|
|
|
AES-256 key encrypted with RSA-OAEP-SHA256 |
Base64 |
~344 chars (2048-bit RSA) |
|
|
Payload data encrypted with AES-256-CBC |
Base64 |
Variable (depends on payload size) |
|
|
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:
-
Base64 decode the
encryptedKeyfield -
Use RSA-OAEP-SHA256 decryption with your private key
-
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:
-
Base64 decode the
dataandivfields -
Validate IV is exactly 16 bytes
-
Use AES-256-CBC decryption with the recovered key and IV
-
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.