DocsEncryption
Security

Payload Encryption

Encrypt API request bodies before sending them to PayoFlux, and decrypt encrypted responses on your server. Applies to all payment API endpoints.

Overview

PayoFlux supports end-to-end payload encryption for server-to-server integrations. Instead of sending plain JSON payment data over the wire, you encrypt the full request body on your server, send the ciphertext to PayoFlux, and receive an encrypted response that you decrypt locally.

  1. Build your request payload as documented on each API page (e.g. Charge, Refunds, Status).
  2. Serialize the payload to a JSON string and encrypt it with your Encryption Key.
  3. Send the encrypted string wrapped in a payload field to the API endpoint.
  4. PayoFlux decrypts the request, processes the payment, and returns an encrypted response.
  5. Decrypt the response on your server before using the result.

Important

Encryption is performed on your server only. Do not share your Encryption Key or API secret keys with anyone. Never expose them in client-side code, mobile apps, or browser JavaScript.

Encryption Key

Each merchant receives a unique Encryption Key when onboarding is complete. You can view and copy it from the merchant dashboard at any time.

Where to find your key

  1. Sign in to the PayoFlux merchant dashboard
  2. Complete merchant onboarding if you have not already
  3. Go to Config (Settings → API Configuration) in the sidebar
  4. Copy the Encryption Key shown alongside your Test and Live secret keys

Example Encryption Key format

a1b2c3d4e5f6789012345678abcdef01a1b2c3d4e5f6789012345678abcdef01

Important

The Encryption Key is a 64-character hexadecimal string (256-bit). Do not share it with anyone — treat it with the same confidentiality as your API secret keys. Store it securely in environment variables or a secrets manager on your server only. It is separate from your API key and webhook hash.

Algorithm & Format

PayoFlux uses AES-256-CBC for payload encryption and decryption.

PropertyValue
AlgorithmAES-256-CBC
Key derivation64-character hex string decoded to 32 raw bytes (legacy 32-char UTF-8 keys still supported)
IV16 random bytes, unique per encryption operation
Ciphertext encodingHexadecimal
Output format{iv_hex}:{ciphertext_hex}

Note

A fresh random IV is generated for every request and every response. Never reuse an IV with the same key.

Encrypting Requests

Before calling any payment API endpoint, encrypt your JSON request body and wrap it in a payload field.

Step-by-step

  1. Build the JSON object exactly as shown on the relevant API page (Charge, Refunds, etc.).
  2. Call JSON.stringify() (or equivalent) to produce a compact JSON string.
  3. Encrypt the string using AES-256-CBC with your Encryption Key.
  4. Format the result as {iv_hex}:{ciphertext_hex}.
  5. POST to the API endpoint with body {"payload": "<encrypted_string>"}.

Encrypted request body

Request body

{
  "payload": "3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a:8e4f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0"
}

Required headers

Headers

Content-Type: application/json
Authorization: Bearer your_api_key_or_sandbox_api_key

Important

Do not share your API key or Encryption Key with anyone. Use them only on your trusted backend server. Never send keys in emails, chat messages, or client-side code.

Example cURL

Encrypted charge request

curl -X POST https://api.payoflux.com/api/test/charge \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_api_key_or_sandbox_api_key" \
  -d '{
    "payload": "3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8:8e4f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a"
  }'

Decrypting Responses

Every authenticated payment API response is encrypted — including success, declined/failed, blocked, and validation errors. Decrypt the payload field to obtain the standard response object documented on each API page.

Encrypted response body (success)

Response body

{
  "success": true,
  "payload": "7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7:4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8"
}

Encrypted response body (declined / error)

When the payment is declined or the request fails validation, the outer envelope still uses a payload field, but success is false:

Encrypted declined/error wire format

{
  "success": false,
  "payload": "<iv_hex>:<ciphertext_hex>"
}

After decryption — success

Decrypted success response (Charge)

{
  "success": true,
  "status": "SUCCESS",
  "data": {
    "amount": 100,
    "currency": "USD",
    "merchantOrderId": "ORD-12345",
    "transactionId": "FP260231SJWKL80027"
  }
}

After decryption — declined

Decrypted declined response (Charge)

{
  "success": false,
  "status": "FAILED",
  "message": "Transaction declined by issuer",
  "error": {
    "code": "DECLINED",
    "message": "Transaction declined by issuer"
  },
  "data": {
    "amount": 100.5,
    "currency": "USD",
    "merchantOrderId": "ORD-12345",
    "transactionId": "FP260231SJWKL80027",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@example.com",
    "webhookUrl": "https://your-domain.com/webhook"
  }
}

Note

