Back to Blog
11 min read
SignLift (API Based Signing Solutions)·

Digital Signature API for Dynamics 365 Invoice Signing: The Complete Integration Guide

Step-by-step guide to automating invoice signing in Microsoft Dynamics 365 with the SignLift digital signature API: auth, request anatomy, Power Automate, Azure Functions, X++, LTV timestamping, and bulk signing.

By Anirudha Narkar

Why invoice signing is the hidden bottleneck in Dynamics 365

Every finance team running Microsoft Dynamics 365 hits the same wall at month-end. Invoices are generated cleanly inside the ERP — and then someone exports each one to PDF, opens it, applies a signature, saves it, and re-uploads it before it can reach the customer. For a batch of 500 invoices, that is an afternoon gone, and an afternoon of error-prone manual work standing between a posted invoice and the cash it represents.

This guide shows you how to remove that step entirely. We will wire Dynamics 365 to the SignLift digital signature API so that the moment an invoice PDF exists, it comes back digitally signed — in seconds, with a tamper-evident PKI signature, an audit-ready timestamp, and zero human clicks.

This is a hands-on integration guide, not a brochure. You get the exact request anatomy, three integration patterns (no-code and code-first), invoice-specific signature placement, and the compliance details that matter for statutory retention in India.

What you will build

The end-to-end flow is simple to reason about:

1Dynamics 365 ──(1) invoice PDF generated──▶
2
3 (2) PDF (base64) + signing instructions
4
5SignLift API ──(3) POST /sign-pdf (X-API-Token)──▶
6 PKI signing + timestamp / LTV
7
8 (4) signed PDF (base64) returned
9
10Store back in D365 / SharePoint + email to customer

The signing certificate lives server-side on SignLift — your Dynamics environment never has to hold a private key. You send a PDF plus a small JSON instruction set; you get a signed PDF back.

Before you start

You will need:

  • A SignLift API token — the X-API-Token value provisioned by SignSecure. Treat it as a secret.
  • A Dynamics 365 environment — Finance & Operations, Business Central, or Customer Engagement / Sales.
  • A way to obtain the invoice PDF — SSRS / Print Management archive (F&O), report-to-PDF (Business Central), or a Dataverse note / attachment (CE).
  • A place to call HTTP from — Power Automate, an Azure Function, a Logic App, or X++ (pick one; see the patterns below).

You do not need to generate, store, or manage the signing certificate yourself — SignLift holds the PKCS#12 (.pfx) certificate and signs on your behalf.

How the SignLift API works

Authentication

Every authenticated request carries a single header:

  • X-API-Token (required) — the API token provisioned by SignSecure.

Auth failures are explicit, which makes them easy to handle in a flow:

  • Missing header401 with {"status": false, "error": {"message": "Missing API token"}}
  • Invalid / expired token401 with {"status": false, "error": {"message": "Invalid token"}}
  • Expired license403 with {"status": false, "error": {"message": "License expired"}}

The three endpoints

  • GET /version — health check / version info. No auth.
  • GET /check-token — verify the token and read its expiry. Auth required.
  • POST /sign-pdf — sign a PDF document. Auth required.

Run /check-token from your monitoring before you ever push production traffic. It returns the token expiry so you can alert weeks before it lapses instead of discovering it during a month-end run:

1curl https://api.signsecure.in/check-token \
2 -H "X-API-Token: $SIGNLIFT_TOKEN"
1{
2 "status": true,
3 "timestamp": "2026-03-21T15:17:16.623Z[UTC]",
4 "tokenExpiry": "21-03-2027"
5}

Anatomy of a /sign-pdf request

This is the heart of the integration. The request has two top-level objects: document (what to sign and where the result goes) and signature (how to sign it).

1{
2 "document": {
3 "source": { "type": "base64", "base64": "<invoice-pdf-base64>" },
4 "output": { "type": "base64" }
5 },
6 "signature": {
7 "type": "sign-ltv",
8 "tsaUrl": "http://timestamp.digicert.com",
9 "reason": "Invoice approved for issue",
10 "location": "Mumbai, IN",
11 "lockAfterSigning": true,
12 "appearance": {
13 "renderMode": "image-and-description",
14 "imageBase64": "<signature-png-base64>",
15 "signerName": "Acme Corp Pvt Ltd",
16 "signerNameDisplay": "signed-by-with-name",
17 "showTimestamp": true,
18 "additionalText": "GSTIN: 27AAACA1234A1Z5"
19 },
20 "placements": [
21 {
22 "type": "text",
23 "searchText": "Authorised Signatory",
24 "position": "above",
25 "gap": 6,
26 "width": 180,
27 "height": 60
28 }
29 ]
30 }
31}

