The BoldSign mobile app is now available. Visitthis link for more details and give it a try!
The BoldSign mobile app is now available. Visitthis link for more details and give it a try!
Explore the BoldSign features that make eSigning easier.
BoldSign already simplifies the eSignature process, and with the integration of webhooks, you can further automate your document management. This blog post will guide you through the process of automatically downloading eSignature documents as soon as they are completed by the signers using webhook callbacks.
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
use Exception;
class BoldSignWebhookController extends Controller
{
private const SECRET_KEY = '<<>>';
private function parseHeader($header) {
if (!is_string($header)) {
return null;
}
$result = [
'timestamp' => -1,
'signatures' => [],
];
$items = explode(",", $header);
foreach ($items as $item) {
$key = explode("=", trim($item));
if ($key[0] === 't') {
$result['timestamp'] = intval($key[1], 10);
}
if (in_array($key[0], ['s0', 's1'])) {
$result['signatures'][] = $key[1];
}
}
return (object) $result;
}
private function secureCompare($a, $b) {
if (strlen($a) !== strlen($b)) {
return false;
}
return hash_equals($a, $b);
}
private function isFromBoldSign($signatureHeader, $payload, $secretKey)
{
$parsed = $this->parseHeader($signatureHeader);
if (!$parsed) {
throw new Exception("BoldSign signatures don't exist");
}
$signatureMatched = false;
foreach ($parsed->signatures as $signature) {
$computedSignature = hash_hmac('sha256', $parsed->timestamp . "." . $payload, $secretKey);
if ($this->secureCompare($computedSignature, $signature)) {
$signatureMatched = true;
break;
}
}
if (!$signatureMatched) {
throw new Exception("Unable to verify the signatures");
}
$tolerance = 300;
$timestampAge = time() - $parsed->timestamp;
if ($tolerance > 0 && $timestampAge > $tolerance) {
throw new Exception("Exceeded allowed tolerance range");
}
return true;
}
public function handleWebhook(Request $request) {
$eventType = $request->header('x-boldsign-event');
if ($eventType == "Verification") {
return response()->json(['status' => 'verified'], 200);
}
$signature = $request->header('x-boldsign-signature');
$payload = $request->getContent();
try {
$isValid = $this->isFromBoldSign($signature, $payload, self::SECRET_KEY);
} catch (Exception $e) {
Log::error($e->getMessage());
return response()->json(['error' => 'Bad Request'], 400);
}
if (!$isValid) {
return response()->json(['error' => 'Forbidden'], 403);
}
// Handle the event.
if ($eventType === "Completed") {
$this->handleCompletedEvent(json_decode($payload, true));
}
return response()->json(['status' => 'success'], 200);
}
private function handleCompletedEvent($payloadObject) {
$documentId = $payloadObject['data']['documentId'];
$apiKey = '{Your API Key}';
$directory = '{Specify the directory where you want to save the file}';
$response = Http::withHeaders([
'accept' => 'application/json',
'X-API-KEY' => $apiKey,
])->get('https://api.boldsign.com/v1/document/download', [
'documentId' => $documentId,
]);
if ($response->successful()) {
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
$filePath = $directory . '/' . $documentId . '.pdf';
file_put_contents($filePath, $response->body());
Log::info("File {$documentId}.pdf downloaded and saved successfully.");
} else {
Log::error("Error downloading document: " . $response->body());
}
}
}
namespace BoldSignWebhookListener
{
using System;
using System.IO;
using System.Threading.Tasks;
using BoldSign.Api;
using BoldSign.Model.Webhook;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("webhook")]
public class WebhookExampleController : ControllerBase
{
[HttpPost]
public async Task Webhook()
{
var sr = new StreamReader(this.Request.Body);
var json = await sr.ReadToEndAsync();
if (this.Request.Headers[WebhookUtility.BoldSignEventHeader] == "Verification")
{
return this.Ok();
}
// TODO: Update your webhook secret key.
var SECRET_KEY = "<<>>";
try
{
WebhookUtility.ValidateSignature(
json,
this.Request.Headers[WebhookUtility.BoldSignSignatureHeader],
SECRET_KEY);
}
catch (BoldSignSignatureException ex)
{
Console.WriteLine(ex);
return this.Forbid();
}
var eventPayload = WebhookUtility.ParseEvent(json);
if (eventPayload.Event.EventType == WebHookEventType.Completed)
{
var documentEvent = eventPayload.Data as DocumentEvent;
if (documentEvent != null)
{
await DownloadCompletedDocument(documentEvent.DocumentId);
}
}
return this.Ok();
}
private async Task DownloadCompletedDocument(string documentId)
{
try
{
var apiClient = new ApiClient("https://api.boldsign.com", "{Your API Key}");
var documentClient = new DocumentClient(apiClient);
var documentStream = documentClient.DownloadDocument(documentId);
// Check if the document stream is not null.
if (documentStream != null)
{
// Specify the path where the document should be saved.
var downloadsPath = @"{Specify the path where the document should be saved}";
// Ensure the directory exists
if (!Directory.Exists(downloadsPath))
{
Directory.CreateDirectory(downloadsPath);
}
var filePath = Path.Combine(downloadsPath, $"{documentId}.pdf");
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
await documentStream.CopyToAsync(fileStream);
Console.WriteLine($"Document {documentId} downloaded successfully.");
}
else
{
Console.WriteLine($"Error downloading document {documentId}: Document stream is null.");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error downloading document {documentId}: {ex}");
}
}
}
}
from flask import Flask, request, jsonify
import hashlib
import requests
import os
import hmac
import time
import json
app = Flask(__name__)
# TODO: Update your webhook secret key
SECRET_KEY = "<<>>"
class BoldSignWebhookError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
class BoldSignHelper:
@staticmethod
def is_from_boldsign(signature: str, body: str, secretKey: str, tolerance=300) -> bool:
parsed = BoldSignHelper.parse_header(signature)
timestamp = parsed.timestamp
signatures = parsed.signatures
if timestamp == -1:
raise BoldSignWebhookError("Unable to parse timestamp")
if not signatures:
raise BoldSignWebhookError("Unable to find any signature from the provided header value")
computedSignature = hmac.new(
key=secretKey.encode('utf-8'),
msg=(str(timestamp) + "." + body).encode('utf-8'),
digestmod=hashlib.sha256
).hexdigest()
matched = any(hmac.compare_digest(sign, computedSignature) for sign in signatures)
if not matched:
raise BoldSignWebhookError("HMAC SHA256 signatures don't match")
age = time.time() - timestamp
if tolerance > 0 and age > tolerance:
raise BoldSignWebhookError("Event is outside of the timestamp age tolerance")
return True
@staticmethod
def parse_header(header: str):
class ParsedHeader:
def __init__(self, timestamp, signatures):
self.timestamp = timestamp
self.signatures = signatures
if not header:
raise BoldSignWebhookError("Signature header value seems to be empty")
timestamp = -1
signatures = []
for item in header.split(","):
x, y = item.strip().split("=")
if x == "t":
timestamp = int(y)
elif x in ["s0", "s1"]:
signatures.append(y)
return ParsedHeader(timestamp, signatures)
@app.route('/webhook', methods=['POST'])
def webhook():
event_type = request.headers.get('x-boldsign-event')
if event_type == "Verification":
return jsonify({'status': 'verified'}), 200
payload = request.get_data(as_text=True)
signature = request.headers.get('x-boldsign-signature')
try:
is_valid = BoldSignHelper.is_from_boldsign(signature, payload, SECRET_KEY)
except BoldSignWebhookError as e:
return jsonify({'error': str(e)}), 400
if not is_valid:
return jsonify({'error': 'Forbidden'}), 403
payload_json = json.loads(payload)
if event_type == "Completed":
document_id = payload_json['data']['documentId']
download_completed_document(document_id)
return jsonify({'status': 'success'}), 200
def download_completed_document(document_id):
api_key = "{Your API Key}"
directory = "{Specify the directory where you want to save the file}"
url = f"https://api.boldsign.com/v1/document/download?documentId={document_id}"
headers = {
"accept": "application/json",
"X-API-KEY": api_key
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
if not os.path.exists(directory):
os.makedirs(directory)
file_path = os.path.join(directory, f"{document_id}.pdf")
with open(file_path, "wb") as file:
file.write(response.content)
print(f"File {document_id}.pdf downloaded and saved successfully.")
else:
print(f"Error downloading document: {response.text}")
if __name__ == '__main__':
app.run(debug=True, port=5000)
const crypto = require('crypto');
const axios = require('axios');
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
app.use(express.json());
// TODO: Update your webhook secret key
const SECRET_KEY = "<$lt;YOUR_SECRET_KEY>>";
// Function to validate signature
function isFromBoldSign(signatureHeader, payload, secretKey, tolerance = 300) {
const parsedHeader = parseHeader(signatureHeader);
const timestamp = parsedHeader.timestamp;
const signatures = parsedHeader.signatures;
if (timestamp === -1) {
throw new Error("Unable to parse timestamp");
}
if (!signatures.length) {
throw new Error("Unable to find any signature from the provided header value");
}
const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(`${timestamp}.${payload}`);
const computedSignature = hmac.digest('hex');
const matched = signatures.some(sign => crypto.timingSafeEqual(Buffer.from(sign), Buffer.from(computedSignature)));
if (!matched) {
throw new Error("HMAC SHA256 signatures don't match");
}
const age = Math.floor(Date.now() / 1000) - timestamp;
if (tolerance > 0 && age > tolerance) {
throw new Error("Event is outside of the timestamp age tolerance");
}
return true;
}
// Function to parse header
function parseHeader(header) {
const result = {
timestamp: -1,
signatures: []
};
if (!header) {
throw new Error("Signature header value seems to be empty");
}
header.split(",").forEach(item => {
const [key, value] = item.trim().split("=");
if (key === "t") {
result.timestamp = parseInt(value);
} else if (key === "s0" || key === "s1") {
result.signatures.push(value);
}
});
return result;
}
app.post('/webhook', async (req, res) => {
const eventType = req.headers['x-boldsign-event'];
if (eventType === "Verification") {
return res.status(200).json({ status: 'verified' });
}
const payload = JSON.stringify(req.body);
const signature = req.headers['x-boldsign-signature'];
try {
isFromBoldSign(signature, payload, SECRET_KEY);
} catch (error) {
return res.status(400).json({ error: error.message });
}
if (eventType === "Completed") {
const documentId = req.body.data.documentId;
await downloadCompletedDocument(documentId);
}
res.status(200).json({ status: 'success' });
});
async function downloadCompletedDocument(documentId) {
const apiKey = "{Your API Key}";
const directory = "{Specify the directory where you want to save the file}";
const url = `https://api.boldsign.com/v1/document/download?documentId=${documentId}`;
const headers = {
"accept": "application/json",
"X-API-KEY": apiKey
};
try {
const response = await axios.get(url, { headers, responseType: 'stream' });
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}
const filePath = path.join(directory, `${documentId}.pdf`);
const writer = fs.createWriteStream(filePath);
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
} catch (error) {
console.error(`Error downloading document: ${error.message}`);
}
}
const PORT = 5000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Webhooks are automated messages sent from apps when specific events occur. They are a simple and effective way to receive real-time updates without constantly polling a server for data. In the context of BoldSign, webhooks notify your application when events, such as when a document is signed, occur.
To get started, you’ll need to set up a webhook in BoldSign. Refer to the Webhooks Introduction in the BoldSign documentation to set up webhooks in BoldSign. Please select the Completed event while setting up the webhooks. This ensures you receive notification from webhooks when a document has been fully signed.
Once you have set up the webhook in BoldSign, the next step is to configure your server to listen for these events and handle them appropriately.
For more on listening to callbacks from webhooks on your local machine, refer to the Setting up your endpoint to listen for callbacks API documentation.
By integrating BoldSign webhooks with your application, you can automate the process of downloading signed documents, ensuring you have immediate access to completed eSignatures. This not only saves time but also reduces the risk of human error in handling important documents. Start leveraging webhooks today to enhance your document workflow automation with BoldSign.
If you are not yet a BoldSign customer, we welcome you to start a 30-day free trial now to see how much it has to offer. Please feel free to leave a comment below. Your thoughts are much appreciated. If you have any questions or would like more information about our services, please schedule a demo or contact our support team via our support portal.
Gopinath is a passionate software developer with 2 years of experience at BoldSign. He is an avid writer and enjoys sharing his insights on technology and development. In his free time, he enjoys exploring new technologies and learning new things.
Gopinath is a passionate software developer with 2 years of experience at BoldSign. He is an avid writer and enjoys sharing his insights on technology and development. In his free time, he enjoys exploring new technologies and learning new things.
Latest Articles
Demo-Rss
Mandating Signer Authentication When Making Signature Requests via API
How to Send Documents for eSignature with Identity Verification via API