Read More on Telerik Blogs
December 17, 2025 Productivity
Get A Free Trial

Learn how to integrate Telerik Document Processing with Azure and AWS cloud services, including all the PDF processing you’ll need in a serverless environment.

Offloading document generation, conversion or manipulation to cloud services allows you to handle large workloads without overloading on-prem resources. Azure Functions and AWS Lambda scale automatically based on demand, so whether you need to generate 10 or 10,000 documents, the infrastructure adjusts dynamically. Serverless models charge per execution time and resources consumed, eliminating the need for dedicated servers for document processing. Thus, you pay only for what you use.

This blog post introduces comprehensive cloud integration demonstrations for Azure and AWS, showcasing PDF processing capabilities using Progress Telerik Document Processing in serverless environments. The examples demonstrate two main integration patterns: PDF merging and external digital signing, implemented for both Azure Functions and AWS Lambda. The complete code projects are available in our SDK repository: Cloud Integration Demos.

Integrate RadPdfProcessing with Azure Functions

First, you’ll need: an active Azure subscription, Azure functions tools installed and the Telerik NuGet feed set up.

  1. Create an Azure Functions App

  1. Select Anonymous authorization level:

  1. Install the following NuGet packages:

  1. Rename the Functiona1.cs file to “MergeFunction” and introduce your custom implementation in the Run method:
using HttpMultipartParser;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using System.Net;
using Telerik.Windows.Documents.Fixed.FormatProviders.Pdf;
using Telerik.Windows.Documents.Fixed.Model;
 
namespace MyAzureFunctionApp;
 
public class MergeFunction
{
    [Function("MergeFunction")]
    public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        Task<MultipartFormDataParser> parsedFormBody = MultipartFormDataParser.ParseAsync(req.Body);
 
        PdfFormatProvider provider = new PdfFormatProvider();
        RadFixedDocument result = MergePdfs(provider, parsedFormBody.Result.Files);
 
        using (MemoryStream outputStream = new MemoryStream())
        {
            provider.Export(result, outputStream, TimeSpan.FromSeconds(20));
            outputStream.Seek(0, SeekOrigin.Begin);
 
            HttpResponseData httpResponseData = req.CreateResponse(HttpStatusCode.OK);
            httpResponseData.Headers.Add("Content-Type", "application/pdf");
            await outputStream.CopyToAsync(httpResponseData.Body);
 
            return httpResponseData;
        }
    }
 
    static RadFixedDocument MergePdfs(PdfFormatProvider pdfFormatProvider, IReadOnlyList<FilePart> files)
    {
        RadFixedDocument mergedDocument = new RadFixedDocument();
        foreach (FilePart file in files)
        {
            RadFixedDocument documentToMerge = pdfFormatProvider.Import(file.Data, TimeSpan.FromSeconds(20));
            mergedDocument.Merge(documentToMerge);
        }
 
        return mergedDocument;
    }
} 

This Azure Function, named MergeFunction, handles HTTP GET and POST requests to merge multiple PDF files into a single document. It uses:

  • HttpMultipartParser to parse multipart form data and extract uploaded files.
  • Telerik PdfFormatProvider and RadFixedDocument to import and merge PDF documents.
  • The merged PDF is exported to a memory stream and returned as an HTTP response with the application/pdf content type.

The function supports anonymous access and processes uploaded files efficiently, so that the merged PDF is delivered in the response. Since the function is implemented with anonymous access for demo purposes, consider adding authentication as needed for your scenario.

  1. Run the project and copy the URL from the window that pops up:

  1. Create a .NET Console App (e.g., MyMergeApp) that acts as a client for the Azure Function PDF Merge service.

using System.Diagnostics;
using System.Net.Http.Headers;
 
