Skip to Content
AuthenticationAPI Encryption

API Encryption

Some merchant accounts are enrolled in API payload encryption. When encryption is enabled for your business, the Merchant API decrypts non-GET request bodies and encrypts successful HTTP 200 responses.

Current Server Behaviour

The implementation uses Laravel’s encrypter with AES-256-GCM. The encryption key must be exactly 32 bytes. For enrolled businesses:

  • POST and PUT request bodies must be encrypted before they reach the controller.
  • GET request bodies are not decrypted.
  • Successful HTTP 200 responses are encrypted and returned as application/octet-stream.
  • The server does not rely on X-Encrypted-Payload or X-Encrypt-Response headers to decide whether encryption applies; enrollment controls the behaviour.

If the encrypted body cannot be decrypted, the API returns:

{ "error": "Failed to decrypt payload." }

If the encryption key is missing or invalid, the API returns:

{ "error": "Invalid or missing encryption key. Kindly reach out to our customer care" }

Encrypted Payload Format

Send the Laravel-compatible encrypted string as the raw request body. After base64 decoding, the payload has this JSON shape:

{ "iv": "base64-iv", "value": "base64-ciphertext", "mac": "", "tag": "base64-auth-tag" }

The full JSON object is then base64 encoded and sent as the HTTP body.

Example: Plain Payout Body Before Encryption

{ "account_number": "0123456789", "account_name": "John Doe", "bank_code": "000013", "amount": "100", "currency": "NGN", "sender_name": "Jane Smith", "narration": "Payment for services", "reference": "ngn_ref_123" }

Example Request

curl -X POST "https://api.cashonrails.com/api/v1/bank_transfer" \ -H "Authorization: Bearer <secret_key>" \ -H "Content-Type: application/octet-stream" \ -H "X-Signature: <base64-rsa-signature>" \ -H "Idempotency-Key: ngn_ref_123" \ --data-binary '<laravel-compatible-encrypted-payload>'

For payout requests, sign the exact encrypted request body if encryption is enabled for your account, because the server verifies X-Signature against the raw body it receives.

Node.js Encryption Example

const crypto = require('crypto'); function encryptForCashonrails(payload, encryptionKey) { const key = Buffer.from(encryptionKey, 'utf8'); if (key.length !== 32) throw new Error('Encryption key must be 32 bytes'); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); const value = Buffer.concat([ cipher.update(JSON.stringify(payload), 'utf8'), cipher.final() ]).toString('base64'); const envelope = { iv: iv.toString('base64'), value, mac: '', tag: cipher.getAuthTag().toString('base64') }; return Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64'); }

Decrypting a Response

Treat an encrypted 200 response body as the same base64-encoded envelope and decrypt it with the same 32-byte encryption key. The decrypted value is the original JSON response string.

Common Pitfalls

  • Sending normal JSON after API encryption has been enabled for the business.
  • Using a key shorter or longer than 32 bytes.
  • Signing a different string than the body sent to the API.
  • Parsing encrypted 200 responses as JSON before decrypting them.
  • Assuming encryption replaces HTTPS, bearer auth, webhook verification, or RSA signing. It does not.
Last updated on