Build a CRM-to-eSign Document Pipeline in n8n Using Foxit’s REST APIs

This tutorial shows how to wire the Foxit API n8n integration into a single automated chain. You’ll learn how to generate a PDF, send it for signature, and archive the signed copy without anyone touching a file.
A deal moves to closed-won in the CRM, and then a person takes over. Someone exports the deal fields, pastes them into a contract template, saves a PDF, uploads it to a signing tool, types in the counterparty’s email, and hits send. Days later they check whether it was signed, download the executed copy, and drop it into a shared drive so finance can find it. Every one of those steps is a place where data gets retyped, a wrong template gets attached, or a signature request stalls because nobody was watching.
The failure is structural, not human. The deal data lives in the CRM, the contract template lives in a content folder, the signature lives in a separate vendor, and the archive lives somewhere else again. Nothing connects them, so a person becomes the integration layer.
The target state removes that person from the loop. A CRM event fires, an automation runs a short chain of API calls, and a signed PDF lands in storage with no manual touch. This tutorial builds exactly that using n8n, a source-available workflow tool, as the orchestrator. You will build it step by step, and you do not need to be an n8n expert to follow along. The chain is a CRM webhook, then document generation, then send-for-signature, then an archive step, wired together with n8n’s HTTP Request node.
A Continuous Chain of REST Calls
The pipeline has three stages, and each is a REST call the previous stage feeds directly.
- Generate. A CRM webhook delivers deal data into n8n. The workflow merges that data into a Word template and renders a PDF through the Foxit Document Generation API, a cloud REST endpoint with no SDK to install.
- Sign. The rendered PDF goes straight to the Foxit eSign API, which creates a signing folder, routes it to the parties, and manages the signature lifecycle.
- Archive. When signing completes, the workflow downloads the executed PDF and writes it to storage, then updates the CRM record.
The data handoff between stages is what makes the chain clean. Document Generation returns the finished PDF as a base64 string in its JSON response, and that same base64 string is exactly what the eSign folder-create call accepts as its file payload. No temporary files, no disk writes, and no format juggling between the two calls. The eSign completion event then carries the folder reference the archive stage uses to pull the signed document.
One detail belongs up front, since it shapes every node. The two APIs live on two different hosts. Document Generation runs on https://na1.fusion.foxit.com, and eSign runs on https://na1.foxitesign.foxit.com. They also authenticate differently, which the next section covers. There is no dedicated Foxit node in n8n and none is needed, because both are well-formed REST endpoints that the HTTP Request node handles with custom headers and JSON bodies.
Run n8n Locally and Get Foxit Credentials
The whole tutorial runs on free, self-hosted infrastructure. There is no n8n Cloud plan to sign up for and no paid Foxit tier involved. You run n8n yourself in a container, and the only account you create is Foxit’s free Developer plan.
Install Docker
You need Docker to run n8n. On macOS or Windows, install Docker Desktop; on Linux, install Docker Engine. Both are covered on the Get Docker page. After installing, confirm it works by running docker --version in a terminal.
Start n8n and create your local account
Run the two commands below, taken from n8n’s Docker docs:
docker volume create n8n_data
docker run -it --rm --name n8n -p 5678:5678 \
-v n8n_data:/home/node/.n8n \
docker.n8n.io/n8nio/n8n The n8n_data volume persists your workflows and credentials across restarts, since n8n stores them in a local SQLite database by default. The -p 5678:5678 flag maps the editor to a local port. Now do these steps in order:
- Open http://localhost:5678 in your browser.
- On first launch, n8n asks you to create an owner account. This is a local account for your own instance, not an n8n Cloud login, so use any email and password you like.
- You land on an empty workflow canvas. This is where you will build the pipeline.
Swap -it --rm for -d in the run command when you later want n8n running in the background.
Get your Foxit credentials
Register a Foxit account at account.foxit.com/site/sign-up, verify your email, and activate the free Developer plan. From the APIs Dashboard, copy your Document Generation Client ID and Client Secret. The eSign API is provisioned separately, so once eSign is active on your account, retrieve its Client ID and Client Secret as a distinct pair. You now have two credential sets, and they are not interchangeable.
They differ because the two APIs authenticate differently, and this is the single thing that trips up most first builds. Document Generation takes the credentials as two request headers, lowercase client_id and client_secret, on every call. eSign instead runs an OAuth2 exchange first, trading its credentials for a short-lived bearer token that you then send as an Authorization: Bearer <token> header.
Store the DocGen credentials once, reuse them everywhere
Rather than pasting your Client ID and Secret into every node, store them once in n8n’s credential store. Because Foxit needs two custom headers, use the Custom Auth credential type (the same type Foxit’s own published n8n template uses). In n8n, go to Credentials, click Create credential, search for Custom Auth, click Continue, and paste this JSON:
{
"headers": {
"client_id": "YOUR_FOXIT_CLIENT_ID",
"client_secret": "YOUR_FOXIT_CLIENT_SECRET"
}
}
The Custom Auth credential holds both Foxit headers in one place. Any HTTP Request node can attach it, so your Client ID and Secret never appear in a node field or an exported workflow.
In this credential, the headers object lists the two headers Foxit expects, with your real Client ID and Secret as the values. Save it, and every Document Generation call in this tutorial can attach it instead of carrying the raw keys.
Import the Finished Workflow (Optional Shortcut)
If you would rather see the whole pipeline first and study it, import the finished version and then follow the step-by-step build below to understand each node. On any workflow canvas, open the ⋯ (Actions) menu in the top right and choose Import from URL….
The Actions menu on the workflow canvas. “Import from URL…” pulls a workflow straight from a link, so there is nothing to download.
Paste the raw URL of the ready-made pipeline JSON and click Import:
https://github.com/lucienchemaly/foxit-demo-templates/raw/main/n8n/foxit-crm-to-esign-pipeline.json
The Import from URL dialog. n8n fetches the JSON and renders the full node graph on the canvas, ready for you to attach your own credentials.
The imported graph arrives with placeholder credentials, so you still create the Custom Auth credential above and select it on the two Foxit nodes. Whether you import or build from scratch, the sections below explain exactly what each node does.
Step 1: Trigger the Workflow and Generate the PDF
Add the trigger and fire it with a test request
Start the workflow with a Webhook node, which is how a CRM will eventually call your pipeline when a deal closes. Click the + on the canvas, search for Webhook, and add it. Set HTTP Method to POST and Path to crm-deal-closed. n8n gives every Webhook node two addresses, a Test URL for building and a Production URL for live traffic.
The Webhook node’s Test URL and “Listen for test event” button. Clicking Listen puts the node in a one-shot capture mode so you can send it a sample request while building.
Because you are working locally and do not have a real CRM pointed at your laptop yet, fire the webhook yourself. Click Listen for test event, copy the Test URL, and send it a sample deal payload with cURL:
curl -X POST "http://localhost:5678/webhook-test/crm-deal-closed" \
-H "Content-Type: application/json" \
-d '{
"company_name": "Acme Robotics",
"deal_id": "INV-2026-0042",
"amount": "1875.50"
}' In this request, you POST a small JSON object that mimics what a CRM would send when a deal closes. The Webhook node captures it, and those three fields (company_name, deal_id, amount) become available to every downstream node as {{ $json.body.company_name }} and so on. When you later move to production, switch to the node’s Production URL and register it in your CRM’s webhook settings. To expose your local machine to the internet for that, run a tunnel such as ngrok (ngrok http 5678) and register the tunnel address.
Prepare the Word template
The template is an ordinary Word document with Foxit DocGen merge tags where the deal data should land. A tag is a field name in double braces, and you can add Word-style format switches for dates and currency. Download the ready-to-use sample so you do not have to author one, invoice_simple.docx. It carries {{ companyName }}, {{ invoiceNumber }}, {{ invoiceDate \@ MM/dd/yyyy }}, and {{ totalDue \# "$#,##0.00" }}. For repeating rows such as line items, DocGen supports a loop with {{TableStart:lineItems}} and {{TableEnd:lineItems}} markers placed in the same table row, shown in the companion invoice_table.docx. If you want to confirm a template’s tags before wiring it in, POST it once to https://na1.fusion.foxit.com/document-generation/api/AnalyzeDocumentBase64, which returns the full list of tags it detected. The Foxit DocGen quickstart walks through the request shape in more detail.
Add and configure the Generate PDF node
Click + to add an HTTP Request node after the Webhook, and configure it field by field.
- Method : set to
POST. - URL :
https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64 - Authentication : choose Generic Credential Type, then Custom Auth, and select the credential you created earlier.
- Send Body : turn this on, set the body type to JSON, and paste the payload below.
The HTTP Request node config panel. Every Foxit call in this tutorial is built here by setting the method and URL, attaching the credential, and toggling Send Body on for the JSON payload.
{
"base64FileString": "<base64-encoded invoice_simple.docx>",
"documentValues": {
"companyName": "{{ $json.body.company_name }}",
"invoiceNumber": "{{ $json.body.deal_id }}",
"invoiceDate": "2026-07-01",
"totalDue": "{{ $json.body.amount }}"
},
"outputFormat": "pdf"
} In this body, base64FileString is the Word template encoded as base64, documentValues is a flat object whose keys match the template tags exactly and whose values are pulled from the webhook payload using n8n expressions, and outputFormat is the lowercase string "pdf". The keys in documentValues must match the tag names in the template character for character, since a mismatch renders that field empty rather than raising an error. To turn the template file into the base64 string, fetch it with an HTTP Request node set to return the file, then convert it in a Code node with items[0].binary.data.data, or paste a pre-encoded string while you are testing.
One limit is worth planning for. Document Generation rejects a .docx payload larger than 4 MB measured after base64 encoding, which is roughly a 3 MB raw file, and the rejection comes back as an HTTP 500 rather than a friendly validation message. If your template is heavy, compress its images through Word’s Picture Format pane, drop embedded fonts and OLE objects, and split an oversized template into parts you merge later.
The endpoint is synchronous, so there is no polling. The response is a JSON object with message, fileExtension, and base64FileString, where base64FileString holds the rendered PDF as base64. That value flows straight into Step 2.
Step 2: Exchange an eSign Token and Send for Signature
Get a bearer token
The eSign leg starts by turning your eSign credentials into a bearer token. Add another HTTP Request node and configure it:
- Method :
POST - URL :
https://na1.foxitesign.foxit.com/api/oauth2/access_token - Send Body : on, body type Form Urlencoded, with four fields,
grant_typeset toclient_credentials,client_idset to your eSign Client ID,client_secretset to your eSign Client Secret, andscopeset toread-write.
The response is JSON with access_token, token_type (the string bearer), expires_in, and instance_url. This is a standard OAuth2 client-credentials grant as defined in RFC 6749. The token is long-lived but not permanent, so in production cache it and refresh when expires_in is close to elapsing rather than minting a new one on every run.
The token node uses a Form URL Encoded body with the four OAuth fields, not JSON. This is the one eSign call that sends the raw credentials rather than a bearer header.
Click Execute step on this node to confirm it works before wiring the rest. A successful run returns the token in the output panel.
A successful token call. The green check and the access_token / token_type values confirm the credentials and endpoint are correct before you build the next node.
Place signature fields with Text Tags
Signature placement is handled inside the document using Foxit eSign Text Tags, which you type into the Word template while authoring it. The syntax is ${fieldtype:party:mandatory}, so ${signfield:1:y} is a required signature for party 1 and ${datefield:2:n} is an optional date for party 2. If a tag needs to contain a space, replace it with an underscore, because a literal space breaks tag recognition. To keep the tags invisible in the finished document, set their text color to match the page background. On upload you set processTextTags: true, and Foxit converts each tag into a real signing field automatically, which removes any manual drag-and-drop field placement.
There is a party-mapping rule that fails silently, so check it before every send. Every party number referenced by a Text Tag must have a matching entry in the parties array of the create call. If a tag points at party 2 but your parties array only defines party 1, a sendNow: true create still returns success while quietly dropping party 2’s fields, so nobody is ever asked to sign them. Line up the token party numbers with the parties entries first.
Add the Create Signature Folder node
Add a third HTTP Request node for the send-for-signature call:
- Method :
POST - URL :
https://na1.foxitesign.foxit.com/api/folders/createfolder - Send Headers : on, add one header named
Authorizationwith the valueBearer {{ $json.access_token }}, which references the token from the previous node. - Send Body : on, body type JSON, with the payload below.
{
"folderName": "Contract - {{ $('CRM Webhook').item.json.body.company_name }}",
"inputType": "base64",
"base64FileString": ["{{ $('Generate PDF').item.json.base64FileString }}"],
"fileNames": ["contract.pdf"],
"processTextTags": true,
"sendNow": true,
"parties": [
{
"firstName": "Alex",
"lastName": "Rivera",
"emailId": "[email protected]",
"permission": "FILL_FIELDS_AND_SIGN",
"sequence": 1,
"workflowSequence": 1
}
]
} In this call, inputType is set to "base64" and base64FileString is an array holding the PDF that Step 1 produced, which is the direct handoff between the two APIs. The parties array lists each signer with their name, email, and order, processTextTags: true turns the embedded tags into fields, and sendNow: true dispatches the folder for signature in the same request. The response nests the identifier at folder.folderId, which you keep for status tracking and archival. Foxit’s API uses folder terminology throughout rather than envelope. If your process needs a review gate before anything is sent, set sendNow: false to create a draft, then dispatch it later with a POST /api/folders/sendDraftFolder call carrying the returned folderId.
The create-folder node. The Authorization header value Bearer {{ $json.access_token }} pulls the token straight from the previous node, and the input panel on the left shows that token flowing in.
The finished chain in the local n8n editor. Each stage is a plain HTTP Request node, and the base64 PDF from the generation node flows directly into the eSign create-folder node.
Once the folder is sent, the signer receives the document with the Text Tags already converted into interactive fields routed to them.
What the recipient sees after dispatch. This is the visible proof that processTextTags placed the fields correctly and routed them to the right party.
Step 3: Capture Signing Status and Archive the Document
With the folder sent, the workflow needs to know when it is signed so it can archive the result. The folder moves through a defined lifecycle, DRAFT, then SHARED, then PARTIALLY SIGNED, then COMPLETED, then EXECUTED, where the digital signature is applied to the PDF a few seconds after COMPLETED. Archive on the COMPLETED or EXECUTED event so you capture the finalized, digitally signed file.
Foxit eSign can push a webhook when the folder completes, configured in the eSign portal’s owner-only API and Webhooks settings, and you would receive it with a second n8n Webhook node. For a first local build, the simpler path is to poll. Add a Schedule Trigger node paired with an HTTP Request to https://na1.foxitesign.foxit.com/api/folders/viewActivityHistory?folderId={id}, which reads the folder’s audit trail (Created, Invitation Sent, Opened, Viewed, Signed, Folder Executed). This endpoint is GET-only and returns data only once the folder has been shared or sent, so it will not respond for a draft. Gate the next step with an IF node that checks whether the status has reached COMPLETED.
On completion, download the signed PDF through the eSign API and route the binary to a storage node, whether that is Google Drive, an S3 bucket, or an internal file-system node, then fire any downstream updates such as a Slack message or a CRM field write. Because not every send completes, add an error branch as well. If a party declines or the folder expires, route to a separate path that updates the CRM record to reflect the outcome and alerts the deal owner, so a human steps in only on the exceptions.
Common Mistakes and Troubleshooting
A few pitfalls account for most failed first runs.
- Wrong host. Document Generation is on
na1.fusion.foxit.com; eSign is onna1.foxitesign.foxit.com. Sending a DocGen body to the eSign host (or the reverse) returns an auth or not-found error. - Credential not attached. If a Foxit call comes back
401, the most common cause is an HTTP Request node with Authentication left on None. Attach the Custom Auth credential (DocGen) or add theAuthorization: Bearerheader (eSign). - Token expired or missing. The eSign bearer token is not permanent. If createfolder returns
401after the pipeline has been idle, re-run the token node so a freshaccess_tokenflows into the header. - Tag names do not match. A
documentValueskey that does not exactly match a{{ }}tag renders that field blank with no error. Cross-check the names, or runAnalyzeDocumentBase64to list the real tags. - Base64 payload too big. An HTTP 500 with a “cannot be larger than 4 MB” body means the encoded
.docxexceeded the cap. Slim the template per Step 1. - Party numbers do not line up. A Text Tag pointing at a party not present in the
partiesarray is silently dropped, so that signer is never asked to sign. Match token party numbers topartiesentries.
Foxit API n8n FAQ
Do I need a paid plan to test this pipeline?
The free Foxit Developer plan gives you 500 credits per year with instant activation and no credit card required. n8n runs as the source-available Community Edition in Docker with no license key. Both are fully functional for building and testing the complete pipeline described here.
Are the Document Generation and eSign credentials the same?
No. Each API uses its own Client ID and Secret. You will store two separate credential sets in n8n, one for the Document Generation header-based auth and one for the eSign OAuth2 token exchange. They come from separate sections of the Foxit APIs Dashboard.
What if my CRM does not support outbound webhooks?
Use an n8n Schedule node as your trigger. Add an HTTP Request node after it that polls your CRM’s REST API for deals matching your target stage criteria. HubSpot’s Deal Search API and Salesforce’s SOQL query endpoint both support this pattern. The rest of the workflow runs identically from that point forward.
Can I place signature fields without editing the Word template?
Yes. If you prefer not to embed Text Tags in the DOCX, omit processTextTags: true from your createfolder call and place fields manually using the Foxit eSign web editor after the document is uploaded. The manual approach works fine for low-volume workflows but does not scale as cleanly as tag-based automation when the template changes frequently.
What is the lifecycle of a signing folder in Foxit eSign?
A folder moves through five states in order:
- DRAFT : created but not sent
- SHARED : sent to all parties
- PARTIALLY SIGNED : at least one party has signed
- COMPLETED : all parties have signed
- EXECUTED : the digital signature stamp is applied
Your n8n archival step should trigger on COMPLETED or EXECUTED. If you archive on COMPLETED and need the final stamp, wait for the EXECUTED event instead.
How should I handle token expiry in the eSign OAuth2 flow?
The expires_in field in the token response tells you how long the bearer token is valid. For high-frequency workflows, add a Code node before the createfolder call that checks whether the stored token is still valid and re-requests it if not. You can also request a fresh token at the start of each pipeline execution. The client-credentials grant is stateless, so there is no session overhead to worry about.
How do I debug a merged PDF that returns with blank fields?
Run your template through the Analyze endpoint at POST https://na1.fusion.foxit.com/document-generation/api/AnalyzeDocumentBase64 before executing the full pipeline. It returns every tag the API detects, making it straightforward to catch case mismatches or typos. The merge API performs a case-sensitive lookup, so a template field named {{First_Name}} will not match a JSON key of first_name.
What to Build Next
The fastest way to prove the pipeline is to run the middle of it by hand first. Spin up the local n8n container, activate the free Foxit Developer plan, pull your Client ID and Secret from the APIs Dashboard, and fire the Document Generation POST against invoice_simple.docx from a REST client like Postman or cURL. Once that returns a base64 PDF, you know the hardest call works before any nodes are wired.
From there, two durable extensions are worth building. Save your signing setup as a reusable eSign template with POST /api/templates/createtemplate so future sends skip the field setup, and version the n8n workflow itself by exporting its JSON, so the whole chain can be redeployed or shared across the team.