URGENT: How to encode webhook response to Base64 String

Hi there,

I am building Whatsapp Flows for which I need to send the data to my N8N webhook. Facebook/Whatsapp Business Manager demands the response to be in base64 String however (as you can see in the screenshot attached). What would be the quickest and best way to achieve this?
Thank you very much in advance!!!

Information on your n8n setup

  • n8n version: 1.82.1
  • Database (default: SQLite): SQLite
  • n8n EXECUTIONS_PROCESS setting (default: own, main): Own
  • Running n8n via (Docker, npm, n8n cloud, desktop app): Google Cloud Project
  • Operating system: Windows 10
1 Like

It’s not the most beautiful solution, but it works.

1 Like

const base64Response = Buffer.from(jsonString).toString(‘base64’);

this feels wrong

just use an expression {{ “this is your String”.base64Encode()}}

1 Like

Problem is it doesn’t reach the edit fields or code node. I selected that the respond to Webhook should be the one that sends the response. It’s because I clicked on Test webhook I guess it didn’t process further.

But anyways you manually entered „ok“
How can we send the actual response of the webhook to base64.

What you are saying is, that the workflow isn’t triggered?

Is your webhook reachable from the web?

Well it triggers but just the initial Get Webhook the rest doesn’t execute as I just went on Test Listening for event.

Make a second workflow with manual trigger and a HTTPS node and post something to your webhook, and see what happens

When you click on Listening for test event, only the webhook node will be triggered. After receiving a test event, you have the option to pin data by clicking on the pin icon located in the top right corner of the webhook node. Pinning saves the data received from the webhook. Once the data is pinned, every time you click the main Test Workflow button, the workflow will use this saved (pinned) data instead of waiting for new data.

@Franz @atwork1

Used that exact workflow from above.
Still says wasnt base64 encoded.

Do you know what exactly the response should be?
Regarding to your workflow only a value in encoded.
Maybe the whole json document should be encoded.

Is there something on your „expected result“ tab?

Hi @Franz
thanks for replying again. Well they just provide a link which I read through and dont know what to do differently:

I need to send a base64 encrypted string as return. My return says data: [base64encrypted string]
so dont really know.

@atwork1

My example was to show how to easily base64 encode a string.

You might have to encode the entire response Body as the error message says, and not just part of the json.

After processing the decrypted request, create a response and encrypt it before sending it back to the WhatsApp client. Encrypt the payload using the AES key received in the request and send it back as a Base64 string.

The full response must be encrypted and encoded.

I’m having the same issue here. Been trying to solve if for the last 10 hours straight :confused:

I simply can’t decrypt and encrypt the content. The suggested code by Meta doesn’t work on n8n. Can you please help?

Here’s the code

// demo encryption/decryption script
// put public key in public_key.pem file in the same folder as this script
// put private key in private_key.pem file in the same folder as this script
// run with: node <script-file-name>

import crypto from "crypto";
import fs from "fs";

const CLEAR_AES_KEY_STR = "<some-key-data>"
const PRIVATE_KEY_DATA = fs.readFileSync('private_key.pem', 'utf8');
const PUBLIC_KEY_DATA = fs.readFileSync('public_key.pem', 'utf8');

console.log("Clear key: " + CLEAR_AES_KEY_STR)

const encryptedAesKey = crypto.publicEncrypt(
  {
    key: PUBLIC_KEY_DATA,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256"
  }
  ,
  Buffer.from(CLEAR_AES_KEY_STR)
);

const encryptedAesKeyBase64 = Buffer.from(encryptedAesKey).toString('base64');

console.log("Encrypted base64 key: " + encryptedAesKeyBase64)

const decryptedAesKey = crypto.privateDecrypt(
  {
    key: crypto.createPrivateKey({
      key: PRIVATE_KEY_DATA,
      format: 'pem',
      type: 'pkcs1',//ignored if format is pem
      passphrase: '<passphrase>'
    }),
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256",
  },
  Buffer.from(encryptedAesKeyBase64, "base64"),
);

console.log("Decrypted key: " + decryptedAesKey)
if (decryptedAesKey.toString() === CLEAR_AES_KEY_STR) {
  console.log("Success, keys match!")
} else {
  console.log("Failed, keys do not match!")
}

Here are the instructions from Meta:

For data_api_version “3.0” you should follow below instructions to decrypt request payload:

  1. extract payload encryption key from encrypted_aes_key field:
  • decode base64-encoded field content to byte array;
  • decrypt resulting byte array with the private key corresponding to the uploaded public key using RSA/ECB/OAEPWithSHA-256AndMGF1Padding algorithm with SHA256 as a hash function for MGF1;
  • as a result, you’ll get a 128-bit payload encryption key.
  1. decrypt request payload from encrypted_flow_data field:
  • decode base64-encoded field content to get encrypted byte array;
  • decrypt encrypted byte array using AES-GCM algorithm, payload encryption key and initialization vector passed in initial_vector field (which is base64-encoded as well and should be decoded first). Note that the 128-bit authentication tag for the AES-GCM algorithm is appended to the end of the encrypted array.
  • result of above step is UTF-8 encoded clear request payload.

For data_api_version “3.0” you should follow below instructions to encrypt the response:

  • encode response payload string to response byte array using UTF-8;

  • prepare initialization vector for response encryption by inverting all bits of the initialization vector used for request payload encryption;

  • encrypt response byte array using AES-GCM algorithm with the following parameters:

    • secret key - payload encryption key from request decryption stage;

    • initialization vector for response encryption from above step;

    • empty AAD (additional authentication data) - many libraries assume this by default, check the documentation of the library in use;

    • 128-bit (16 byte) length for authentication tag - many libraries assume this by default, check the documentation of the library in use;

  • append authentication tag generated during encryption to the end of the encryption result;

  • encode the whole output as base64 string and send it in the HTTP response body as plain text.

@shrjrwkls

Thanks a lot. I tried to repliicate that in N8N hands me the error

“error”: “crypto is not defined”

or that he cant read the data from body - no body found.

Cant solve this for some reason.

Any help still appreciated. I don’t even get it to actually return the encrypted response let alone if that then works and the health check doesnt return an error.
@Franz

I think this works in the Cloud version as it has the crypto package already installed.

If you find a way to fix it, please let me know.

Yes importing the crypto module isnt possible for JS at least I didn’t find a way.

Any workarounds to realize this via N8N?

So you solved it with the Cloud Version before?

I maked it work, this is only for ping verification, later I’ll be working on handling navigation and callbacks too, but at least with tis you can publish your flow

Self hosted n8n, with environment variables:
- name: N8N_ENABLE_RAW_EXECUTION
value: “true”
- name: NODE_FUNCTION_ALLOW_BUILTIN
value: crypto
- name: NODE_FUNCTION_ALLOW_EXTERNAL
value: “”