Document source and output

The source can be inline base64 or an S3 object — and the output mirrors it:

  • base64 — send the PDF in the request body. Simplest; ideal from Power Automate.
  • s3 — point at an object in a bucket via { "bucket": "...", "key": "..." }. Ideal for large or high-volume batches so you are not shipping megabytes through your flow.
  • Output default is { "type": "base64" }, which returns the signed PDF in the response. Set it to S3 to have SignLift write the signed file straight to a bucket and return the path instead.

Choosing the signature type (and why invoices need LTV)

  • "sign" — a standard PKI signature. Verifiable as long as the certificate revocation info is reachable.
  • "sign-ltv" — Long-Term Validation. SignLift embeds the validation data (DSS/LTV) into the PDF so the signature stays verifiable for years, even after the signing certificate expires.

For invoices this is not optional fine print. Indian statute requires you to retain tax invoices for years (8 years under the Companies Act; GST records similarly long). A plain signature can become "validity unknown" once the certificate lifecycle moves on. Use sign-ltv. Add a tsaUrl (an RFC 3161 timestamp authority) and SignLift also embeds a trusted timestamp proving when the invoice was signed — exactly what an auditor wants to see.

Placing the signature: anchor to text, do not hardcode pixels

This is the single most useful feature for invoice automation, and where most naive integrations go wrong. You can place a signature at fixed coordinates:

1{ "type": "coordinates", "pages": [1], "x": 380, "y": 90, "width": 180, "height": 60 }

But invoices vary in length — a 1-page invoice and a 3-page invoice put the signature block in different places, so hardcoded coordinates drift. Instead, anchor to text that already prints on your template. Almost every invoice layout has an "Authorised Signatory" or "For <Company Name>" line. Tell SignLift to find it and place the signature relative to it:

1{
2 "type": "text",
3 "searchText": "Authorised Signatory",
4 "position": "above",
5 "gap": 6,
6 "width": 180,
7 "height": 60
8}

The text-placement fields:

  • searchText — text to find in the PDF (e.g. your signatory line).
  • position — "above" or "below" the found text.
  • gap — points of spacing between the text and the signature box.
  • offsetX — horizontal nudge in points.
  • pages — restrict the search to specific pages (defaults to all).

Now the signature lands in the right spot on every invoice regardless of how many line items it has — no template-specific math.

Signature appearance

renderMode controls the visual:

  • "description" — text only ("Digitally signed by … / Date … / Reason …").
  • "image" — your signature or logo PNG only.
  • "image-and-description" — image beside the text. Recommended for invoices.
  • "background-image-and-description" — text drawn over a faded image (use a transparent PNG).
  • "name-and-description" — name plus the standard description block.

Pair it with additionalText to surface compliance metadata right in the stamp — GSTIN, an internal approval ID, "Original for Recipient", etc. showTimestamp: true prints the signing time; lockAfterSigning: true flattens the document so no further edits are possible after signing.

The response

1{
2 "status": true,
3 "timestamp": "2026-03-21T15:17:16.623Z[UTC]",
4 "document": { "content": "<base64-encoded signed PDF>" },
5 "tokenExpiry": "21-03-2027"
6}