namespace MyMergeApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string functionUrl = "http://localhost:7163/api/MergeFunction";
 
            // Specify PDF files to merge from the Resources directory
            string[] pdfFilePaths = { "../../../Resources/file1.pdf", "../../../Resources/file2.pdf" };
 
            // Create multipart form data content for file upload
            using MultipartFormDataContent content = new();
 
            // Add each PDF file to the form data
            foreach (string filePath in pdfFilePaths)
            {
                byte[] fileBytes = File.ReadAllBytes(filePath);
                ByteArrayContent fileContent = new(fileBytes);
                fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
                content.Add(fileContent, "files", Path.GetFileName(filePath));
            }
 
            // Send the merge request to the Azure Function
            HttpResponseMessage response = PostAsync(functionUrl, content).Result;
            response.EnsureSuccessStatusCode();
 
            // Read the merged PDF from the response
            byte[] result = response.Content.ReadAsByteArrayAsync().Result;
 
            // Save the merged PDF to disk
            string outputPath = "merged_output.pdf";
            if (File.Exists(outputPath))
            {
                File.Delete(outputPath);
            }
            File.WriteAllBytes(outputPath, result);
 
            // Open the merged PDF in the default viewer
            Process.Start(new ProcessStartInfo(outputPath) { UseShellExecute = true });
        }
        static async Task<HttpResponseMessage> PostAsync(string url, MultipartFormDataContent content)
        {
            using HttpClient httpClient = new HttpClient();
            httpClient.Timeout = TimeSpan.FromMinutes(2); // Allow sufficient time for large files
 
            return await httpClient.PostAsync(url, content);
        }
    }
}

It demonstrates how to send multiple PDF files to the MergeFunction endpoint and handle the merged result. The workflow includes:

  • Loading PDF files from a local Resources directory.
  • Packaging the files as multipart form data for upload.
  • Sending an HTTP POST request to the Azure Function endpoint.
  • Receiving the merged PDF in the response and saving it to disk.
  • Automatically opening the merged PDF in the system’s default viewer.
    The application uses HttpClient for communication, enables proper content type headers and includes a helper method PostAsync for sending requests with a configurable timeout.
  1. Run the Azure Functions .NET Worker (e.g., MyAzureFunctionApp.csproj):

  1. While the service is running, run the Console App (e.g., MyMergeApp).

As a result, a merged PDF document will be produced:

Integrate RadPdfProcessing with AWS Lambda

Using an AWS Lambda function for signing PDF documents offers a more secure, scalable and cost-effective approach to digital signature workflows. By offloading the signing process to a serverless environment, sensitive private keys remain isolated in the cloud, reducing the risk of exposure on client machines.

The following tutorial defines the steps for creating an AWS Lambda function that performs digital signature operations for PDF documents using a private key stored in a certificate file. The AWS Lambda function is purposed to act as a more secure service that remotely signs PDF documents, and the private key is never exposed to the client.

Before starting, you’ll need valid AWS Credentials and the Telerik NuGet Feed properly set up.

  1. Create AWS Lambda Project (e.g., MyAWSLambda):

  1. From the Select Blueprint wizard, select “.NET 8 (Container Image)”:

  1. Open the Function.cs file and implement the custom logic for signing PDF documents in the FunctionHandler:

The method receives data to be signed, signs it using a private key certificate, and returns the Base64-encoded signature. The .pfx certificate is stored independently from the project used for processing documents.

NOTE: Do not forget that sensitive data should be stored properly and that this implementation is for demo purposes.

using Amazon.Lambda.Core;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
 
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
 
namespace MyAWSLambda;
 
