Skip to navigation

Payload Encryption

Encrypt what INFONITE sends you with AES-256-GCM, using a key only you hold

View as Markdown

What a flow session pushes to your servers — the payload of every webhook it sends, its results included — can be delivered encrypted with a key that is yours. You mint it, you pass it once when you create the session, and from that moment those payloads exist in readable form only inside your own process. What you read with your application secret — the results endpoint, the attachments — answers over TLS and is not encrypted with it: Financial Rider and Spain Public Administration say exactly what the key covers.

Encrypted values are always self-describing: the name of the scheme, ::, and the Base64 of the encrypted bytes. Read the prefix first and handle what it says.

aes::U2FsdGVkX19hZXMyNTZnY20rZW52ZWxvcGU9PQ…
aes::

The scheme. Read it first — it says how to open everything after it. Here, AES-256-GCM.

Base64

The encrypted bytes. Decode it and you get the IV, the ciphertext and the authentication tag, in that order.

This page is about aes:: — AES-256-GCM, the scheme behind every encrypted webhook payload of a flow session, and the one your integration will use. The platform also accepts values encrypted towards it with RSA, for credentials travelling the other way; that is a different problem with a different answer, and it lives at the end of this page.


Encrypting with AES-256-GCM

AES-256-GCM (Galois/Counter Mode) with a 12-byte IV and a 16-byte authentication tag. GCM is authenticated encryption: decryption fails loudly if a single byte was altered in transit, so a payload that decrypts is a payload nobody touched.

The encrypted payload is delivered as a string prefixed with aes:: followed by the Base64-encoded binary data.

Prefixaes::(Discard first)
+
Base64 Encoded EnvelopeU2FsdGVkX19…(Decode to raw binary)
IV / NonceFirst 12 bytes
CiphertextMiddle bytes
Auth TagLast 16 bytes

The raw byte layout after decoding the Base64 portion follows a strict concatenation sequence:

ComponentSizeDescription
IV (Nonce)12 bytesCryptographically secure random initialization vector.
CiphertextVariable bytesThe actual encrypted JSON string.
Authentication Tag16 bytesGMAC integrity and authenticity validation tag.

Encryption & Decryption Workflows

Here is the technical walkthrough for implementing the cryptography pipelines. They are the same five moves read in opposite directions — the one you implement is the top row:

Encrypting — what we do Decrypting — what you do JSON payload AES-256-GCMyour 32-byte key + random 12-byte IV IV ‖ ciphertext ‖ tag Base64 aes:: + the string aes:: + the string strip the prefix Base64 decode split 12 ‖ … ‖ 16 AES-256-GCM openfails loudly if a byte changed JSON payload

Encryption Walkthrough

Encryption Walkthrough
1

Generate IV: Create a cryptographically secure random 12-byte initialization vector (nonce).

2

Encrypt: Encrypt the plaintext payload using the AES-256-GCM algorithm with your allocated 32-byte key and the generated IV.

3

Assemble Envelope: Concatenate the IV (12 bytes), the resulting ciphertext (variable bytes), and the 16-byte authentication tag returned by the cipher.

4

Encode and Prefix: Base64-encode the concatenated byte block and prepended with the aes:: prefix.

Decryption Walkthrough

Decryption Walkthrough
1

Remove Prefix: Strip the aes:: prefix from the string.

2

Decode Base64: Decode the remaining Base64-encoded string to extract the raw bytes.

3

Extract Payload Parts:

  • IV (Initialization Vector): Extract the first 12 bytes.
  • Tag (Authentication Tag): Extract the last 16 bytes.
  • Ciphertext: Extract the middle bytes (everything between the first 12 bytes and the last 16 bytes).
4

Decrypt: Decrypt the Ciphertext using the AES-256-GCM algorithm with your allocated Session aes_key (configured during Create Session), the extracted IV, and the Tag.


Interactive Cryptography Sandbox

Test your GCM encryption and decryption logic directly in this sandbox tool. Toggle the switcher below to change between the Encrypt and Decrypt pipelines.

AES-256-GCM Playground
AES Key (32-byte Base64)
Plaintext JSON to Encrypt
Encrypted Payload (prefixed with aes::)
Output Results
Output will appear here…

Implementation Code Examples

Select your preferred programming language tab below to copy production-ready code examples matching this AES-256-GCM specification.

const crypto = require('node:crypto');
/**
* Encrypt plaintext using AES-256-GCM and return the base64-encoded string prefixed with "aes::".
* @param {Buffer} key - 32-byte AES key
* @param {Buffer} plaintext - Data to encrypt
* @returns {string} Encrypted payload prefixed with "aes::"
*/
function encryptGcm(key, plaintext) {
if (key.length !== 32) throw new Error('Key must be exactly 32 bytes');
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();
const envelope = Buffer.concat([iv, ciphertext, tag]);
return `aes::${envelope.toString('base64')}`;
}
/**
* Decrypt an aes:: prefixed Base64 string using AES-256-GCM.
* @param {Buffer} key - 32-byte AES key
* @param {string} payloadB64 - Encrypted payload prefixed with "aes::"
* @returns {Buffer} Decrypted plaintext bytes
*/
function decryptGcm(key, payloadB64) {
if (key.length !== 32) throw new Error('Key must be exactly 32 bytes');
const blob = Buffer.from(payloadB64.replace(/^aes::/, ''), 'base64');
const iv = blob.subarray(0, 12);
const tag = blob.subarray(blob.length - 16);
const ciphertext = blob.subarray(12, blob.length - 16);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
}
// Example usage:
const key = Buffer.from('9127m7kfzkNxS14A2U71lEzcyaqv9K5xDLtKuFTg+84=', 'base64');
// Encrypt example
const secretData = Buffer.from(JSON.stringify({ test: 'You got it!!!' }), 'utf8');
const encrypted = encryptGcm(key, secretData);
console.log('Encrypted Payload:', encrypted);
// Decrypt example
const decrypted = decryptGcm(key, encrypted);
console.log('Decrypted Plaintext:', decrypted.toString('utf8'));