Decode document.content and you have the signed invoice. (With S3 output, content is the s3://bucket/key path instead.) On failure you get status: false and a human-readable error.message — always branch on status, never just the HTTP code.

Pick your integration pattern

There is no single right way to call SignLift from Dynamics — it depends on volume and where your team is comfortable building.

  • A. Power Automate — best for most teams and low-to-moderate volume. Lowest effort, native D365 connectors, fastest to ship.
  • B. Azure Function / Logic App — best for high volume, event-driven signing with retries. Cleanest for bulk and easy Key Vault secrets.
  • C. X++ (F&O in-process) — best when signing must be tightly coupled to posting logic. Highest effort; keep it asynchronous.

No servers, native Dynamics triggers. This is the path most finance teams should take first.

  1. Trigger. Use the F&O connector "When a business event occurs" on a customer-invoice event, or a Dataverse "When a row is added" trigger for D365 Sales. A scheduled trigger over recently posted invoices works for nightly catch-up.
  2. Get the invoice PDF. Fetch the generated PDF — from the F&O document archive, a SharePoint library, or the Dataverse attachment — into a "Get file content" step.
  3. Build the request body. Add an HTTP action and compose the JSON, encoding the PDF with the base64() expression (shown below).
  4. Parse and decode. Add "Parse JSON" on the response, check status, then convert the signed PDF back with base64ToBinary(body('HTTP')?['document']?['content']).
  5. Store and send. Write the signed PDF back to the invoice record / SharePoint and optionally email it to the customer.
1{
2 "document": {
3 "source": { "type": "base64", "base64": "@{base64(body('Get_file_content'))}" },
4 "output": { "type": "base64" }
5 },
6 "signature": {
7 "type": "sign-ltv",
8 "tsaUrl": "http://timestamp.digicert.com",
9 "reason": "Invoice approved for issue",
10 "location": "@{variables('LegalEntityCity')}",
11 "lockAfterSigning": true,
12 "appearance": {
13 "renderMode": "image-and-description",
14 "signerName": "@{variables('CompanyName')}",
15 "showTimestamp": true,
16 "additionalText": "@{concat('GSTIN: ', variables('GSTIN'))}"
17 },
18 "placements": [
19 { "type": "text", "searchText": "Authorised Signatory",
20 "position": "above", "gap": 6, "width": 180, "height": 60 }
21 ]
22 }
23}

HTTP action settings:

  • Method — POST
  • URI — https://api.signsecure.in/sign-pdf
  • Headers — X-API-Token (from a secure environment variable / Key Vault) and Content-Type: application/json

That is the entire loop — and it now runs every time an invoice is posted, with no one touching a mouse.

Pattern B — Azure Function (C#) for bulk signing

When you are signing thousands of invoices, or you want robust retries and centralized secrets, put a thin Azure Function between Dynamics and SignLift. D365 raises a business event (or drops a message on Service Bus), the function signs, and writes the result back.

1public class SignLiftClient
2{
3 private readonly HttpClient _http;
4
5 public SignLiftClient(HttpClient http, string apiToken)
6 {
7 _http = http;
8 _http.BaseAddress = new Uri("https://api.signsecure.in/");
9 _http.DefaultRequestHeaders.Add("X-API-Token", apiToken);
10 }
11
12 public async Task<byte[]> SignInvoiceAsync(byte[] pdfBytes, string gstin, string city)
13 {
14 var request = new
15 {
16 document = new
17 {
18 source = new { type = "base64", base64 = Convert.ToBase64String(pdfBytes) },
19 output = new { type = "base64" }
20 },
21 signature = new
22 {
23 type = "sign-ltv",
24 tsaUrl = "http://timestamp.digicert.com",
25 reason = "Invoice approved for issue",
26 location = city,
27 lockAfterSigning = true,
28 appearance = new
29 {
30 renderMode = "image-and-description",
31 signerNameDisplay = "signed-by-with-name",
32 showTimestamp = true,
33 additionalText = "GSTIN: " + gstin
34 },
35 placements = new[]
36 {
37 new { type = "text", searchText = "Authorised Signatory",
38 position = "above", gap = 6.0, width = 180.0, height = 60.0 }
39 }
40 }
41 };
42
43 using var resp = await _http.PostAsJsonAsync("sign-pdf", request);
44 var result = await resp.Content.ReadFromJsonAsync<SignLiftResponse>();
45
46 if (result is null || !result.Status)
47 throw new InvalidOperationException(result?.Error?.Message ?? "Signing failed");
48
49 return Convert.FromBase64String(result.Document.Content);
50 }
51}
52
53public record SignLiftResponse(bool Status, DocumentPayload Document, ErrorPayload Error);
54public record DocumentPayload(string Content);
55public record ErrorPayload(string Message);

Store apiToken in Azure Key Vault and inject it via configuration — never in code or plain-text app settings. For a batch, fan out with bounded concurrency rather than a serial loop, and for very large PDFs switch source/output to type: "s3" so you pass pointers instead of megabytes of base64:

1var throttler = new SemaphoreSlim(8); // tune to your throughput limits
2var tasks = invoices.Select(async inv =>
3{
4 await throttler.WaitAsync();
5 try { return await client.SignInvoiceAsync(inv.Pdf, inv.Gstin, inv.City); }
6 finally { throttler.Release(); }
7});
8var signed = await Task.WhenAll(tasks);

This is where "1–2 hours becomes 1–2 minutes" actually comes from: parallel calls to a stateless signing endpoint, with no human in the loop.

Pattern C — X++ directly from Finance & Operations

If you want signing to happen in-process right after the invoice posts, call the API from X++ via the .NET HttpClient. Keep it asynchronous (a batch job or business-event handler) so signing never blocks the posting transaction:

1public str signInvoicePdf(System.Byte[] _pdfBytes)
2{
3 str token = SysGlobalCache::get('SignLift', 'token'); // from a secure store
4 str endpoint = 'https://api.signsecure.in/sign-pdf';
5 str base64Pdf = System.Convert::ToBase64String(_pdfBytes);
6
7 str body = strFmt(@'{"document":{"source":{"type":"base64","base64":"%1"},'
8 + '"output":{"type":"base64"}},'
9 + '"signature":{"type":"sign-ltv","reason":"Invoice approved for issue",'
10 + '"lockAfterSigning":true,'
11 + '"placements":[{"type":"text","searchText":"Authorised Signatory",'
12 + '"position":"above","gap":6,"width":180,"height":60}]}}', base64Pdf);
13
14 System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
15 client.DefaultRequestHeaders.Add('X-API-Token', token);
16
17 var content = new System.Net.Http.StringContent(
18 body, System.Text.Encoding::get_UTF8(), 'application/json');
19 var response = client.PostAsync(endpoint, content).get_Result();
20 str json = response.Content.ReadAsStringAsync().get_Result();
21
22 // Parse json, read document.content, Convert.FromBase64String back to bytes,
23 // then attach to the invoice via DocuRef / Print Management archive.
24 return json;
25}

For most shops, Pattern A or B is cleaner than embedding HTTP calls in X++ — but this option exists when you need signing tightly coupled to posting.

Make it production-grade

Error handling, retries, and idempotency

  • Always branch on status, not just the HTTP status code. SignLift returns a structured error.message you can log and surface.
  • Retry transient failures (network timeouts, 5xx) with exponential backoff. Signing is safe to retry — you just get a freshly signed copy.
  • Guard against double-signing. Track a "signed" flag on the invoice record so a re-run of a batch does not sign the same document twice. Idempotency is the integrator's job.
  • Pre-flight the token. Have monitoring hit /check-token daily and alert when tokenExpiry is within ~30 days.

Security

  • Keep the X-API-Token in a secret store — Azure Key Vault, Dataverse secure environment variables, or F&O secure configuration. Never commit it.
  • Restrict who can edit the flow / function. Anyone who can change it can change what gets signed.
  • Use HTTPS only and pin to your provisioned endpoint.
  • Log the audit trail — store the returned timestamp, the signing reason, and the invoice ID for every signed document.

Is it legally valid in India?

Signatures applied by SignLift are PKI digital signatures backed by an X.509 certificate (held server-side as a PKCS#12 .pfx). Under the Information Technology Act, 2000, digital signatures created with a valid certificate carry legal recognition, and a signed PDF validates against its trust chain in standard readers like Adobe Acrobat — the signature is tamper-evident, so any change to the invoice after signing invalidates it visibly. With sign-ltv plus a timestamp authority, the signed invoice remains independently verifiable for the full statutory retention period, without depending on live certificate or revocation servers years down the line. That is the difference between "we signed it" and "we can prove we signed it, and when."

Test before you go live

  1. Health check — GET /version (no auth) confirms reachability.
  2. Token check — GET /check-token confirms your token and shows its expiry.
  3. Round-trip one invoice — send a real (non-production) invoice through /sign-pdf, open the returned PDF in Adobe Reader, and confirm the signature panel shows a valid signature anchored in the right place.

A Postman collection ships with the SignLift docs so you can exercise every endpoint before writing a line of integration code.

Conclusion

Manual invoice signing is a self-inflicted bottleneck. With the SignLift digital signature API, Dynamics 365 can hand off every posted invoice and get back a signed, timestamped, audit-ready PDF in seconds — whether you wire it up with a no-code Power Automate flow, an Azure Function for bulk runs, or X++ inside Finance & Operations. Start with Pattern A to prove the loop end-to-end, then graduate to Pattern B when volume demands it.

Ready to automate your invoice signing? Talk to SignSecure to get a SignLift API token and start signing in an afternoon.

Related articles

SignLift (API Based Signing Solutions)·

What Is E Sign And How E Sign API Works?

Learn what is E Sign & How E Sign API can work to streamline document signing workflow by effortlessly integrating with any existing system.