Always check for a payload field and decrypt it before parsing. Do not assume declined or validation errors arrive as plain JSON — they use the same encryption contract as success responses when your API key is valid.

Important

Missing or invalid API keys may return an unencrypted error (no merchant encryption key is available). Once authenticated, all subsequent responses for that request are encrypted.

Code Examples

Complete examples showing how to encrypt a request, call the API, and decrypt the response. Select a language tab below.

Encrypt & decrypt

const crypto = require('crypto');

/**
 * Derive a 32-byte AES key from the merchant Encryption Key.
 * 64-char hex keys are decoded directly; legacy 32-char keys are UTF-8 padded.
 */
function deriveKey(encryptionKey) {
  if (/^[0-9a-fA-F]{64}$/.test(encryptionKey)) {
    return Buffer.from(encryptionKey, 'hex');
  }
  return Buffer.from(
    encryptionKey.padEnd(32, '0').substring(0, 32),
    'utf-8',
  );
}

/**
 * Encrypt a plain-text JSON string using AES-256-CBC.
 * Returns "{iv_hex}:{ciphertext_hex}".
 */
function encryptPayload(plainText, encryptionKey) {
  const keyBuffer = deriveKey(encryptionKey);
  const iv = crypto.randomBytes(16); // fresh IV per request
  const cipher = crypto.createCipheriv('aes-256-cbc', keyBuffer, iv);
  let encrypted = cipher.update(plainText, 'utf-8', 'hex');
  encrypted += cipher.final('hex');
  return `${iv.toString('hex')}:${encrypted}`;
}

/**
 * Decrypt an encrypted payload string back to plain text.
 */
function decryptPayload(encryptedPayload, encryptionKey) {
  const keyBuffer = deriveKey(encryptionKey);
  const [ivHex, encrypted] = encryptedPayload.split(':');
  if (!ivHex || !encrypted) {
    throw new Error('Invalid encrypted payload format');
  }
  const iv = Buffer.from(ivHex, 'hex');
  const decipher = crypto.createDecipheriv('aes-256-cbc', keyBuffer, iv);
  let decrypted = decipher.update(encrypted, 'hex', 'utf-8');
  decrypted += decipher.final('utf-8');
  return decrypted;
}

// 1. Load your Encryption Key from environment (Dashboard → Config)
const encryptionKey = process.env.PAYOFLUX_ENCRYPTION_KEY;

// 2. Build the request body exactly as documented on the API page
const requestBody = {
  merchantOrderId: 'ORD-12345',
  payment: { amount: 100, currency: 'USD' },
  card: {
    number: '4111111111111111',
    expiryMonth: 12,
    expiryYear: 2030,
    cvv: '123',
    holderName: 'John Doe',
  },
  customer: {
    firstName: 'John',
    lastName: 'Doe',
    email: 'john@example.com',
    ip: '185.23.44.91',
  },
  billing: {
    addressLine1: '12, Hill',
    city: 'Nevada',
    state: 'Nevada',
    country: 'US',
    postalCode: '12345',
  },
  callback: {
    webhookUrl: 'https://your-domain.com/webhook',
    returnUrl: 'https://your-domain.com/payment/callback',
  },
};

// 3. Encrypt the JSON string and wrap it in a payload field
const encrypted = encryptPayload(JSON.stringify(requestBody), encryptionKey);

// 4. Send the encrypted request to PayoFlux
const response = await fetch('https://api.payoflux.com/api/test/charge', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: 'Bearer your_api_key_or_sandbox_api_key',
  },
  body: JSON.stringify({ payload: encrypted }),
});

// 5. Decrypt the encrypted response payload field
const { payload: encryptedResponse } = await response.json();
const decrypted = JSON.parse(decryptPayload(encryptedResponse, encryptionKey));
console.log(decrypted);

Applies To

Payload encryption applies to all authenticated payment API endpoints documented in this portal:

Note

Webhook delivery uses HMAC-SHA256 signing (not payload encryption). See the Webhook page for verification details.

Security Best Practices

  • Do not share your API key or Encryption Key with anyone — including in email, chat, or with untrusted third parties.
  • Perform all encryption and decryption on your backend server — never in the browser or mobile app.
  • Store the Encryption Key in environment variables or a dedicated secrets manager.
  • Use HTTPS for all API calls; encryption protects the payload content, not the transport layer alone.
  • Rotate keys immediately if you suspect the Encryption Key has been compromised — contact support to regenerate.
  • Log decrypted payloads only in secure, PCI-compliant environments; never log full card numbers or CVV.

Critical

Sending plain (unencrypted) JSON to endpoints that expect encrypted payloads will result in a 400 Bad Request error. Always encrypt before sending when payload encryption is enabled for your merchant account.