Multi-Language Integration Examples
A complete, working Partner API client — encrypt, sign, send, decrypt — in six languages. Every example implements the exact same five steps described in Authentication & Security; pick your tab below.
The five steps, in every language
- Serialize your plaintext JSON body.
- Encrypt it with AES-256-GCM using your
SaltAESKey(64-char hex or 44-char base64 → 32 bytes), a random 12-byte IV, and AAD"<clientId>.<timestamp>". - Build the envelope
{"payload":"<base64 ciphertext>"}— by hand, with no extra whitespace, since the checksum must byte-match what the server re-serializes. - Sign — HMAC-SHA256 over
"<timestamp>.<clientId>.<envelope>"using yourSaltAESKeyas the secret, sent asX-Checksum. - Send
POST {baseUrl}/process{path}withX-Client-Id,X-Timestamp,X-IV,X-Tag,X-Checksum,X-Request-Id, then decrypt the response the same way in reverse (AAD uses the response'sX-Timestamp).
The envelope must be byte-exact
paymentSystem's checksum check re-serializes the parsed request body with a compact JSON encoder (no spaces). If your language's JSON library adds a space after : — Python's json.dumps does this by default — your checksum won't match what the server computes and every call fails with 401 Checksum invalid. Every example below builds the one-field envelope by hand (string concatenation) instead of trusting a JSON library's default formatting, which sidesteps the problem entirely.
Requirements per language
| Language | Minimum version | Dependencies |
|---|---|---|
| Node.js | 18+ (built-in fetch, crypto) | none |
| PHP | 7.1+ (AEAD tag support in openssl_encrypt) | ext-openssl, ext-curl (both usually bundled) |
| Java | 11+ (java.net.http.HttpClient) | none — javax.crypto is built in |
| Python | 3.8+ | pip install cryptography requests |
| Go | 1.21+ | none — standard library only |
| C# / .NET | .NET 8+ (AesGcm with explicit tag size) | none — System.Security.Cryptography is built in |
Full Client Example
Every tab below implements the identical function: encrypt a plaintext body, sign it, POST it to {baseUrl}/process{path}, and decrypt the response. The worked call at the bottom of each tab is an AEPS balance enquiry — swap path and the body for any endpoint.
import { createCipheriv, createDecipheriv, createHmac, randomBytes, randomUUID } from 'crypto';
function normalizeKey(raw) {
// SaltAESKey: 64-char hex or 44-char base64 -> 32 raw bytes
return /^[0-9a-fA-F]{64}$/.test(raw) ? Buffer.from(raw, 'hex') : Buffer.from(raw, 'base64');
}
async function callPartnerApi(baseUrl, path, plaintextBody, clientId, aesKeyRaw, checksumSecret) {
const aesKey = normalizeKey(aesKeyRaw);
const rawBody = JSON.stringify(plaintextBody);
const timestamp = Math.floor(Date.now() / 1000);
const iv = randomBytes(12);
const aad = `${clientId}.${timestamp}`;
const cipher = createCipheriv('aes-256-gcm', aesKey, iv);
cipher.setAAD(Buffer.from(aad, 'utf8'));
const ciphertext = Buffer.concat([cipher.update(rawBody, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
// Build the envelope by hand — must byte-match {"payload":"..."} exactly
const envelope = `{"payload":"${ciphertext.toString('base64')}"}`;
const canonical = `${timestamp}.${clientId}.${envelope}`;
const checksum = createHmac('sha256', checksumSecret).update(canonical).digest('hex');
const res = await fetch(`${baseUrl}/process${path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Id': clientId,
'X-Timestamp': String(timestamp),
'X-IV': iv.toString('base64'),
'X-Tag': tag.toString('base64'),
'X-Checksum': checksum,
'X-Request-Id': randomUUID(),
},
body: envelope,
});
return decryptResponse(res, clientId, aesKey);
}
async function decryptResponse(res, clientId, aesKey) {
const body = await res.json();
const respIv = res.headers.get('X-IV');
const respTag = res.headers.get('X-Tag');
const respTimestamp = res.headers.get('X-Timestamp');
if (!body.payload || !respIv || !respTag) return body; // unencrypted error object
const aad = `${clientId}.${respTimestamp}`;
const decipher = createDecipheriv('aes-256-gcm', aesKey, Buffer.from(respIv, 'base64'));
decipher.setAAD(Buffer.from(aad, 'utf8'));
decipher.setAuthTag(Buffer.from(respTag, 'base64'));
const plaintext = Buffer.concat([
decipher.update(Buffer.from(body.payload, 'base64')),
decipher.final(),
]).toString('utf8');
return JSON.parse(plaintext);
}
// Example: AEPS balance enquiry
const result = await callPartnerApi(
'https://api.bucksbox.in/gateway',
'/aeps/balance',
{
merchantId: 'MERCHANT123',
AadharNo: '999999999999',
TxnAmount: 0,
Latitude: '19.0760',
Longitude: '72.8777',
CustomerName: 'Ramesh Kumar',
PidData: '<PidData XML from the RD service>',
BankName: '918152',
},
process.env.BUCKSBOX_CLIENT_ID,
process.env.BUCKSBOX_AES_KEY,
process.env.BUCKSBOX_CHECKSUM_SECRET,
);
console.log(result);
<?php
declare(strict_types=1);
function normalizeKey(string $raw): string
{
// SaltAESKey: 64-char hex or 44-char base64 -> 32 raw bytes
if (strlen($raw) === 64 && ctype_xdigit($raw)) {
return hex2bin($raw);
}
return base64_decode($raw);
}
function callPartnerApi(string $baseUrl, string $path, array $plaintextBody, string $clientId, string $aesKeyRaw, string $checksumSecret): array
{
$aesKey = normalizeKey($aesKeyRaw);
$rawBody = json_encode($plaintextBody, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$timestamp = time();
$iv = random_bytes(12);
$aad = "{$clientId}.{$timestamp}";
$tag = '';
$ciphertext = openssl_encrypt($rawBody, 'aes-256-gcm', $aesKey, OPENSSL_RAW_DATA, $iv, $tag, $aad, 16);
if ($ciphertext === false) {
throw new RuntimeException('AES-256-GCM encryption failed');
}
// Build the envelope by hand — must byte-match {"payload":"..."} exactly
$envelope = '{"payload":"' . base64_encode($ciphertext) . '"}';
$canonical = "{$timestamp}.{$clientId}.{$envelope}";
$checksum = hash_hmac('sha256', $canonical, $checksumSecret);
$requestId = sprintf(
'%s-%s-%s-%s-%s',
bin2hex(random_bytes(4)), bin2hex(random_bytes(2)),
bin2hex(random_bytes(2)), bin2hex(random_bytes(2)), bin2hex(random_bytes(6))
);
$ch = curl_init("{$baseUrl}/process{$path}");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $envelope,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-Client-Id: {$clientId}",
"X-Timestamp: {$timestamp}",
'X-IV: ' . base64_encode($iv),
'X-Tag: ' . base64_encode($tag),
"X-Checksum: {$checksum}",
"X-Request-Id: {$requestId}",
],
]);
$raw = curl_exec($ch);
if ($raw === false) {
throw new RuntimeException('cURL error: ' . curl_error($ch));
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
$respHeaders = [];
foreach (explode("\r\n", substr($raw, 0, $headerSize)) as $line) {
if (strpos($line, ':') !== false) {
[$k, $v] = explode(':', $line, 2);
$respHeaders[strtolower(trim($k))] = trim($v);
}
}
return decryptResponse(substr($raw, $headerSize), $respHeaders, $clientId, $aesKey);
}
function decryptResponse(string $bodyText, array $respHeaders, string $clientId, string $aesKey): array
{
$respJson = json_decode($bodyText, true);
if (!isset($respJson['payload'], $respHeaders['x-iv'], $respHeaders['x-tag'])) {
return $respJson; // unencrypted error object
}
$aad = "{$clientId}.{$respHeaders['x-timestamp']}";
$plaintext = openssl_decrypt(
base64_decode($respJson['payload']), 'aes-256-gcm', $aesKey, OPENSSL_RAW_DATA,
base64_decode($respHeaders['x-iv']), base64_decode($respHeaders['x-tag']), $aad
);
if ($plaintext === false) {
throw new RuntimeException('Response decryption failed — invalid auth tag');
}
return json_decode($plaintext, true);
}
// Example: AEPS balance enquiry
$result = callPartnerApi(
'https://api.bucksbox.in/gateway',
'/aeps/balance',
[
'merchantId' => 'MERCHANT123',
'AadharNo' => '999999999999',
'TxnAmount' => 0,
'Latitude' => '19.0760',
'Longitude' => '72.8777',
'CustomerName' => 'Ramesh Kumar',
'PidData' => '<PidData XML from the RD service>',
'BankName' => '918152',
],
getenv('BUCKSBOX_CLIENT_ID'),
getenv('BUCKSBOX_AES_KEY'),
getenv('BUCKSBOX_CHECKSUM_SECRET')
);
print_r($result);
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.HexFormat;
import java.util.UUID;
public class BucksBoxPartnerClient {
private final String baseUrl;
private final String clientId;
private final byte[] aesKey; // 32 bytes
private final String checksumSecret;
private final HttpClient http = HttpClient.newHttpClient();
public BucksBoxPartnerClient(String baseUrl, String clientId, String aesKeyRaw, String checksumSecret) {
this.baseUrl = baseUrl;
this.clientId = clientId;
this.checksumSecret = checksumSecret;
// SaltAESKey: 64-char hex or 44-char base64 -> 32 raw bytes
this.aesKey = aesKeyRaw.length() == 64
? HexFormat.of().parseHex(aesKeyRaw)
: Base64.getDecoder().decode(aesKeyRaw);
}
public String call(String path, String plaintextJsonBody) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
String aad = clientId + "." + timestamp;
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(aesKey, "AES"), new GCMParameterSpec(128, iv));
cipher.updateAAD(aad.getBytes(StandardCharsets.UTF_8));
byte[] encrypted = cipher.doFinal(plaintextJsonBody.getBytes(StandardCharsets.UTF_8));
// Java appends the 16-byte GCM tag to the end of the ciphertext output
int tagLen = 16;
byte[] ciphertext = Arrays.copyOfRange(encrypted, 0, encrypted.length - tagLen);
byte[] tag = Arrays.copyOfRange(encrypted, encrypted.length - tagLen, encrypted.length);
// Build the envelope by hand — must byte-match {"payload":"..."} exactly
String envelope = "{\"payload\":\"" + Base64.getEncoder().encodeToString(ciphertext) + "\"}";
String canonical = timestamp + "." + clientId + "." + envelope;
String checksum = hmacSha256Hex(canonical, checksumSecret);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/process" + path))
.header("Content-Type", "application/json")
.header("X-Client-Id", clientId)
.header("X-Timestamp", String.valueOf(timestamp))
.header("X-IV", Base64.getEncoder().encodeToString(iv))
.header("X-Tag", Base64.getEncoder().encodeToString(tag))
.header("X-Checksum", checksum)
.header("X-Request-Id", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.ofString(envelope))
.build();
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
return decryptResponse(response);
}
private String decryptResponse(HttpResponse<String> response) throws Exception {
String bodyText = response.body();
String payload = extractPayload(bodyText); // swap for Jackson/Gson in real code
String respIv = response.headers().firstValue("X-IV").orElse(null);
String respTag = response.headers().firstValue("X-Tag").orElse(null);
String respTimestamp = response.headers().firstValue("X-Timestamp").orElse(null);
if (payload == null || respIv == null || respTag == null) {
return bodyText; // unencrypted error object
}
String aad = clientId + "." + respTimestamp;
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(aesKey, "AES"),
new GCMParameterSpec(128, Base64.getDecoder().decode(respIv)));
cipher.updateAAD(aad.getBytes(StandardCharsets.UTF_8));
byte[] ciphertext = Base64.getDecoder().decode(payload);
byte[] tag = Base64.getDecoder().decode(respTag);
byte[] combined = new byte[ciphertext.length + tag.length];
System.arraycopy(ciphertext, 0, combined, 0, ciphertext.length);
System.arraycopy(tag, 0, combined, ciphertext.length, tag.length);
return new String(cipher.doFinal(combined), StandardCharsets.UTF_8);
}
private static String hmacSha256Hex(String data, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return HexFormat.of().formatHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
// Minimal {"payload":"..."} extractor to avoid adding a JSON-library dependency to this
// example — use Jackson's ObjectMapper or Gson in production code.
private static String extractPayload(String json) {
int i = json.indexOf("\"payload\"");
if (i < 0) return null;
int start = json.indexOf('"', json.indexOf(':', i) + 1) + 1;
int end = json.indexOf('"', start);
return json.substring(start, end);
}
public static void main(String[] args) throws Exception {
BucksBoxPartnerClient client = new BucksBoxPartnerClient(
"https://api.bucksbox.in/gateway",
System.getenv("BUCKSBOX_CLIENT_ID"),
System.getenv("BUCKSBOX_AES_KEY"),
System.getenv("BUCKSBOX_CHECKSUM_SECRET")
);
// Example: AEPS balance enquiry
String body = """
{"merchantId":"MERCHANT123","AadharNo":"999999999999","TxnAmount":0,
"Latitude":"19.0760","Longitude":"72.8777","CustomerName":"Ramesh Kumar",
"PidData":"<PidData XML from the RD service>","BankName":"918152"}""";
System.out.println(client.call("/aeps/balance", body));
}
}
import base64
import hashlib
import hmac
import json
import os
import time
import uuid
import requests
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def normalize_key(raw: str) -> bytes:
"""SaltAESKey: 64-char hex or 44-char base64 -> 32 raw bytes."""
if len(raw) == 64:
try:
return bytes.fromhex(raw)
except ValueError:
pass
return base64.b64decode(raw)
def call_partner_api(base_url: str, path: str, plaintext_body: dict,
client_id: str, aes_key_raw: str, checksum_secret: str) -> dict:
aes_key = normalize_key(aes_key_raw)
# separators=(",", ":") -> compact JSON, no spaces, matching the server's re-serialization
raw_body = json.dumps(plaintext_body, separators=(",", ":"))
timestamp = int(time.time())
iv = os.urandom(12)
aad = f"{client_id}.{timestamp}".encode("utf-8")
aesgcm = AESGCM(aes_key)
# AESGCM.encrypt() returns ciphertext with the 16-byte tag appended
sealed = aesgcm.encrypt(iv, raw_body.encode("utf-8"), aad)
ciphertext, tag = sealed[:-16], sealed[-16:]
# Build the envelope by hand — must byte-match {"payload":"..."} exactly
envelope = '{"payload":"' + base64.b64encode(ciphertext).decode("ascii") + '"}'
canonical = f"{timestamp}.{client_id}.{envelope}"
checksum = hmac.new(checksum_secret.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json",
"X-Client-Id": client_id,
"X-Timestamp": str(timestamp),
"X-IV": base64.b64encode(iv).decode("ascii"),
"X-Tag": base64.b64encode(tag).decode("ascii"),
"X-Checksum": checksum,
"X-Request-Id": str(uuid.uuid4()),
}
response = requests.post(f"{base_url}/process{path}", data=envelope, headers=headers)
return decrypt_response(response, client_id, aes_key)
def decrypt_response(response: requests.Response, client_id: str, aes_key: bytes) -> dict:
resp_iv = response.headers.get("X-IV")
resp_tag = response.headers.get("X-Tag")
resp_timestamp = response.headers.get("X-Timestamp")
body = response.json()
if "payload" not in body or not resp_iv or not resp_tag:
return body # unencrypted error object
aad = f"{client_id}.{resp_timestamp}".encode("utf-8")
aesgcm = AESGCM(aes_key)
sealed = base64.b64decode(body["payload"]) + base64.b64decode(resp_tag)
plaintext = aesgcm.decrypt(base64.b64decode(resp_iv), sealed, aad)
return json.loads(plaintext)
# Example: AEPS balance enquiry
result = call_partner_api(
base_url="https://api.bucksbox.in/gateway",
path="/aeps/balance",
plaintext_body={
"merchantId": "MERCHANT123",
"AadharNo": "999999999999",
"TxnAmount": 0,
"Latitude": "19.0760",
"Longitude": "72.8777",
"CustomerName": "Ramesh Kumar",
"PidData": "<PidData XML from the RD service>",
"BankName": "918152",
},
client_id=os.environ["BUCKSBOX_CLIENT_ID"],
aes_key_raw=os.environ["BUCKSBOX_AES_KEY"],
checksum_secret=os.environ["BUCKSBOX_CHECKSUM_SECRET"],
)
print(result)
package bucksbox
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
type PartnerClient struct {
BaseURL string
ClientID string
AESKey []byte // 32 bytes, normalized from SaltAESKey
ChecksumSecret string
}
// NewPartnerClient normalizes SaltAESKey: 64-char hex or 44-char base64 -> 32 raw bytes.
func NewPartnerClient(baseURL, clientID, aesKeyRaw, checksumSecret string) (*PartnerClient, error) {
var key []byte
var err error
if len(aesKeyRaw) == 64 {
key, err = hex.DecodeString(aesKeyRaw)
}
if err != nil || len(aesKeyRaw) != 64 {
key, err = base64.StdEncoding.DecodeString(aesKeyRaw)
}
if err != nil || len(key) != 32 {
return nil, fmt.Errorf("invalid SaltAESKey")
}
return &PartnerClient{BaseURL: baseURL, ClientID: clientID, AESKey: key, ChecksumSecret: checksumSecret}, nil
}
func (c *PartnerClient) Call(path string, plaintextBody map[string]interface{}) (map[string]interface{}, error) {
rawBody, err := json.Marshal(plaintextBody) // Go's json.Marshal is compact by default — no spaces
if err != nil {
return nil, err
}
timestamp := time.Now().Unix()
iv := make([]byte, 12)
if _, err := rand.Read(iv); err != nil {
return nil, err
}
aad := []byte(fmt.Sprintf("%s.%d", c.ClientID, timestamp))
block, err := aes.NewCipher(c.AESKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Go's Seal appends the 16-byte tag to the end of the ciphertext
sealed := gcm.Seal(nil, iv, rawBody, aad)
ciphertext, tag := sealed[:len(sealed)-16], sealed[len(sealed)-16:]
// Build the envelope by hand — must byte-match {"payload":"..."} exactly
envelope := `{"payload":"` + base64.StdEncoding.EncodeToString(ciphertext) + `"}`
canonical := fmt.Sprintf("%d.%s.%s", timestamp, c.ClientID, envelope)
mac := hmac.New(sha256.New, []byte(c.ChecksumSecret))
mac.Write([]byte(canonical))
checksum := hex.EncodeToString(mac.Sum(nil))
requestID := make([]byte, 16) // any unique string works — a UUID v4 is just convention
_, _ = rand.Read(requestID)
req, err := http.NewRequest("POST", c.BaseURL+"/process"+path, bytes.NewBufferString(envelope))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Client-Id", c.ClientID)
req.Header.Set("X-Timestamp", strconv.FormatInt(timestamp, 10))
req.Header.Set("X-IV", base64.StdEncoding.EncodeToString(iv))
req.Header.Set("X-Tag", base64.StdEncoding.EncodeToString(tag))
req.Header.Set("X-Checksum", checksum)
req.Header.Set("X-Request-Id", hex.EncodeToString(requestID))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return c.decryptResponse(resp)
}
func (c *PartnerClient) decryptResponse(resp *http.Response) (map[string]interface{}, error) {
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var envelope struct {
Payload string `json:"payload"`
}
_ = json.Unmarshal(bodyBytes, &envelope)
respIV := resp.Header.Get("X-IV")
respTag := resp.Header.Get("X-Tag")
respTimestamp := resp.Header.Get("X-Timestamp")
if envelope.Payload == "" || respIV == "" || respTag == "" {
var errBody map[string]interface{}
_ = json.Unmarshal(bodyBytes, &errBody)
return errBody, nil // unencrypted error object
}
aad := []byte(fmt.Sprintf("%s.%s", c.ClientID, respTimestamp))
ivBytes, _ := base64.StdEncoding.DecodeString(respIV)
tagBytes, _ := base64.StdEncoding.DecodeString(respTag)
ciphertextBytes, _ := base64.StdEncoding.DecodeString(envelope.Payload)
sealed := append(ciphertextBytes, tagBytes...)
block, err := aes.NewCipher(c.AESKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
plaintext, err := gcm.Open(nil, ivBytes, sealed, aad)
if err != nil {
return nil, fmt.Errorf("response decryption failed: %w", err)
}
var result map[string]interface{}
if err := json.Unmarshal(plaintext, &result); err != nil {
return nil, err
}
return result, nil
}
using System;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class BucksBoxPartnerClient
{
private readonly string _baseUrl;
private readonly string _clientId;
private readonly byte[] _aesKey; // 32 bytes
private readonly string _checksumSecret;
private readonly HttpClient _http = new HttpClient();
public BucksBoxPartnerClient(string baseUrl, string clientId, string aesKeyRaw, string checksumSecret)
{
_baseUrl = baseUrl;
_clientId = clientId;
_checksumSecret = checksumSecret;
// SaltAESKey: 64-char hex or 44-char base64 -> 32 raw bytes
_aesKey = aesKeyRaw.Length == 64
? Convert.FromHexString(aesKeyRaw)
: Convert.FromBase64String(aesKeyRaw);
}
public async Task<string> CallAsync(string path, string plaintextJsonBody)
{
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
byte[] iv = RandomNumberGenerator.GetBytes(12);
byte[] aad = Encoding.UTF8.GetBytes($"{_clientId}.{timestamp}");
byte[] plaintext = Encoding.UTF8.GetBytes(plaintextJsonBody);
byte[] ciphertext = new byte[plaintext.Length];
byte[] tag = new byte[16];
// .NET 8+: AesGcm requires an explicit tag size. On .NET 6/7 use `new AesGcm(_aesKey)` instead.
using (var gcm = new AesGcm(_aesKey, tag.Length))
{
gcm.Encrypt(iv, plaintext, ciphertext, tag, aad);
}
// Build the envelope by hand — must byte-match {"payload":"..."} exactly
string envelope = "{\"payload\":\"" + Convert.ToBase64String(ciphertext) + "\"}";
string canonical = $"{timestamp}.{_clientId}.{envelope}";
string checksum = HmacSha256Hex(canonical, _checksumSecret);
var request = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/process{path}")
{
Content = new StringContent(envelope, Encoding.UTF8, "application/json"),
};
request.Headers.Add("X-Client-Id", _clientId);
request.Headers.Add("X-Timestamp", timestamp.ToString());
request.Headers.Add("X-IV", Convert.ToBase64String(iv));
request.Headers.Add("X-Tag", Convert.ToBase64String(tag));
request.Headers.Add("X-Checksum", checksum);
request.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());
HttpResponseMessage response = await _http.SendAsync(request);
return await DecryptResponseAsync(response);
}
private async Task<string> DecryptResponseAsync(HttpResponseMessage response)
{
string bodyText = await response.Content.ReadAsStringAsync();
using JsonDocument doc = JsonDocument.Parse(bodyText);
bool hasIv = response.Headers.TryGetValues("X-IV", out var ivValues);
bool hasTag = response.Headers.TryGetValues("X-Tag", out var tagValues);
response.Headers.TryGetValues("X-Timestamp", out var tsValues);
if (!doc.RootElement.TryGetProperty("payload", out var payloadProp) || !hasIv || !hasTag)
{
return bodyText; // unencrypted error object
}
string respTimestamp = tsValues?.FirstOrDefault();
byte[] aad = Encoding.UTF8.GetBytes($"{_clientId}.{respTimestamp}");
byte[] respIv = Convert.FromBase64String(ivValues!.First());
byte[] respTag = Convert.FromBase64String(tagValues!.First());
byte[] respCiphertext = Convert.FromBase64String(payloadProp.GetString()!);
byte[] plaintext = new byte[respCiphertext.Length];
using (var gcm = new AesGcm(_aesKey, respTag.Length))
{
gcm.Decrypt(respIv, respCiphertext, respTag, plaintext, aad);
}
return Encoding.UTF8.GetString(plaintext);
}
private static string HmacSha256Hex(string data, string secret)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
return Convert.ToHexString(hash).ToLowerInvariant();
}
}
// Example: AEPS balance enquiry
var client = new BucksBoxPartnerClient(
"https://api.bucksbox.in/gateway",
Environment.GetEnvironmentVariable("BUCKSBOX_CLIENT_ID"),
Environment.GetEnvironmentVariable("BUCKSBOX_AES_KEY"),
Environment.GetEnvironmentVariable("BUCKSBOX_CHECKSUM_SECRET"));
string body = "{\"merchantId\":\"MERCHANT123\",\"AadharNo\":\"999999999999\",\"TxnAmount\":0," +
"\"Latitude\":\"19.0760\",\"Longitude\":\"72.8777\",\"CustomerName\":\"Ramesh Kumar\"," +
"\"PidData\":\"<PidData XML from the RD service>\",\"BankName\":\"918152\"}";
Console.WriteLine(await client.CallAsync("/aeps/balance", body));
Common pitfalls
- Checksum computed over the plaintext, not the envelope. See the warning above — sign the final
{"payload":"..."}string, not your original JSON body. - Wrong AAD. Requests use
"<clientId>.<your X-Timestamp>"; decrypting a response uses"<clientId>.<the response's own X-Timestamp>"— these are two different timestamps, easy to mix up if you reuse a variable. - Key decoded wrong.
SaltAESKeyis either a 64-char hex string or a 44-char base64 string — both decode to exactly 32 bytes. Decoding a hex key as base64 (or vice versa) silently produces the wrong key length and every call fails at the crypto layer, not the network layer. - Tag/ciphertext split. Java, Go, and Python's
cryptographylibrary all return ciphertext-with-tag-appended from a single encrypt call — you must split the last 16 bytes off before sendingX-Tagandpayloadseparately. PHP'sopenssl_encryptand C#'sAesGcminstead give you the tag as a separate out-parameter/buffer — no splitting needed. Mixing up which convention your language uses is the most common source of "encryption failed" bugs when porting from another language's example.
Next steps
- Authentication & Security — the full protocol reference these examples implement.
- Endpoint Reference — every callable path and its plaintext body.
- AEPS / DMT / UPI — full request/response documentation per service.