Encryption API
Manage business public keys used for end-to-end encryption in WhatsApp Flows, ensuring secure data collection and transmission.
Official Documentation: WhatsApp Business Encryption API
Overview
The Encryption API manages public keys for secure Flow data handling:
- Generate Keys: Create encryption key pairs using built-in helper
- Set Public Key: Upload business public key for Flows encryption
- Get Public Key: Retrieve current active encryption key
- Secure Flows: Enable end-to-end encryption for Flow submissions
Endpoints
GET /{PHONE_NUMBER_ID}/whatsapp_business_encryptionPOST /{PHONE_NUMBER_ID}/whatsapp_business_encryptionImportant Notes
Quick Start
import WhatsApp from 'meta-cloud-api';
const client = new WhatsApp({ accessToken: process.env.CLOUD_API_ACCESS_TOKEN!, phoneNumberId: Number(process.env.WA_PHONE_NUMBER_ID), businessAcctId: process.env.WA_BUSINESS_ACCOUNT_ID!,});
// Generate encryption key pairconst keyPair = client.generateEncryption();
// Store private key securelyawait storeSecurely('WA_PRIVATE_KEY', keyPair.privateKey);
// Upload public keyawait client.encryption.setEncryptionPublicKey(keyPair.publicKey);
// Verify uploadconst currentKey = await client.encryption.getEncryptionPublicKey();console.log('Active public key:', currentKey);Generate Encryption Keys
Create a new encryption key pair for Flows.
// Generate key pairconst { publicKey, privateKey } = client.generateEncryption();
console.log('Public key:', publicKey);console.log('Private key:', privateKey); // Keep this secret!
// Store private key securelyawait secureStorage.set('whatsapp-private-key', privateKey);Set Public Key
Upload the business public key to enable Flow encryption.
Basic Upload
const keyPair = client.generateEncryption();
await client.encryption.setEncryptionPublicKey(keyPair.publicKey);
console.log('Public key uploaded successfully');Complete Setup Flow
async function setupFlowEncryption() { // Generate keys const keyPair = client.generateEncryption();
// Store private key in secure storage await storePrivateKey(keyPair.privateKey);
// Upload public key to WhatsApp await client.encryption.setEncryptionPublicKey(keyPair.publicKey);
console.log('Flow encryption configured');
return { publicKey: keyPair.publicKey, privateKeyStored: true, };}
async function storePrivateKey(privateKey: string) { // Use AWS Secrets Manager, Azure Key Vault, etc. await secretsManager.createSecret({ name: 'whatsapp/flow-private-key', secretString: privateKey, });}Get Public Key
Retrieve the currently active public key.
const publicKey = await client.encryption.getEncryptionPublicKey();
console.log('Current public key:', publicKey);Verify Key Configuration
async function verifyEncryptionSetup() { try { const publicKey = await client.encryption.getEncryptionPublicKey();
if (publicKey) { console.log('Encryption is configured'); return true; } else { console.log('No encryption key set'); return false; } } catch (error) { console.error('Failed to retrieve public key:', error); return false; }}Decrypt Flow Data
Decrypt encrypted data received from Flow submissions.
import crypto from 'crypto';
async function decryptFlowData( encryptedData: string, aesKey: string, iv: string): Promise<any> { // Retrieve private key const privateKey = await retrievePrivateKey();
// Decrypt AES key with RSA private key const decryptedAesKey = crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256', }, Buffer.from(aesKey, 'base64') );
// Decrypt data with AES key const decipher = crypto.createDecipheriv( 'aes-256-gcm', decryptedAesKey, Buffer.from(iv, 'base64') );
let decrypted = decipher.update(encryptedData, 'base64', 'utf8'); decrypted += decipher.final('utf8');
return JSON.parse(decrypted);}
async function retrievePrivateKey(): Promise<string> { // Retrieve from secure storage const secret = await secretsManager.getSecretValue({ SecretId: 'whatsapp/flow-private-key', });
return secret.SecretString!;}Complete Flow Webhook Handler
Handle encrypted Flow submissions in your webhook.
import express from 'express';import crypto from 'crypto';
const app = express();app.use(express.json());
app.post('/flow-webhook', async (req, res) => { const { encrypted_flow_data, encrypted_aes_key, initial_vector } = req.body;
if (!encrypted_flow_data) { return res.json({ version: '3.0' }); }
try { // Decrypt the flow data const decryptedData = await decryptFlowData( encrypted_flow_data, encrypted_aes_key, initial_vector );
// Process the decrypted data console.log('Decrypted flow data:', decryptedData);
// Your business logic here const result = await processFlowSubmission(decryptedData);
// Return response res.json({ version: '3.0', data: result, }); } catch (error) { console.error('Decryption failed:', error); res.status(500).json({ error: 'Decryption failed' }); }});
async function processFlowSubmission(data: any) { // Process the decrypted form data // Save to database, trigger actions, etc. return { success: true };}Key Rotation
Periodically rotate encryption keys for enhanced security.
async function rotateEncryptionKeys() { // Generate new key pair const newKeyPair = client.generateEncryption();
// Store new private key await secretsManager.updateSecret({ SecretId: 'whatsapp/flow-private-key', SecretString: newKeyPair.privateKey, });
// Upload new public key await client.encryption.setEncryptionPublicKey(newKeyPair.publicKey);
// Archive old private key for historical data await archiveOldKey();
console.log('Encryption keys rotated');}
// Rotate keys every 90 dayssetInterval(rotateEncryptionKeys, 90 * 24 * 60 * 60 * 1000);Response Formats
Get Public Key Response
{ public_key: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...'}Set Public Key Response
{ success: true}Error Handling
try { await client.encryption.setEncryptionPublicKey(publicKey);} catch (error) { if (error.response) { const { code, message } = error.response.data.error;
switch (code) { case 131031: console.error('Invalid public key format'); break; case 100: console.error('Invalid parameter'); break; default: console.error(`Error ${code}: ${message}`); } }}Best Practices
-
Store Private Keys Securely: Use proper secrets management
import { SecretsManager } from 'aws-sdk';async function storePrivateKey(privateKey: string) {const secretsManager = new SecretsManager();await secretsManager.createSecret({Name: 'whatsapp/flow-encryption-key',SecretString: privateKey,});} -
Never Expose Private Keys: Keep them out of logs and code
// ❌ Badconsole.log('Private key:', privateKey);// ✅ Goodconsole.log('Private key stored securely'); -
Use Environment-Specific Keys: Different keys per environment
const keyPair = client.generateEncryption();if (process.env.NODE_ENV === 'production') {await storeInProduction(keyPair.privateKey);} else {await storeInDevelopment(keyPair.privateKey);} -
Implement Key Backup: Maintain secure key backups
async function backupEncryptionKey() {const privateKey = await retrievePrivateKey();// Backup to multiple secure locationsawait primaryStorage.store(privateKey);await backupStorage.store(privateKey);await offlineVault.store(privateKey);} -
Test Decryption: Verify keys work correctly
async function testEncryption() {const keyPair = client.generateEncryption();// Upload public keyawait client.encryption.setEncryptionPublicKey(keyPair.publicKey);// Test dataconst testData = { message: 'test' };// Encrypt with public keyconst encrypted = encryptWithPublicKey(JSON.stringify(testData),keyPair.publicKey);// Decrypt with private keyconst decrypted = decryptWithPrivateKey(encrypted,keyPair.privateKey);console.log('Encryption test:', JSON.parse(decrypted));} -
Handle Decryption Errors Gracefully
async function safeDecrypt(encryptedData: string) {try {return await decryptFlowData(encryptedData);} catch (error) {console.error('Decryption failed:', error.message);// Check if key rotation occurredconst isKeyRotated = await checkKeyRotation();if (isKeyRotated) {// Try with previous keyreturn await decryptWithPreviousKey(encryptedData);}throw error;}}
Security Considerations
Key Management
- Generate keys once per phone number
- Store private keys in secure vaults (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault)
- Never commit keys to version control
- Use separate keys for different environments
- Rotate keys periodically (every 90 days recommended)
Access Controls
// Implement strict access controlsasync function setEncryptionKey(publicKey: string, userId: string) { // Verify authorization if (!hasPermission(userId, 'manage:encryption')) { throw new Error('Unauthorized: Cannot manage encryption keys'); }
// Audit log await auditLog.record({ action: 'set_encryption_key', user: userId, timestamp: new Date(), });
// Set key await client.encryption.setEncryptionPublicKey(publicKey);}Compliance
- Maintain audit logs of key operations
- Document key rotation procedures
- Implement key recovery processes
- Follow data protection regulations (GDPR, CCPA, etc.)
Related Documentation
- Flows API - Create and manage Flows
- Webhooks - Handle Flow submissions
- Security Guide - Comprehensive security practices
- Flow Builder - Official Flow documentation
Source Code
View the source code on GitHub: EncryptionApi.ts