public class Function
{
    public async Task<string> FunctionHandler(Input input, ILambdaContext context)
    {
        byte[] dataToSign = Convert.FromBase64String(input.DataToSign);
 
        byte[]? result = SignData(dataToSign, input.DigestAlgorithm);
 
        if (result != null)
        {
            return Convert.ToBase64String(result);
        }
        else
        {
            throw new InvalidOperationException("Signing operation failed");
        }
    }
    static byte[]? SignData(byte[] dataToSign, string? digestAlgorithm)
    {
        string certificateFilePassword = "johndoe";
        string certificateFilePath = "Resources/JohnDoe.pfx";
 
        using (Stream stream = File.OpenRead(certificateFilePath))
        {
            byte[] certRawData = new byte[stream.Length];
            stream.ReadExactly(certRawData, 0, (int)stream.Length);
 
            using (X509Certificate2 fullCert = new X509Certificate2(certRawData, certificateFilePassword))
            {
                using (RSA? rsa = fullCert.GetRSAPrivateKey())
                {
                    if (rsa == null)
                    {
                        throw new InvalidOperationException("Certificate does not contain an RSA private key");
                    }
 
                    // Map the digest algorithm string to HashAlgorithmName
                    HashAlgorithmName algorithmName = HashAlgorithmName.SHA256;
                    algorithmName = digestAlgorithm switch
                    {
                        "Sha384" => HashAlgorithmName.SHA384,
                        "Sha512" => HashAlgorithmName.SHA512,
                        _ => HashAlgorithmName.SHA256,
                    };
 
                    byte[]? bytes = rsa.SignData(dataToSign, algorithmName, RSASignaturePadding.Pkcs1);
                    return bytes;
                }
            }
        }
    }
 
    public class Input
    {
        public string DataToSign { get; set; } = string.Empty;
 
        public string? DigestAlgorithm { get; set; }
    }
}
  1. Modify the aws-lambda-tools-defaults.json file and specify the function-name for the AWS Lambda function (e.g., ExternalSignPdfAWSFunction):
{
  "Information": [
    "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
    "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
    "dotnet lambda help",
    "All the command line options for the Lambda command can be specified in this file."
  ],
  "profile": "telerik-dpl",
  "region": "eu-north-1",
  "configuration": "Release",
  "package-type": "image",
  "function-memory-size": 512,
  "function-timeout": 30,
  "image-command": "MyAWSLambda::MyAWSLambda.Function::FunctionHandler",
  "docker-host-build-output-dir": "./bin/Release/lambda-publish",
  "function-name": "ExternalSignPdfAWSFunction"
}
  1. Create a Console App (e.g., MySigningApp.csproj) that will consume the AWS Lambda function.

  2. Install the following NuGet packages:

  • AWSSDK.Lambda
  • Telerik.Documents.Fixed

The Resources folder stores the unsigned PDF document together with the public .crt certificate.

  1. Modify the Program.cs file to demonstrate how to apply a digital signature to a PDF document using the Telerik PdfProcessinglibrary and an external AWS Lambda signer. The workflow includes importing a PDF, creating a signature field with a visual representation, configuring signature settings and exporting the signed document.

Note: We will use the specified function-name in the previous step when creating the LambdaFunctionSigner object:

using System.Diagnostics;
using Telerik.Documents.Fixed.Model.DigitalSignatures;
using Telerik.Documents.Primitives;
using Telerik.Windows.Documents.Fixed.FormatProviders.Pdf;
using Telerik.Windows.Documents.Fixed.Model;
using Telerik.Windows.Documents.Fixed.Model.Annotations;
using Telerik.Windows.Documents.Fixed.Model.DigitalSignatures;
using Telerik.Windows.Documents.Fixed.Model.Editing;
using Telerik.Windows.Documents.Fixed.Model.InteractiveForms;
using Telerik.Windows.Documents.Fixed.Model.Objects;
using Telerik.Windows.Documents.Fixed.Model.Resources;
 
