Decryption Examples
The following examples show how to receive a callback and decrypt the EnvelopeCallbackPayload JSON. The CallbackKey is a 32-character key generated when configuring the callback in the console.
Go
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
// CallbackRequest is the callback request body
type CallbackRequest struct {
Encrypt string `json:"encrypt"`
}
// EnvelopeCallbackPayload is the decrypted callback payload
type EnvelopeCallbackPayload struct {
EventType string `json:"EventType"`
EnvelopeId string `json:"EnvelopeId"`
OccurredAt string `json:"OccurredAt"`
PreviousEnvelopeStatus string `json:"PreviousEnvelopeStatus"`
CurrentEnvelopeStatus string `json:"CurrentEnvelopeStatus"`
VoidReason string `json:"VoidReason,omitempty"`
Recipients []RecipientCallbackInfo `json:"Recipients"`
}
// RecipientCallbackInfo is recipient callback information
type RecipientCallbackInfo struct {
RecipientId string `json:"RecipientId"`
Name string `json:"Name"`
Email string `json:"Email"`
SigningOrder int `json:"SigningOrder"`
PreviousStatus string `json:"PreviousStatus"`
CurrentStatus string `json:"CurrentStatus"`
OperatedAt string `json:"OperatedAt"`
DeclineReason string `json:"DeclineReason,omitempty"`
}
// decryptPayload decrypts callback data
// encryptedBase64: value of the encrypt field in the callback request
// callbackKey: 32-character CallbackKey generated in the console
func decryptPayload(encryptedBase64 string, callbackKey string) ([]byte, error) {
// 1. Base64 decode
cipherData, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return nil, fmt.Errorf("base64 decode failed: %w", err)
}
// 2. Create the AES-256-GCM decryptor
keyBytes := []byte(callbackKey)
if len(keyBytes) != 32 {
return nil, fmt.Errorf("callback key must be 32 bytes, got %d", len(keyBytes))
}
block, err := aes.NewCipher(keyBytes)
if err != nil {
return nil, fmt.Errorf("create AES cipher failed: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("create GCM failed: %w", err)
}
// 3. Split nonce (first 12 bytes) and ciphertext
nonceSize := gcm.NonceSize() // 12
if len(cipherData) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
nonce := cipherData[:nonceSize]
ciphertext := cipherData[nonceSize:]
// 4. GCM decrypt
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("GCM decrypt failed: %w", err)
}
return plaintext, nil
}
func callbackHandler(w http.ResponseWriter, r *http.Request) {
const callbackKey = "your-32-char-callback-key-here!!" // Replace with the CallbackKey generated in the console
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "read body failed", http.StatusBadRequest)
return
}
var req CallbackRequest
if err := json.Unmarshal(body, &req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
// Decrypt
plaintext, err := decryptPayload(req.Encrypt, callbackKey)
if err != nil {
log.Printf("decrypt failed: %v", err)
http.Error(w, "decrypt failed", http.StatusBadRequest)
return
}
// Parse the payload
var payload EnvelopeCallbackPayload
if err := json.Unmarshal(plaintext, &payload); err != nil {
log.Printf("unmarshal payload failed: %v", err)
http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
// Process the callback event
log.Printf("Received event: %s, EnvelopeId: %s", payload.EventType, payload.EnvelopeId)
// Return 200 to acknowledge successful processing
w.WriteHeader(http.StatusOK)
}
func main() {
http.HandleFunc("/callback", callbackHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}Python
Dependencies:pip install pycryptodome # or pip install pycryptodomex
import base64
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
from Crypto.Cipher import AES
CALLBACK_KEY = "your-32-char-callback-key-here!!" # Replace with the CallbackKey generated in the console
def decrypt_payload(encrypted_base64: str, callback_key: str) -> dict:
"""Decrypt callback data
Args:
encrypted_base64: value of the encrypt field in the callback request
callback_key: 32-character CallbackKey generated in the console
Returns:
decrypted payload dictionary
"""
# 1. Base64 decode
cipher_data = base64.b64decode(encrypted_base64)
# 2. Split nonce (first 12 bytes) and ciphertext+tag
nonce = cipher_data[:12]
ciphertext_with_tag = cipher_data[12:]
# 3. Split ciphertext and GCM auth tag (last 16 bytes)
ciphertext = ciphertext_with_tag[:-16]
tag = ciphertext_with_tag[-16:]
# 4. AES-256-GCM decrypt
key_bytes = callback_key.encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
# 5. Parse JSON
return json.loads(plaintext.decode('utf-8'))
class CallbackHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)
try:
req = json.loads(body)
payload = decrypt_payload(req['encrypt'], CALLBACK_KEY)
# Process the callback event
print(f"Received event: {payload['EventType']}, "
f"EnvelopeId: {payload['EnvelopeId']}")
# Return 200 to acknowledge successful processing
self.send_response(200)
self.end_headers()
except Exception as e:
print(f"Error processing callback: {e}")
self.send_response(400)
self.end_headers()
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8080), CallbackHandler)
print('Callback server listening on port 8080...')
server.serve_forever()Java
Dependencies:Java 8+ (javax.crypto built in)
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
/**
* GSign callback decryption utility class
*
* Ciphertext format: Base64( nonce[12 bytes] + GCM_ciphertext + GCM_tag[16 bytes] )
*/
public class GSignCallbackDecryptor {
private static final int NONCE_LENGTH = 12;
private static final int TAG_LENGTH_BITS = 128; // 16 bytes
/**
* Decrypt callback data
*
* @param encryptedBase64 value of the encrypt field in the callback request
* @param callbackKey 32-character CallbackKey generated in the console
* @return decrypted JSON string
*/
public static String decryptPayload(String encryptedBase64, String callbackKey) throws Exception {
// 1. Base64 decode
byte[] cipherData = Base64.getDecoder().decode(encryptedBase64);
// 2. Split nonce (first 12 bytes) and ciphertext
byte[] nonce = Arrays.copyOfRange(cipherData, 0, NONCE_LENGTH);
byte[] ciphertext = Arrays.copyOfRange(cipherData, NONCE_LENGTH, cipherData.length);
// 3. Create the AES-256-GCM decryptor
byte[] keyBytes = callbackKey.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_LENGTH_BITS, nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
// 4. Decrypt
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
}
}
// ====== Spring Boot Controller example ======
//
// import com.fasterxml.jackson.databind.JsonNode;
// import com.fasterxml.jackson.databind.ObjectMapper;
// import org.springframework.web.bind.annotation.*;
//
// @RestController
// public class CallbackController {
//
// private static final String CALLBACK_KEY = "your-32-char-callback-key-here!!"; // Replace with the CallbackKey generated in the console
// private final ObjectMapper objectMapper = new ObjectMapper();
//
// @PostMapping("/callback")
// public void handleCallback(@RequestBody JsonNode body) throws Exception {
// String encryptedData = body.get("encrypt").asText();
//
// // Decrypt
// String payloadJson = GSignCallbackDecryptor.decryptPayload(encryptedData, CALLBACK_KEY);
//
// // Parse the payload
// JsonNode payload = objectMapper.readTree(payloadJson);
// String eventType = payload.get("EventType").asText();
// String envelopeId = payload.get("EnvelopeId").asText();
//
// System.out.printf("Received event: %s, EnvelopeId: %s%n", eventType, envelopeId);
// }
// }Node.js
Dependencies:Node.js 12+ (crypto module built in)
const crypto = require('crypto');
const http = require('http');
const CALLBACK_KEY = 'your-32-char-callback-key-here!!'; // Replace with the CallbackKey generated in the console
/**
* Decrypt callback data
* @param {string} encryptedBase64 - value of the encrypt field in the callback request
* @param {string} callbackKey - 32-character CallbackKey generated in the console
* @returns {object} decrypted payload object
*/
function decryptPayload(encryptedBase64, callbackKey) {
// 1. Base64 decode
const cipherData = Buffer.from(encryptedBase64, 'base64');
// 2. Split nonce (first 12 bytes), ciphertext, and auth tag (last 16 bytes)
const nonce = cipherData.subarray(0, 12);
const authTag = cipherData.subarray(cipherData.length - 16);
const ciphertext = cipherData.subarray(12, cipherData.length - 16);
// 3. AES-256-GCM decrypt
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
Buffer.from(callbackKey, 'utf8'),
nonce
);
decipher.setAuthTag(authTag);
// 4. Decrypt to plaintext
let plaintext = decipher.update(ciphertext);
plaintext = Buffer.concat([plaintext, decipher.final()]);
// 5. Parse JSON
return JSON.parse(plaintext.toString('utf8'));
}
// HTTP server for receiving callbacks
const server = http.createServer((req, res) => {
if (req.method !== 'POST') {
res.writeHead(405);
res.end();
return;
}
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try {
const { encrypt } = JSON.parse(body);
const payload = decryptPayload(encrypt, CALLBACK_KEY);
// Process the callback event
console.log(`Received event: ${payload.EventType}, EnvelopeId: ${payload.EnvelopeId}`);
// Return 200 to acknowledge successful processing
res.writeHead(200);
res.end();
} catch (err) {
console.error('Error processing callback:', err);
res.writeHead(400);
res.end();
}
});
});
server.listen(8080, () => {
console.log('Callback server listening on port 8080...');
});C# (.NET)
Dependencies:.NET 6+ (System.Security.Cryptography built in)
using System;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
/// <summary>
/// GSign callback decryption utility class
/// </summary>
public static class GSignCallbackDecryptor
{
/// <summary>
/// Decrypt callback data
/// </summary>
/// <param name="encryptedBase64">Value of the encrypt field in the callback request</param>
/// <param name="callbackKey">32-character CallbackKey generated in the console</param>
/// <returns>Decrypted JSON string</returns>
public static string DecryptPayload(string encryptedBase64, string callbackKey)
{
// 1. Base64 decode
byte[] cipherData = Convert.FromBase64String(encryptedBase64);
// 2. Split nonce (first 12 bytes), ciphertext, and auth tag (last 16 bytes)
byte[] nonce = cipherData[..12];
byte[] tag = cipherData[^16..];
byte[] ciphertext = cipherData[12..^16];
// 3. AES-256-GCM decrypt
byte[] keyBytes = Encoding.UTF8.GetBytes(callbackKey);
byte[] plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(keyBytes, 16);
aes.Decrypt(nonce, ciphertext, tag, plaintext);
return Encoding.UTF8.GetString(plaintext);
}
}
// ====== ASP.NET Core Minimal API example ======
//
// var builder = WebApplication.CreateBuilder(args);
// var app = builder.Build();
// const string callbackKey = "your-32-char-callback-key-here!!"; // Replace with the CallbackKey generated in the console
//
// app.MapPost("/callback", async (HttpRequest request) =>
// {
// var body = await JsonSerializer.DeserializeAsync<JsonElement>(request.Body);
// var encryptedData = body.GetProperty("encrypt").GetString()!;
//
// var payloadJson = GSignCallbackDecryptor.DecryptPayload(encryptedData, callbackKey);
// var payload = JsonSerializer.Deserialize<JsonElement>(payloadJson);
//
// Console.WriteLine($"Received event: {payload.GetProperty("EventType")}, "
// + $"EnvelopeId: {payload.GetProperty("EnvelopeId")}");
//
// return Results.Ok();
// });
//
// app.Run();PHP
Dependencies:PHP 7.1+ (openssl extension built in)
<?php
/**
* Decrypt GSign callback data
*
* @param string $encryptedBase64 Value of the encrypt field in the callback request
* @param string $callbackKey 32-character CallbackKey generated in the console
* @return array Decrypted payload array
*/
function decryptPayload(string $encryptedBase64, string $callbackKey): array
{
// 1. Base64 decode
$cipherData = base64_decode($encryptedBase64, true);
if ($cipherData === false) {
throw new RuntimeException('Base64 decode failed');
}
// 2. Split nonce (first 12 bytes), ciphertext, and auth tag (last 16 bytes)
$nonce = substr($cipherData, 0, 12);
$tag = substr($cipherData, -16);
$ciphertext = substr($cipherData, 12, -16);
// 3. AES-256-GCM decrypt
$plaintext = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$callbackKey,
OPENSSL_RAW_DATA,
$nonce,
$tag
);
if ($plaintext === false) {
throw new RuntimeException('AES-GCM decrypt failed');
}
// 4. Parse JSON
return json_decode($plaintext, true, 512, JSON_THROW_ON_ERROR);
}
// ====== Callback receiver example ======
$callbackKey = 'your-32-char-callback-key-here!!'; // Replace with the CallbackKey generated in the console
$body = file_get_contents('php://input');
$req = json_decode($body, true);
try {
$payload = decryptPayload($req['encrypt'], $callbackKey);
// Process the callback event
error_log(sprintf(
'Received event: %s, EnvelopeId: %s',
$payload['EventType'],
$payload['EnvelopeId']
));
// Return 200 to acknowledge successful processing
http_response_code(200);
} catch (Throwable $e) {
error_log('Error processing callback: ' . $e->getMessage());
http_response_code(400);
}