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

Dependenciespip 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

DependenciesJava 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

DependenciesNode.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

DependenciesPHP 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);
}