BucksBox Developer DocsBucksBox Developer Docs
Getting Started
Merchant API
Partner API
SDK
GitHub
Getting Started
Merchant API
Partner API
SDK
GitHub
  • Overview
  • Authentication & Security
  • Endpoint Reference
  • Multi-Language Integration
  • Account

    • Profile
    • Balance
    • Merchants
    • Transactions
    • Commission
  • On behalf of merchants

    • UPI
    • AEPS
    • DMT

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

  1. Serialize your plaintext JSON body.
  2. 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>".
  3. Build the envelope {"payload":"<base64 ciphertext>"} — by hand, with no extra whitespace, since the checksum must byte-match what the server re-serializes.
  4. Sign — HMAC-SHA256 over "<timestamp>.<clientId>.<envelope>" using your SaltAESKey as the secret, sent as X-Checksum.
  5. Send POST {baseUrl}/process{path} with X-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's X-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

LanguageMinimum versionDependencies
Node.js18+ (built-in fetch, crypto)none
PHP7.1+ (AEAD tag support in openssl_encrypt)ext-openssl, ext-curl (both usually bundled)
Java11+ (java.net.http.HttpClient)none — javax.crypto is built in
Python3.8+pip install cryptography requests
Go1.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.

Node.js
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
<?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);
Java
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));
    }
}
Python
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)
Go
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
}
C&
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. SaltAESKey is 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 cryptography library all return ciphertext-with-tag-appended from a single encrypt call — you must split the last 16 bytes off before sending X-Tag and payload separately. PHP's openssl_encrypt and C#'s AesGcm instead 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.
Prev
Endpoint Reference