namespace MySigningApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            PdfFormatProvider provider = new PdfFormatProvider();
            RadFixedDocument document = ImportDocument(provider);
 
            string signatureName = "SampleSignature";
 
            Form form = new Form();
            form.FormSource = new FormSource();
            form.FormSource.Size = new Size(120, 120);
 
            // We will use the editor to fill the Form XObject.  
            FixedContentEditor formEditor = new FixedContentEditor(form.FormSource);
            formEditor.DrawCircle(new Point(50, 50), 20);
            formEditor.DrawText(signatureName);
 
            // The Signature object is added to a signature field, so we can add a visualization to it.  
            SignatureField signatureField = new SignatureField(signatureName);
 
            // Create the external signer that will call AWS Lambda to perform the actual signing
            ExternalSignerBase externalSigner = new LambdaFunctionSigner("ExternalSignPdfAWSFunction", "eu-north-1");
 
            // Configure the signature with digest algorithm and timestamp server
            Signature signature = new Signature(externalSigner);
            signature.Settings.DigestAlgorithm = DigestAlgorithmType.Sha512;
            signature.Settings.TimeStampServer = new TimeStampServer("http://timestamp.digicert.com", TimeSpan.FromSeconds(10));
            signatureField.Signature = signature;
 
            // The widget contains the Form XObject and defines the appearance of the signature field.  
            SignatureWidget widget = signatureField.Widgets.AddWidget();
            widget.Rect = new Rect(200, 600, 100, 100);
            widget.Border = new AnnotationBorder(100, AnnotationBorderStyle.Solid, null);
            widget.Content.NormalContentSource = form.FormSource;
 
            // The Widget class inherits from Annotation. And, as any other annotation, must be added to the respective collection of the page.  
            RadFixedPage page = document.Pages.AddPage();
            page.Annotations.Add(widget);
            document.AcroForm.FormFields.Add(signatureField);
 
            document.AcroForm.SignatureFlags = SignatureFlags.AppendOnly;
 
            ExportDocument(provider, document);
 
        }
 
        static RadFixedDocument ImportDocument(PdfFormatProvider provider)
        {
            using (FileStream stream = File.OpenRead("Resources/SampleDocument.pdf"))
            {
                return provider.Import(stream, TimeSpan.FromSeconds(20));
            }
        }
 
        static void ExportDocument(PdfFormatProvider provider, RadFixedDocument document)
        {
            string outputPath = "ExternallySignedDocument.pdf";
            File.Delete(outputPath);
 
            using (FileStream stream = File.Create(outputPath))
            {
                provider.Export(document, stream, TimeSpan.FromSeconds(60));
            }
 
            Process.Start(new ProcessStartInfo(outputPath) { UseShellExecute = true });
        }
    }
}
  1. Implement the External Signer.

The LambdaFunctionSigner class is a custom implementation of an external signer that delegates digital signing operations to an AWS Lambda function. It inherits from ExternalSignerBaseand is designed for scenarios where the private key is securely stored in AWS Lambda, while the public certificate resides locally.

using Amazon.Lambda;
using Amazon.Lambda.Model;
using Amazon.Runtime; 
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using Telerik.Documents.Fixed.Model.DigitalSignatures;
 
namespace MySigningApp
{
    public class LambdaFunctionSigner : ExternalSignerBase
 
    {
        private readonly string functionName;
        private readonly AmazonLambdaClient lambdaClient;
 
        /// <summary>
        /// Creates a new instance of LambdaFunctionSigner.
        /// </summary>
        /// <param name="functionName">The AWS Lambda function name or ARN (e.g., "MyAWSLambda" or "arn:aws:lambda:region:account:function:name")</param>
        /// <param name="region">The AWS region where the Lambda function is deployed (e.g., "us-east-1", "eu-north-1")</param>
        public LambdaFunctionSigner(string functionName, string region)
        {
            this.functionName = functionName;
            var credentials = new BasicAWSCredentials("your-access-key", "your-secret-key");
            this.lambdaClient = new AmazonLambdaClient(credentials, Amazon.RegionEndpoint.GetBySystemName(region));
        }
 
        /// <summary>
        /// Gets the certificate chain used for signing.
        /// </summary>
        /// <returns>An array of X509Certificate2 objects representing the certificate chain.</returns>
        /// <remarks>
        /// The public certificate is loaded from the Resources folder and should match
        /// the private key certificate stored in the AWS Lambda function.
        /// </remarks>
        protected override X509Certificate2[] GetCertificateChain()
        {
            string publicKey = "Resources/JohnDoe.crt";
            return [new X509Certificate2(publicKey)];
        }
 