Sending values in the other direction

Everything above solves one problem: we send you data, and only you can read it. There is a second, separate problem — you send us a secret, and only we can read it — and it needs a different kind of key.

It comes up when your server hands us something an engine will use on the end user’s behalf, typically a set of credentials. A shared key is the wrong tool there: whoever can encrypt could also decrypt, and both sides holding the same secret is exactly what you do not want for someone else’s password.

So each application gets an RSA key pair whose private half is generated inside the platform and never leaves it. You receive the public key — a PEM you paste into your key store, exactly as you would any other public key. Encrypt with it and nobody downstream can read the value: not an intermediary, not a log, and not your own process once the value has been sent. (An X.509 certificate carrying that same public key is available too, for stacks that expect one; it is self-signed, so it is a container with a validity window rather than a chain of trust — the key inside it is what does the work.)

Two schemes travel that way, and the choice between them is a size limit, not a preference:

PrefixSchemeUse it for
rsa::RSA, PKCS#1 v1.5A single short value. A 2048-bit key caps the payload at roughly 245 bytes.
hybrid::AES-256-GCM under an RSA-wrapped keyEverything else — a whole credentials object exceeds the RSA limit immediately.

The two envelopes, byte by byte

rsa:: is the simple one: the value’s UTF-8 bytes, encrypted with the public key using RSA PKCS#1 v1.5 padding, Base64-encoded, prefixed. Nothing else travels.

hybrid:: carries two things in one Base64 string: a fresh AES-256 key that only the platform can unwrap, and the value encrypted with that key — the same AES-256-GCM envelope this page describes above, laid out the same way.

Prefixhybrid::(Says how)
+
Base64 Encoded Envelopek9Xr2m1pQ…(One string, four parts)
Wrapped AES keyRSA PKCS#1 v1.5 — 256 bytes for a 2048-bit key
IV / Nonce12 bytes
CiphertextAES-256-GCM
Auth TagLast 16 bytes
ComponentSizeDescription
Wrapped AES keyKey size ÷ 8 — 256 bytes for a 2048-bit keyA random 32-byte AES-256 key, encrypted with your application’s public key (RSA PKCS#1 v1.5). We unwrap it with the private half.
IV (Nonce)12 bytesRandom, one per value.
CiphertextVariableThe value — a string, or a whole JSON object — under AES-256-GCM with that key.
Authentication Tag16 bytesGCM’s tag; a value that was touched in transit fails to decrypt instead of decrypting wrong.

Generate a new AES key and a new IV for every value. Both are cheap, and reusing either is the one way to weaken GCM. The RSA public key is the only thing you keep.

Encrypting towards us — code examples

Each example reads the public key exactly as the Console gives it — the PEM you copy or download from App Settings → Security & Keys — and produces both envelopes. All three were run against the platform’s own decryption before being published here.

The Console’s PEM is in PKCS#1 form (-----BEGIN RSA PUBLIC KEY-----). Node.js and Python read it as it is. The JDK only parses the -----BEGIN PUBLIC KEY----- form, so the Java example wraps the key into it in a dozen lines — no extra library, no certificate.

const crypto = require('node:crypto');
const fs = require('node:fs');
/** The PEM from the Console, as downloaded. */
function loadPublicKey(pem) {
return crypto.createPublicKey(pem);
}
/** One short value (up to 245 bytes with a 2048-bit key): RSA PKCS#1 v1.5, prefixed "rsa::". */
function encryptRsa(publicKey, value) {
const ciphertext = crypto.publicEncrypt(
{ key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(value, 'utf8')
);
return `rsa::${ciphertext.toString('base64')}`;
}
/** Anything, any size: a fresh AES-256 key wrapped with RSA, then AES-256-GCM. Prefixed "hybrid::". */
function encryptHybrid(publicKey, value) {
const plaintext = Buffer.from(typeof value === 'string' ? value : JSON.stringify(value), 'utf8');
const aesKey = crypto.randomBytes(32);
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const wrappedKey = crypto.publicEncrypt(
{ key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING },
aesKey
);
// wrapped key (256 bytes for a 2048-bit key) || IV (12) || ciphertext || tag (16)
const blob = Buffer.concat([wrappedKey, iv, ciphertext, cipher.getAuthTag()]);
return `hybrid::${blob.toString('base64')}`;
}
// Example usage:
const publicKey = loadPublicKey(fs.readFileSync('public.pem', 'utf8'));
console.log(encryptRsa(publicKey, 's3cret-p4ssw0rd'));
console.log(encryptHybrid(publicKey, { username: '44444444A', password: 's3cret-p4ssw0rd' }));