        /// <summary>
        /// Signs the provided data by invoking the AWS Lambda function.
        /// </summary>
        /// <param name="dataToSign">The byte array containing the data to be signed.</param>
        /// <param name="settings">The signature settings containing digest algorithm and other configuration.</param>
        /// <returns>A byte array containing the digital signature.</returns>
        /// <exception cref="InvalidOperationException">Thrown when the Lambda invocation fails or returns an error.</exception>
        /// <remarks>
        /// This method:
        /// 1. Serializes the data and settings to JSON
        /// 2. Invokes the Lambda function synchronously
        /// 3. Validates the response
        /// 4. Returns the decoded signature bytes
        /// </remarks>
        protected override byte[] SignData(byte[] dataToSign, SignatureSettings settings)
        {
            var requestData = new
            {
                DataToSign = Convert.ToBase64String(dataToSign),
                DigestAlgorithm = settings.DigestAlgorithm.ToString()
            };
 
            string jsonRequest = JsonSerializer.Serialize(requestData, new JsonSerializerOptions
            {
                WriteIndented = true
            });
 
            var invokeRequest = new InvokeRequest
            {
                FunctionName = this.functionName,
                InvocationType = InvocationType.RequestResponse, // synchronous
                Payload = jsonRequest
            };
 
            InvokeResponse invokeResponse = lambdaClient.InvokeAsync(invokeRequest).Result;
 
            if (invokeResponse.StatusCode != 200)
            {
                throw new InvalidOperationException($"Lambda invocation failed with status code: {invokeResponse.StatusCode}");
            }
 
            if (!string.IsNullOrEmpty(invokeResponse.FunctionError))
            {
                string errorMessage = Encoding.UTF8.GetString(invokeResponse.Payload.ToArray());
                throw new InvalidOperationException($"Lambda function error: {invokeResponse.FunctionError}. Details: {errorMessage}");
            }
 
            string jsonResponse = Encoding.UTF8.GetString(invokeResponse.Payload.ToArray());
            string? base64Signature = JsonSerializer.Deserialize<string>(jsonResponse);
 
            if (string.IsNullOrEmpty(base64Signature))
            {
                throw new InvalidOperationException("Invalid response from Lambda function");
            }
 
            return Convert.FromBase64String(base64Signature);
        }
    }
}
  1. Run the project.

As a result, the unsigned PDF document is successfully signed:

Wrapping Up

Integrating Telerik Document Processing with Azure Functions and AWS Lambda demonstrates how serverless architectures can transform document workflows. By leveraging cloud scalability and pay-per-use models, organizations can efficiently handle tasks like PDF merging and secure digital signing without maintaining dedicated infrastructure. These approaches not only reduce operational overhead but also enhance security by isolating sensitive keys in the cloud.

As businesses continue to adopt remote and distributed solutions, similar patterns can be extended to other workflows such as document conversion, watermarking and batch processing, unlocking even greater flexibility and cost efficiency. The examples provided serve as a foundation for building robust, cloud-powered document services that scale seamlessly with demand.

Try out the Telerik Document Processing Libraries, which are included in the Telerik DevCraft bundles to pair perfectly with your favorite component libraries.

Try Telerik DevCraft

About the Author

Desislava Yordanova

Desislava Yordanova is a proactive ambassador of diligent processes and a customer-caring professional. Currently, she is a Technical Support Engineer, Principal in the Document Processing team after a successful journey in the Telerik UI for WinForms team. She joined the company in 2013. Desislava holds a master’s degree in Computer Systems and Technologies from the Technical University in Sofia. Apart from her job, she is keen on snowboarding and travelling. You can find Desislava on LinkedIn.

Related Posts