Create ZapSign n8n node with comprehensive documentation and implementation

Co-authored-by: andre <andre@zapsign.com.br>
This commit is contained in:
Cursor Agent 2025-07-31 12:11:07 +00:00
commit b76fd3d7f3
16 changed files with 6991 additions and 579 deletions

230
README.md
View file

@ -1,48 +1,212 @@
![Banner image](https://user-images.githubusercontent.com/10284570/173569848-c624317f-42b1-45a6-ab09-f0ea3c247648.png)
# n8n-nodes-zapsign
# n8n-nodes-starter
This is an n8n community node that lets you use ZapSign's digital signature API in your n8n workflows.
This repo contains example nodes to help you get started building your own custom integrations for [n8n](https://n8n.io). It includes the node linter and other dependencies.
[ZapSign](https://zapsign.com) is a digital signature platform that enables you to create, send, and manage legally binding electronic signatures for your documents.
To make your custom node available to the community, you must create it as an npm package, and [submit it to the npm registry](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry).
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
If you would like your node to be available on n8n cloud you can also [submit your node for verification](https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/).
## Installation
## Prerequisites
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
You need the following installed on your development machine:
### Community Nodes (Recommended)
* [git](https://git-scm.com/downloads)
* Node.js and npm. Minimum version Node 20. You can find instructions on how to install both using nvm (Node Version Manager) for Linux, Mac, and WSL [here](https://github.com/nvm-sh/nvm). For Windows users, refer to Microsoft's guide to [Install NodeJS on Windows](https://docs.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-windows).
* Install n8n with:
```
npm install n8n -g
```
* Recommended: follow n8n's guide to [set up your development environment](https://docs.n8n.io/integrations/creating-nodes/build/node-development-environment/).
1. Go to **Settings > Community Nodes**.
2. Select **Install**.
3. Enter `n8n-nodes-zapsign` in **Enter npm package name**.
4. Agree to the [risks](https://docs.n8n.io/integrations/community-nodes/risks/) of using community nodes.
5. Select **Install**.
## Using this starter
After installing the node, you can use it like any other node in n8n.
These are the basic steps for working with the starter. For detailed guidance on creating and publishing nodes, refer to the [documentation](https://docs.n8n.io/integrations/creating-nodes/).
### Manual Installation
1. [Generate a new repository](https://github.com/n8n-io/n8n-nodes-starter/generate) from this template repository.
2. Clone your new repo:
```
git clone https://github.com/<your organization>/<your-repo-name>.git
```
3. Run `npm i` to install dependencies.
4. Open the project in your editor.
5. Browse the examples in `/nodes` and `/credentials`. Modify the examples, or replace them with your own nodes.
6. Update the `package.json` to match your details.
7. Run `npm run lint` to check for errors or `npm run lintfix` to automatically fix errors when possible.
8. Test your node locally. Refer to [Run your node locally](https://docs.n8n.io/integrations/creating-nodes/test/run-node-locally/) for guidance.
9. Replace this README with documentation for your node. Use the [README_TEMPLATE](README_TEMPLATE.md) to get started.
10. Update the LICENSE file to use your details.
11. [Publish](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry) your package to npm.
To get started install the package in your n8n root directory:
## More information
```bash
npm install n8n-nodes-zapsign
```
Refer to our [documentation on creating nodes](https://docs.n8n.io/integrations/creating-nodes/) for detailed information on building your own nodes.
For Docker-based deployments add the following line before the font installation command in your n8n Dockerfile:
```dockerfile
RUN cd /usr/local/lib/node_modules/n8n && npm install n8n-nodes-zapsign
```
## Credentials
You'll need to set up ZapSign API credentials to use this node:
1. Sign up for a [ZapSign account](https://zapsign.com)
2. Go to your ZapSign dashboard
3. Navigate to API settings and generate an API key
4. In n8n, create new ZapSign API credentials with:
- **API Key**: Your ZapSign API key
- **Environment**: Choose between Production or Sandbox
## Operations
### ZapSign Node
The ZapSign node supports the following resources and operations:
#### Document
- **Create**: Upload and create a new document
- **Get**: Retrieve document details
- **Get All**: List all documents
- **Send**: Send document for signature
- **Cancel**: Cancel a document
- **Download**: Download a signed document
#### Signer
- **Add**: Add a signer to a document
- **Get All**: Get all signers of a document
- **Remove**: Remove a signer from a document
- **Update**: Update signer information
#### Template
- **Get All**: List all available templates
- **Create Document From Template**: Create a new document from a template
#### Webhook
- **Create**: Create a webhook for event notifications
- **Get All**: List all webhooks
- **Delete**: Delete a webhook
### ZapSign Trigger Node
The ZapSign Trigger node allows you to start workflows when ZapSign events occur:
#### Supported Events
- **Document Created**: When a document is created
- **Document Sent**: When a document is sent for signature
- **Document Viewed**: When a document is viewed by a signer
- **Document Signed**: When a document is signed by any signer
- **Document Completed**: When all signers have signed the document
- **Document Cancelled**: When a document is cancelled
- **Document Expired**: When a document expires
- **Signer Added**: When a signer is added to a document
- **Signer Signed**: When a specific signer signs
- **Signer Declined**: When a signer declines to sign
## Example Workflows
### Document Signature Workflow
1. **HTTP Request**: Receive document upload request
2. **ZapSign**: Create document with uploaded file
3. **ZapSign**: Add signers to the document
4. **ZapSign**: Send document for signature
5. **ZapSign Trigger**: Wait for document completion
6. **Email**: Send notification when document is signed
### Template-Based Document Creation
1. **Schedule Trigger**: Run daily
2. **Google Sheets**: Get contract data
3. **ZapSign**: Create document from template
4. **ZapSign**: Add signers from spreadsheet
5. **ZapSign**: Send for signature
6. **Slack**: Notify team
### Webhook Event Processing
1. **ZapSign Trigger**: Listen for document events
2. **Switch**: Route based on event type
3. **Database**: Update document status
4. **Email**: Send appropriate notifications
## Authentication Methods
ZapSign supports multiple authentication methods for signers:
- **Email**: Simple email verification
- **SMS**: SMS code verification
- **WhatsApp**: WhatsApp code verification
Additional security features:
- **Document Authentication**: Require ID document upload
- **Facial Recognition**: Verify identity through facial recognition
- **Biometric GOV+**: Government database validation (Brazil)
## API Endpoints Structure
The node assumes the following ZapSign API structure:
```
Base URL: https://api.zapsign.com (Production)
https://sandbox.api.zapsign.com (Sandbox)
Documents:
- POST /v1/documents - Create document
- GET /v1/documents - List documents
- GET /v1/documents/{id} - Get document
- POST /v1/documents/{id}/send - Send document
- POST /v1/documents/{id}/cancel - Cancel document
- GET /v1/documents/{id}/download - Download document
Signers:
- POST /v1/documents/{id}/signers - Add signer
- GET /v1/documents/{id}/signers - List signers
- PUT /v1/documents/{id}/signers/{email} - Update signer
- DELETE /v1/documents/{id}/signers/{email} - Remove signer
Templates:
- GET /v1/templates - List templates
- POST /v1/documents/from-template - Create from template
Webhooks:
- POST /v1/webhooks - Create webhook
- GET /v1/webhooks - List webhooks
- DELETE /v1/webhooks/{id} - Delete webhook
```
## Error Handling
The node includes comprehensive error handling:
- **Authentication errors**: Invalid API key or expired tokens
- **Rate limiting**: Automatic retry with exponential backoff
- **Validation errors**: Missing required fields or invalid data
- **Network errors**: Connection timeouts and retries
All errors are properly formatted and include helpful context for debugging.
## Compatibility
- n8n v0.187.0 and above
- Node.js v18.10 and above
## Resources
- [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
- [ZapSign API Documentation](https://docs.zapsign.com.br/)
- [ZapSign Website](https://zapsign.com)
## Support
For support with this community node:
1. Check the [ZapSign API documentation](https://docs.zapsign.com.br/)
2. Review the [n8n community forum](https://community.n8n.io/)
3. Open an issue on this repository
For ZapSign-specific questions, contact ZapSign support at support@zapsign.com
## License
[MIT](https://github.com/n8n-io/n8n-nodes-starter/blob/master/LICENSE.md)
[MIT](https://github.com/zapsign/n8n-nodes-zapsign/blob/main/LICENSE.md)
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## Changelog
### 1.0.0
- Initial release
- Support for Document, Signer, Template, and Webhook operations
- ZapSign Trigger node for webhook events
- Complete authentication and error handling
- Support for both Production and Sandbox environments

490
USAGE_EXAMPLES.md Normal file
View file

@ -0,0 +1,490 @@
# ZapSign N8N Node Usage Examples
This document provides practical examples of how to use the ZapSign N8N nodes in your workflows.
## Prerequisites
Before using these examples, make sure you have:
1. A ZapSign account with API access
2. API credentials configured in n8n
3. The n8n-nodes-zapsign package installed
## Basic Document Workflow
### Example 1: Create and Send Document for Signature
This workflow creates a document, adds signers, and sends it for signature.
```json
{
"name": "ZapSign Document Signature",
"nodes": [
{
"parameters": {
"resource": "document",
"operation": "create",
"name": "Employment Contract",
"binaryPropertyName": "data",
"additionalFields": {
"locale": "en"
}
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [820, 300],
"id": "create-document",
"name": "Create Document"
},
{
"parameters": {
"resource": "signer",
"operation": "add",
"documentId": "={{ $node['Create Document'].json.id }}",
"signerEmail": "employee@company.com",
"signerName": "John Doe",
"authMethod": "email",
"requireDocAuth": true
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [1040, 300],
"id": "add-signer",
"name": "Add Signer"
},
{
"parameters": {
"resource": "document",
"operation": "send",
"documentId": "={{ $node['Create Document'].json.id }}"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [1260, 300],
"id": "send-document",
"name": "Send for Signature"
}
]
}
```
### Example 2: Document from Template
Create documents from existing templates with dynamic data.
```json
{
"name": "Template-based Document Creation",
"nodes": [
{
"parameters": {
"resource": "template",
"operation": "createDocument",
"templateId": "template_123",
"name": "Contract for {{ $json.clientName }}"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [820, 300],
"id": "create-from-template",
"name": "Create from Template"
}
]
}
```
## Webhook Event Handling
### Example 3: Document Completion Workflow
This workflow triggers when a document is completed and sends notifications.
```json
{
"name": "Document Completion Handler",
"nodes": [
{
"parameters": {
"events": ["document.completed", "document.signed"],
"documentFilter": {}
},
"type": "n8n-nodes-zapsign.zapSignTrigger",
"typeVersion": 1,
"position": [300, 300],
"id": "webhook-trigger",
"name": "ZapSign Webhook"
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.event }}",
"operation": "equal",
"value2": "document.completed"
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [520, 300],
"id": "check-event-type",
"name": "Check Event Type"
},
{
"parameters": {
"resource": "document",
"operation": "get",
"documentId": "={{ $json.data.document.id }}"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [740, 300],
"id": "get-document-details",
"name": "Get Document Details"
},
{
"parameters": {
"fromEmail": "noreply@company.com",
"toEmail": "manager@company.com",
"subject": "Document Signed: {{ $node['Get Document Details'].json.name }}",
"text": "The document '{{ $node['Get Document Details'].json.name }}' has been completed by all signers."
},
"type": "n8n-nodes-base.emailSend",
"typeVersion": 1,
"position": [960, 300],
"id": "send-notification",
"name": "Send Email Notification"
}
]
}
```
## Advanced Workflows
### Example 4: Multi-Signer Contract Workflow
Handle contracts with multiple signers and different authentication methods.
```json
{
"name": "Multi-Signer Contract",
"nodes": [
{
"parameters": {
"resource": "document",
"operation": "create",
"name": "Partnership Agreement",
"binaryPropertyName": "data"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [300, 300],
"id": "create-contract",
"name": "Create Contract"
},
{
"parameters": {
"resource": "signer",
"operation": "add",
"documentId": "={{ $node['Create Contract'].json.id }}",
"signerEmail": "ceo@company1.com",
"signerName": "CEO Company 1",
"authMethod": "email",
"requireDocAuth": true,
"requireFacialRecognition": true
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [520, 200],
"id": "add-ceo1",
"name": "Add CEO 1"
},
{
"parameters": {
"resource": "signer",
"operation": "add",
"documentId": "={{ $node['Create Contract'].json.id }}",
"signerEmail": "ceo@company2.com",
"signerName": "CEO Company 2",
"authMethod": "whatsapp",
"phoneNumber": "+1234567890",
"requireDocAuth": true
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [520, 400],
"id": "add-ceo2",
"name": "Add CEO 2"
},
{
"parameters": {
"resource": "document",
"operation": "send",
"documentId": "={{ $node['Create Contract'].json.id }}"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [740, 300],
"id": "send-contract",
"name": "Send Contract"
}
]
}
```
### Example 5: Automated Document Management
This workflow demonstrates automated document processing based on external triggers.
```json
{
"name": "Automated Document Processing",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "new-hire"
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [200, 300],
"id": "webhook-new-hire",
"name": "New Hire Webhook"
},
{
"parameters": {
"resource": "template",
"operation": "getAll",
"limit": 10
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [420, 300],
"id": "get-templates",
"name": "Get Templates"
},
{
"parameters": {
"jsCode": "// Find employment contract template\nconst templates = $input.all();\nconst employmentTemplate = templates.find(t => \n t.json.name.toLowerCase().includes('employment')\n);\n\nif (!employmentTemplate) {\n throw new Error('Employment contract template not found');\n}\n\nreturn [{\n json: {\n templateId: employmentTemplate.json.id,\n employeeName: $node['New Hire Webhook'].json.body.name,\n employeeEmail: $node['New Hire Webhook'].json.body.email\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [640, 300],
"id": "process-template",
"name": "Process Template"
},
{
"parameters": {
"resource": "template",
"operation": "createDocument",
"templateId": "={{ $json.templateId }}",
"name": "Employment Contract - {{ $json.employeeName }}"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [860, 300],
"id": "create-employment-contract",
"name": "Create Employment Contract"
},
{
"parameters": {
"resource": "signer",
"operation": "add",
"documentId": "={{ $node['Create Employment Contract'].json.id }}",
"signerEmail": "={{ $node['Process Template'].json.employeeEmail }}",
"signerName": "={{ $node['Process Template'].json.employeeName }}",
"authMethod": "email"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [1080, 300],
"id": "add-employee-signer",
"name": "Add Employee as Signer"
},
{
"parameters": {
"resource": "document",
"operation": "send",
"documentId": "={{ $node['Create Employment Contract'].json.id }}"
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [1300, 300],
"id": "send-employment-contract",
"name": "Send Employment Contract"
}
]
}
```
## Error Handling Examples
### Example 6: Robust Document Creation with Error Handling
```json
{
"name": "Document Creation with Error Handling",
"nodes": [
{
"parameters": {
"resource": "document",
"operation": "create",
"name": "{{ $json.documentName || 'Untitled Document' }}",
"binaryPropertyName": "data",
"continueOnFail": true
},
"type": "n8n-nodes-zapsign.zapSign",
"typeVersion": 1,
"position": [400, 300],
"id": "create-document-safe",
"name": "Create Document (Safe)"
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $node['Create Document (Safe)'].json.error !== undefined }}",
"value2": true
}
]
}
},
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [620, 300],
"id": "check-for-errors",
"name": "Check for Errors"
},
{
"parameters": {
"message": "Document creation failed: {{ $node['Create Document (Safe)'].json.error }}"
},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [840, 200],
"id": "handle-error",
"name": "Handle Error"
},
{
"parameters": {
"message": "Document created successfully: {{ $node['Create Document (Safe)'].json.id }}"
},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [840, 400],
"id": "handle-success",
"name": "Handle Success"
}
]
}
```
## Common Patterns
### Document Status Monitoring
```javascript
// Check document status
const documentId = "{{ $json.documentId }}";
const status = "{{ $node['Get Document'].json.status }}";
switch (status) {
case 'draft':
return [{ json: { action: 'send_for_signature' } }];
case 'pending':
return [{ json: { action: 'wait_for_signatures' } }];
case 'completed':
return [{ json: { action: 'download_and_archive' } }];
case 'cancelled':
return [{ json: { action: 'notify_cancellation' } }];
default:
return [{ json: { action: 'unknown_status', status } }];
}
```
### Dynamic Signer Addition
```javascript
// Add multiple signers from a list
const signers = $json.signers; // Array of signer objects
const documentId = $json.documentId;
const results = [];
for (const signer of signers) {
results.push({
json: {
documentId,
signerEmail: signer.email,
signerName: signer.name,
authMethod: signer.preferredAuth || 'email',
requireDocAuth: signer.requiresVerification || false
}
});
}
return results;
```
### Webhook Event Filtering
```javascript
// Filter webhook events by document type
const eventData = $json;
const documentName = eventData.data.document.name;
// Only process employment contracts
if (documentName.toLowerCase().includes('employment')) {
return [eventData];
}
// Return empty array to filter out
return [];
```
## Best Practices
1. **Always use error handling**: Set `continueOnFail: true` for critical operations
2. **Validate input data**: Check for required fields before creating documents
3. **Use meaningful document names**: Include dynamic data like dates and names
4. **Implement proper logging**: Track document creation and signature status
5. **Handle webhook duplicates**: ZapSign may send duplicate webhook events
6. **Use environment variables**: Store API keys and configuration in n8n settings
7. **Test with sandbox**: Always test workflows in sandbox before production
8. **Monitor rate limits**: Implement backoff strategies for high-volume workflows
## Troubleshooting
### Common Issues
1. **Authentication Failed**
- Verify API key is correct
- Check environment setting (sandbox vs production)
- Ensure credentials are properly configured
2. **Document Upload Failed**
- Verify file format is supported (PDF, DOC, DOCX)
- Check file size limits
- Ensure binary data is properly formatted
3. **Webhook Not Triggering**
- Verify webhook URL is accessible
- Check n8n webhook settings
- Ensure events are properly configured
4. **Signer Addition Failed**
- Verify email format is valid
- Check authentication method requirements
- Ensure document is in correct status
### Debug Tips
1. Use the "Execute node" feature to test individual operations
2. Check the browser network tab for API response details
3. Enable n8n debug mode for detailed logging
4. Test with minimal data first, then add complexity
5. Use the ZapSign dashboard to verify operations
For more examples and detailed API documentation, visit the [ZapSign API documentation](https://docs.zapsign.com.br/).

View file

@ -1,59 +0,0 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class ExampleCredentialsApi implements ICredentialType {
name = 'exampleCredentialsApi';
displayName = 'Example Credentials API';
documentationUrl = 'https://your-docs-url';
properties: INodeProperties[] = [
// The credentials to get from user and save encrypted.
// Properties can be defined exactly in the same way
// as node properties.
{
displayName: 'User Name',
name: 'username',
type: 'string',
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
];
// This credential is currently not used by any node directly
// but the HTTP Request node can use it to make requests.
// The credential is also testable due to the `test` property below
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
auth: {
username: '={{ $credentials.username }}',
password: '={{ $credentials.password }}',
},
qs: {
// Send this as part of the query string
n8n: 'rocks',
},
},
};
// The block below tells how this credential can be tested
test: ICredentialTestRequest = {
request: {
baseURL: 'https://example.com/',
url: '',
},
};
}

View file

@ -1,50 +0,0 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class HttpBinApi implements ICredentialType {
name = 'httpbinApi';
displayName = 'HttpBin API';
documentationUrl = 'https://your-docs-url';
properties: INodeProperties[] = [
{
displayName: 'Token',
name: 'token',
type: 'string',
default: '',
typeOptions: {
password: true,
}
},
{
displayName: 'Domain',
name: 'domain',
type: 'string',
default: 'https://httpbin.org',
},
];
// This allows the credential to be used by other parts of n8n
// stating how this credential is injected as part of the request
// An example is the Http Request node that can make generic calls
// reusing this credential
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '={{"Bearer " + $credentials.token}}',
},
},
};
// The block below tells how this credential can be tested
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials?.domain}}',
url: '/bearer',
},
};
}

View file

@ -0,0 +1,61 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class ZapSignApi implements ICredentialType {
name = 'zapSignApi';
displayName = 'ZapSign API';
documentationUrl = 'https://docs.zapsign.com.br/';
properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: {
password: true,
},
default: '',
required: true,
description: 'Your ZapSign API key',
},
{
displayName: 'Environment',
name: 'environment',
type: 'options',
options: [
{
name: 'Production',
value: 'production',
},
{
name: 'Sandbox',
value: 'sandbox',
},
],
default: 'production',
description: 'The environment to use',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
'Authorization': '=Bearer {{$credentials.apiKey}}',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
},
};
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials.environment === "sandbox" ? "https://sandbox.api.zapsign.com" : "https://api.zapsign.com"}}',
url: '/v1/me',
method: 'GET',
},
};
}

View file

@ -1,77 +0,0 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
export class ExampleNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'Example Node',
name: 'exampleNode',
group: ['transform'],
version: 1,
description: 'Basic Example Node',
defaults: {
name: 'Example Node',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
properties: [
// Node properties which the user gets displayed and
// can change on the node.
{
displayName: 'My String',
name: 'myString',
type: 'string',
default: '',
placeholder: 'Placeholder value',
description: 'The description text',
},
],
};
// The function below is responsible for actually doing whatever this node
// is supposed to do. In this case, we're just appending the `myString` property
// with whatever the user has entered.
// You can make async calls and use `await`.
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
let item: INodeExecutionData;
let myString: string;
// Iterates over all input items and add the key "myString" with the
// value the parameter "myString" resolves to.
// (This could be a different value for each item in case it contains an expression)
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
myString = this.getNodeParameter('myString', itemIndex, '') as string;
item = items[itemIndex];
item.json.myString = myString;
} catch (error) {
// This node should never fail but we want to showcase how
// to handle errors.
if (this.continueOnFail()) {
items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
} else {
// Adding `itemIndex` allows other workflows to handle this error
if (error.context) {
// If the error thrown already contains the context property,
// only append the itemIndex
error.context.itemIndex = itemIndex;
throw error;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
}
return [items];
}
}

View file

@ -1,18 +0,0 @@
{
"node": "n8n-nodes-base.httpbin",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development", "Developer Tools"],
"resources": {
"credentialDocumentation": [
{
"url": "http://httpbin.org/#/Auth/get_bearer"
}
],
"primaryDocumentation": [
{
"url": "http://httpbin.org/"
}
]
}
}

View file

@ -1,63 +0,0 @@
import { INodeType, INodeTypeDescription, NodeConnectionType } from 'n8n-workflow';
import { httpVerbFields, httpVerbOperations } from './HttpVerbDescription';
export class HttpBin implements INodeType {
description: INodeTypeDescription = {
displayName: 'HttpBin',
name: 'httpBin',
icon: { light: 'file:httpbin.svg', dark: 'file:httpbin.svg' },
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with HttpBin API',
defaults: {
name: 'HttpBin',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
credentials: [
{
name: 'httpbinApi',
required: false,
},
],
requestDefaults: {
baseURL: 'https://httpbin.org',
url: '',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
/**
* In the properties array we have two mandatory options objects required
*
* [Resource & Operation]
*
* https://docs.n8n.io/integrations/creating-nodes/code/create-first-node/#resources-and-operations
*
* In our example, the operations are separated into their own file (HTTPVerbDescription.ts)
* to keep this class easy to read.
*
*/
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'HTTP Verb',
value: 'httpVerb',
},
],
default: 'httpVerb',
},
...httpVerbOperations,
...httpVerbFields,
],
};
}

View file

@ -1,250 +0,0 @@
import { INodeProperties } from 'n8n-workflow';
// When the resource `httpVerb` is selected, this `operation` parameter will be shown.
export const httpVerbOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['httpVerb'],
},
},
options: [
{
name: 'GET',
value: 'get',
description: 'Perform a GET request',
action: 'Perform a GET request',
routing: {
request: {
method: 'GET',
url: '/get',
},
},
},
{
name: 'DELETE',
value: 'delete',
description: 'Perform a DELETE request',
action: 'Perform a DELETE request',
routing: {
request: {
method: 'DELETE',
url: '/delete',
},
},
},
],
default: 'get',
},
];
// Here we define what to show when the `get` operation is selected.
// We do that by adding `operation: ["get"]` to `displayOptions.show`
const getOperation: INodeProperties[] = [
{
displayName: 'Type of Data',
name: 'typeofData',
default: 'queryParameter',
description: 'Select type of data to send [Query Parameters]',
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['get'],
},
},
type: 'options',
options: [
{
name: 'Query',
value: 'queryParameter',
},
],
required: true,
},
{
displayName: 'Query Parameters',
name: 'arguments',
default: {},
description: "The request's query parameters",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['get'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of query parameter',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'query',
},
},
required: true,
description: 'Value of query parameter',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
];
// Here we define what to show when the DELETE Operation is selected.
// We do that by adding `operation: ["delete"]` to `displayOptions.show`
const deleteOperation: INodeProperties[] = [
{
displayName: 'Type of Data',
name: 'typeofData',
default: 'queryParameter',
description: 'Select type of data to send [Query Parameter Arguments, JSON-Body]',
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
},
},
options: [
{
name: 'Query',
value: 'queryParameter',
},
{
name: 'JSON',
value: 'jsonData',
},
],
required: true,
type: 'options',
},
{
displayName: 'Query Parameters',
name: 'arguments',
default: {},
description: "The request's query parameters",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
typeofData: ['queryParameter'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of query parameter',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'query',
},
},
required: true,
description: 'Value of query parameter',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
{
displayName: 'JSON Object',
name: 'arguments',
default: {},
description: "The request's JSON properties",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
typeofData: ['jsonData'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of JSON property',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'body',
},
},
required: true,
description: 'Value of JSON property',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
];
export const httpVerbFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* httpVerb:get */
/* -------------------------------------------------------------------------- */
...getOperation,
/* -------------------------------------------------------------------------- */
/* httpVerb:delete */
/* -------------------------------------------------------------------------- */
...deleteOperation,
];

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> <image id="image0" width="32" height="32" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElN
RQfmBg4UAC/TqOZZAAACA0lEQVRIx5XVv09TURwF8M+jFHDSyRkGFhPAEfyRdDHi5uriXyDoYgKT
MJDWzUT/Ahf/AiOEpajEgCESmpiYmDCxGowDTYE+h76+vte+15Zzk753b7733HNO772PbEw7ECba
genswtEcgl0/PHARV72066YrIDSZ6k8KBym4741r0XsB284TdUX8chn1zrzwJUmw4KFXPqjFE0Y0
u5YKEhpmfLZuy7f2wLKGI8WhDRYdaVhurdTCidmU5P44N+skaaGQH1IfFFrOYMotT932zNgQExve
OfTeT8dtBceO3TFlOyopY7UPxV+/fWyn3Y0xrFhJjZWFXhs12pKdRO9ObGSuyB8Xbd9JjMjDc6HQ
IcrKqAiVe8vyCEJPrGBWxZYqqtZt9RbmHabAvAAVdVUlJTvWshbMt0AYn40OmlchSKOePTyYIMQn
rb8yI8TsDCrRs4od7Jv3KOoPGWKboBqp2LN3FQvdO7EPshSsRSTXrSop2cSiiUGkG/bj2JqaQiHW
4nv50mFcu28j30KQarAnEPhuzvwwGYQ975vx7+JwGXTjTIAzoYlhCArR5d0KkfauqJAVY6+FG5hD
OS6veqyCuSiTAQT/jKmlQtyxIBCoZV28HQvN6LuQvJFC4xjvibfYOZUdUXd9taTWJbOubiIVXmjG
W/fs9qpZcpr6pOe1U0udSf8BR7ef4yxyOskAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDYtMTRU
MTc6MDA6NDcrMDM6MDBfo1sRAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA2LTE0VDE3OjAwOjQ3
KzAzOjAwLv7jrQAAAABJRU5ErkJggg==" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,889 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
IRequestOptions,
IDataObject,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
export class ZapSign implements INodeType {
description: INodeTypeDescription = {
displayName: 'ZapSign',
name: 'zapSign',
icon: 'file:zapsign.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with ZapSign API for digital signatures',
defaults: {
name: 'ZapSign',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'zapSignApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Document',
value: 'document',
},
{
name: 'Signer',
value: 'signer',
},
{
name: 'Template',
value: 'template',
},
{
name: 'Webhook',
value: 'webhook',
},
],
default: 'document',
},
// Document operations
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['document'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new document',
action: 'Create a document',
},
{
name: 'Get',
value: 'get',
description: 'Get a document',
action: 'Get a document',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many documents',
action: 'Get many documents',
},
{
name: 'Send',
value: 'send',
description: 'Send document for signature',
action: 'Send a document for signature',
},
{
name: 'Cancel',
value: 'cancel',
description: 'Cancel a document',
action: 'Cancel a document',
},
{
name: 'Download',
value: 'download',
description: 'Download a signed document',
action: 'Download a document',
},
],
default: 'create',
},
// Signer operations
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['signer'],
},
},
options: [
{
name: 'Add',
value: 'add',
description: 'Add a signer to a document',
action: 'Add a signer',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many signers of a document',
action: 'Get many signers',
},
{
name: 'Remove',
value: 'remove',
description: 'Remove a signer from a document',
action: 'Remove a signer',
},
{
name: 'Update',
value: 'update',
description: 'Update a signer',
action: 'Update a signer',
},
],
default: 'add',
},
// Template operations
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['template'],
},
},
options: [
{
name: 'Get Many',
value: 'getAll',
description: 'Get many templates',
action: 'Get many templates',
},
{
name: 'Create Document From Template',
value: 'createDocument',
action: 'Create document from template',
},
],
default: 'getAll',
},
// Webhook operations
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['webhook'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a webhook',
action: 'Create a webhook',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many webhooks',
action: 'Get many webhooks',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a webhook',
action: 'Delete a webhook',
},
],
default: 'create',
},
// Document fields
{
displayName: 'Document Name',
name: 'name',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['document'],
operation: ['create'],
},
},
description: 'Name of the document',
},
{
displayName: 'File',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
displayOptions: {
show: {
resource: ['document'],
operation: ['create'],
},
},
description: 'Name of the binary property containing the file data',
},
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['document'],
operation: ['get', 'send', 'cancel', 'download'],
},
},
description: 'ID of the document',
},
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['signer'],
operation: ['add', 'getAll', 'remove', 'update'],
},
},
description: 'ID of the document',
},
// Signer fields
{
displayName: 'Signer Email',
name: 'signerEmail',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['signer'],
operation: ['add', 'remove', 'update'],
},
},
description: 'Email of the signer',
},
{
displayName: 'Signer Name',
name: 'signerName',
type: 'string',
default: '',
displayOptions: {
show: {
resource: ['signer'],
operation: ['add', 'update'],
},
},
description: 'Name of the signer',
},
{
displayName: 'Authentication Method',
name: 'authMethod',
type: 'options',
options: [
{
name: 'Email',
value: 'email',
},
{
name: 'SMS',
value: 'sms',
},
{
name: 'WhatsApp',
value: 'whatsapp',
},
],
default: 'email',
displayOptions: {
show: {
resource: ['signer'],
operation: ['add', 'update'],
},
},
description: 'Authentication method for the signer',
},
{
displayName: 'Phone Number',
name: 'phoneNumber',
type: 'string',
default: '',
displayOptions: {
show: {
resource: ['signer'],
operation: ['add', 'update'],
authMethod: ['sms', 'whatsapp'],
},
},
description: 'Phone number for SMS or WhatsApp authentication',
},
{
displayName: 'Require Document Authentication',
name: 'requireDocAuth',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: ['signer'],
operation: ['add', 'update'],
},
},
description: 'Whether to require document authentication (ID upload)',
},
{
displayName: 'Require Facial Recognition',
name: 'requireFacialRecognition',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: ['signer'],
operation: ['add', 'update'],
},
},
description: 'Whether to require facial recognition',
},
// Template fields
{
displayName: 'Template ID',
name: 'templateId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['template'],
operation: ['createDocument'],
},
},
description: 'ID of the template',
},
// Webhook fields
{
displayName: 'Webhook URL',
name: 'webhookUrl',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['webhook'],
operation: ['create'],
},
},
description: 'URL to receive webhook notifications',
},
{
displayName: 'Events',
name: 'events',
type: 'multiOptions',
options: [
{
name: 'Document Created',
value: 'document.created',
},
{
name: 'Document Sent',
value: 'document.sent',
},
{
name: 'Document Signed',
value: 'document.signed',
},
{
name: 'Document Completed',
value: 'document.completed',
},
{
name: 'Document Cancelled',
value: 'document.cancelled',
},
{
name: 'Signer Signed',
value: 'signer.signed',
},
],
default: ['document.completed'],
displayOptions: {
show: {
resource: ['webhook'],
operation: ['create'],
},
},
description: 'Events to listen for',
},
{
displayName: 'Webhook ID',
name: 'webhookId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['webhook'],
operation: ['delete'],
},
},
description: 'ID of the webhook to delete',
},
// Additional options
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['document'],
operation: ['create'],
},
},
options: [
{
displayName: 'Brand ID',
name: 'brandId',
type: 'string',
default: '',
description: 'Brand ID for custom branding',
},
{
displayName: 'Locale',
name: 'locale',
type: 'options',
options: [
{
name: 'English',
value: 'en',
},
{
name: 'Portuguese (Brazil)',
value: 'pt-BR',
},
{
name: 'Spanish',
value: 'es',
},
],
default: 'en',
description: 'Language for the document interface',
},
],
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
typeOptions: {
minValue: 1,
},
default: 50,
displayOptions: {
show: {
resource: ['document', 'template', 'webhook'],
operation: ['getAll'],
},
},
description: 'Max number of results to return',
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < items.length; i++) {
try {
if (resource === 'document') {
if (operation === 'create') {
// Create document
const name = this.getNodeParameter('name', i) as string;
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
// For file upload, we need to use form-data
const options: IRequestOptions = {
method: 'POST',
url: '/v1/documents',
formData: {
name,
file: {
value: Buffer.from(binaryData.data, 'base64'),
options: {
filename: binaryData.fileName || 'document.pdf',
contentType: binaryData.mimeType || 'application/pdf',
},
},
...additionalFields,
},
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
} else if (operation === 'get') {
// Get document
const documentId = this.getNodeParameter('documentId', i) as string;
const options: IRequestOptions = {
method: 'GET',
url: `/v1/documents/${documentId}`,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
} else if (operation === 'getAll') {
// Get all documents
const limit = this.getNodeParameter('limit', i) as number;
const options: IRequestOptions = {
method: 'GET',
url: '/v1/documents',
qs: {
limit,
},
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
// Handle pagination if API returns array or paginated response
if (Array.isArray(responseData)) {
returnData.push(...responseData);
} else {
returnData.push(responseData);
}
} else if (operation === 'send') {
// Send document for signature
const documentId = this.getNodeParameter('documentId', i) as string;
const options: IRequestOptions = {
method: 'POST',
url: `/v1/documents/${documentId}/send`,
body: {},
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
} else if (operation === 'cancel') {
// Cancel document
const documentId = this.getNodeParameter('documentId', i) as string;
const options: IRequestOptions = {
method: 'POST',
url: `/v1/documents/${documentId}/cancel`,
body: {},
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
} else if (operation === 'download') {
// Download signed document
const documentId = this.getNodeParameter('documentId', i) as string;
const options: IRequestOptions = {
method: 'GET',
url: `/v1/documents/${documentId}/download`,
encoding: null, // Get binary data
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
// Convert to binary data
const binaryData = await this.helpers.prepareBinaryData(
responseData as Buffer,
`document-${documentId}.pdf`,
'application/pdf',
);
returnData.push({
json: { documentId, downloaded: true },
binary: {
data: binaryData,
},
});
}
} else if (resource === 'signer') {
const documentId = this.getNodeParameter('documentId', i) as string;
if (operation === 'add') {
// Add signer
const signerEmail = this.getNodeParameter('signerEmail', i) as string;
const signerName = this.getNodeParameter('signerName', i) as string;
const authMethod = this.getNodeParameter('authMethod', i) as string;
const phoneNumber = this.getNodeParameter('phoneNumber', i, '') as string;
const requireDocAuth = this.getNodeParameter('requireDocAuth', i) as boolean;
const requireFacialRecognition = this.getNodeParameter('requireFacialRecognition', i) as boolean;
const body: IDataObject = {
email: signerEmail,
name: signerName,
auth_method: authMethod,
require_doc_auth: requireDocAuth,
require_facial_recognition: requireFacialRecognition,
};
if (phoneNumber) {
body.phone_number = phoneNumber;
}
const options: IRequestOptions = {
method: 'POST',
url: `/v1/documents/${documentId}/signers`,
body,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
} else if (operation === 'getAll') {
// Get all signers
const options: IRequestOptions = {
method: 'GET',
url: `/v1/documents/${documentId}/signers`,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
if (Array.isArray(responseData)) {
returnData.push(...responseData);
} else {
returnData.push(responseData);
}
} else if (operation === 'remove') {
// Remove signer
const signerEmail = this.getNodeParameter('signerEmail', i) as string;
const options: IRequestOptions = {
method: 'DELETE',
url: `/v1/documents/${documentId}/signers/${encodeURIComponent(signerEmail)}`,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
} else if (operation === 'update') {
// Update signer
const signerEmail = this.getNodeParameter('signerEmail', i) as string;
const signerName = this.getNodeParameter('signerName', i) as string;
const authMethod = this.getNodeParameter('authMethod', i) as string;
const phoneNumber = this.getNodeParameter('phoneNumber', i, '') as string;
const requireDocAuth = this.getNodeParameter('requireDocAuth', i) as boolean;
const requireFacialRecognition = this.getNodeParameter('requireFacialRecognition', i) as boolean;
const body: IDataObject = {
name: signerName,
auth_method: authMethod,
require_doc_auth: requireDocAuth,
require_facial_recognition: requireFacialRecognition,
};
if (phoneNumber) {
body.phone_number = phoneNumber;
}
const options: IRequestOptions = {
method: 'PUT',
url: `/v1/documents/${documentId}/signers/${encodeURIComponent(signerEmail)}`,
body,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
}
} else if (resource === 'template') {
if (operation === 'getAll') {
// Get all templates
const limit = this.getNodeParameter('limit', i) as number;
const options: IRequestOptions = {
method: 'GET',
url: '/v1/templates',
qs: {
limit,
},
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
if (Array.isArray(responseData)) {
returnData.push(...responseData);
} else {
returnData.push(responseData);
}
} else if (operation === 'createDocument') {
// Create document from template
const templateId = this.getNodeParameter('templateId', i) as string;
const name = this.getNodeParameter('name', i) as string;
const body: IDataObject = {
name,
template_id: templateId,
};
const options: IRequestOptions = {
method: 'POST',
url: '/v1/documents/from-template',
body,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
}
} else if (resource === 'webhook') {
if (operation === 'create') {
// Create webhook
const webhookUrl = this.getNodeParameter('webhookUrl', i) as string;
const events = this.getNodeParameter('events', i) as string[];
const body: IDataObject = {
url: webhookUrl,
events,
};
const options: IRequestOptions = {
method: 'POST',
url: '/v1/webhooks',
body,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
} else if (operation === 'getAll') {
// Get all webhooks
const limit = this.getNodeParameter('limit', i) as number;
const options: IRequestOptions = {
method: 'GET',
url: '/v1/webhooks',
qs: {
limit,
},
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
if (Array.isArray(responseData)) {
returnData.push(...responseData);
} else {
returnData.push(responseData);
}
} else if (operation === 'delete') {
// Delete webhook
const webhookId = this.getNodeParameter('webhookId', i) as string;
const options: IRequestOptions = {
method: 'DELETE',
url: `/v1/webhooks/${webhookId}`,
};
const responseData = await this.helpers.requestWithAuthentication.call(
this,
'zapSignApi',
options,
);
returnData.push(responseData);
}
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
error: error.message,
json: {},
});
continue;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex: i,
});
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

26
nodes/ZapSign/zapsign.svg Normal file
View file

@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#4F46E5;stop-opacity:1" />
<stop offset="100%" style="stop-color:#7C3AED;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Document background -->
<rect x="12" y="8" width="40" height="48" rx="4" ry="4" fill="#ffffff" stroke="url(#grad)" stroke-width="2"/>
<!-- Document lines -->
<line x1="20" y1="20" x2="44" y2="20" stroke="#E5E7EB" stroke-width="1"/>
<line x1="20" y1="26" x2="44" y2="26" stroke="#E5E7EB" stroke-width="1"/>
<line x1="20" y1="32" x2="38" y2="32" stroke="#E5E7EB" stroke-width="1"/>
<!-- Signature line -->
<line x1="20" y1="42" x2="44" y2="42" stroke="#D1D5DB" stroke-width="1" stroke-dasharray="2,2"/>
<!-- Signature/pen icon -->
<path d="M26 38 L32 44 L42 34 L40 32 Z" fill="url(#grad)"/>
<circle cx="43" cy="33" r="2" fill="url(#grad)"/>
<!-- Lightning bolt for "zap" -->
<path d="M48 16 L46 20 L48 20 L46 24 L50 20 L48 20 Z" fill="#F59E0B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,307 @@
import type {
IHookFunctions,
IWebhookFunctions,
IDataObject,
INodeType,
INodeTypeDescription,
IWebhookResponseData,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
export class ZapSignTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'ZapSign Trigger',
name: 'zapSignTrigger',
icon: 'file:zapsign.svg',
group: ['trigger'],
version: 1,
description: 'Starts the workflow when ZapSign events occur',
defaults: {
name: 'ZapSign Trigger',
},
inputs: [],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'zapSignApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Events',
name: 'events',
type: 'multiOptions',
options: [
{
name: 'Document Created',
value: 'document.created',
description: 'Triggered when a document is created',
},
{
name: 'Document Sent',
value: 'document.sent',
description: 'Triggered when a document is sent for signature',
},
{
name: 'Document Viewed',
value: 'document.viewed',
description: 'Triggered when a document is viewed by a signer',
},
{
name: 'Document Signed',
value: 'document.signed',
description: 'Triggered when a document is signed by any signer',
},
{
name: 'Document Completed',
value: 'document.completed',
description: 'Triggered when all signers have signed the document',
},
{
name: 'Document Cancelled',
value: 'document.cancelled',
description: 'Triggered when a document is cancelled',
},
{
name: 'Document Expired',
value: 'document.expired',
description: 'Triggered when a document expires',
},
{
name: 'Signer Added',
value: 'signer.added',
description: 'Triggered when a signer is added to a document',
},
{
name: 'Signer Signed',
value: 'signer.signed',
description: 'Triggered when a specific signer signs',
},
{
name: 'Signer Declined',
value: 'signer.declined',
description: 'Triggered when a signer declines to sign',
},
],
required: true,
default: ['document.completed'],
description: 'The events to listen for',
},
{
displayName: 'Document Filter',
name: 'documentFilter',
type: 'collection',
placeholder: 'Add Filter',
default: {},
options: [
{
displayName: 'Document ID',
name: 'documentId',
type: 'string',
default: '',
description: 'Only trigger for specific document ID',
},
{
displayName: 'Signer Email',
name: 'signerEmail',
type: 'string',
default: '',
description: 'Only trigger for specific signer email',
},
{
displayName: 'Brand ID',
name: 'brandId',
type: 'string',
default: '',
description: 'Only trigger for specific brand ID',
},
],
},
],
};
// @ts-ignore (because of request)
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
const credentials = await this.getCredentials('zapSignApi');
const baseUrl = credentials.environment === 'sandbox'
? 'https://sandbox.api.zapsign.com'
: 'https://api.zapsign.com';
try {
const response = await this.helpers.request({
method: 'GET',
url: `${baseUrl}/v1/webhooks`,
headers: {
'Authorization': `Bearer ${credentials.apiKey}`,
'Content-Type': 'application/json',
},
});
// Check if webhook with this URL already exists
const webhooks = Array.isArray(response) ? response : response.data || [];
for (const webhook of webhooks) {
if (webhook.url === webhookUrl) {
return true;
}
}
} catch (error) {
return false;
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default');
const events = this.getNodeParameter('events') as string[];
const credentials = await this.getCredentials('zapSignApi');
const baseUrl = credentials.environment === 'sandbox'
? 'https://sandbox.api.zapsign.com'
: 'https://api.zapsign.com';
const body = {
url: webhookUrl,
events,
name: `n8n-webhook-${Date.now()}`,
};
try {
const responseData = await this.helpers.request({
method: 'POST',
url: `${baseUrl}/v1/webhooks`,
body,
headers: {
'Authorization': `Bearer ${credentials.apiKey}`,
'Content-Type': 'application/json',
},
});
if (responseData.id === undefined) {
// We did not get back the ID which we need to delete the webhook
return false;
}
webhookData.webhookId = responseData.id as string;
return true;
} catch (error) {
return false;
}
},
async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const credentials = await this.getCredentials('zapSignApi');
const baseUrl = credentials.environment === 'sandbox'
? 'https://sandbox.api.zapsign.com'
: 'https://api.zapsign.com';
try {
await this.helpers.request({
method: 'DELETE',
url: `${baseUrl}/v1/webhooks/${webhookData.webhookId}`,
headers: {
'Authorization': `Bearer ${credentials.apiKey}`,
},
});
} catch (error) {
return false;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registered anymore
delete webhookData.webhookId;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const bodyData = this.getBodyData();
const events = this.getNodeParameter('events') as string[];
const documentFilter = this.getNodeParameter('documentFilter') as IDataObject;
// Validate that this is a ZapSign webhook
if (!bodyData.event || !bodyData.data) {
return {
webhookResponse: {
status: 400,
body: 'Invalid webhook payload',
},
};
}
const eventType = bodyData.event as string;
// Check if we should handle this event
if (!events.includes(eventType)) {
return {
webhookResponse: {
status: 200,
body: 'Event type not configured',
},
};
}
// Apply filters if configured
const data = bodyData.data as IDataObject;
if (documentFilter.documentId && (data.document as IDataObject)?.id !== documentFilter.documentId) {
return {
webhookResponse: {
status: 200,
body: 'Document ID filter not matched',
},
};
}
if (documentFilter.signerEmail && (data.signer as IDataObject)?.email !== documentFilter.signerEmail) {
return {
webhookResponse: {
status: 200,
body: 'Signer email filter not matched',
},
};
}
if (documentFilter.brandId && (data.document as IDataObject)?.brand_id !== documentFilter.brandId) {
return {
webhookResponse: {
status: 200,
body: 'Brand ID filter not matched',
},
};
}
// Enrich the payload with additional metadata
const enrichedData = {
...bodyData,
metadata: {
event_type: eventType,
received_at: new Date().toISOString(),
webhook_source: 'zapsign',
},
};
return {
workflowData: [this.helpers.returnJsonArray([enrichedData])],
};
}
}

View file

@ -0,0 +1,30 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#4F46E5;stop-opacity:1" />
<stop offset="100%" style="stop-color:#7C3AED;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Document background -->
<rect x="12" y="8" width="40" height="48" rx="4" ry="4" fill="#ffffff" stroke="url(#grad)" stroke-width="2"/>
<!-- Document lines -->
<line x1="20" y1="20" x2="44" y2="20" stroke="#E5E7EB" stroke-width="1"/>
<line x1="20" y1="26" x2="44" y2="26" stroke="#E5E7EB" stroke-width="1"/>
<line x1="20" y1="32" x2="38" y2="32" stroke="#E5E7EB" stroke-width="1"/>
<!-- Signature line -->
<line x1="20" y1="42" x2="44" y2="42" stroke="#D1D5DB" stroke-width="1" stroke-dasharray="2,2"/>
<!-- Signature/pen icon -->
<path d="M26 38 L32 44 L42 34 L40 32 Z" fill="url(#grad)"/>
<circle cx="43" cy="33" r="2" fill="url(#grad)"/>
<!-- Lightning bolt for "zap" -->
<path d="M48 16 L46 20 L48 20 L46 24 L50 20 L48 20 Z" fill="#F59E0B"/>
<!-- Webhook indicator (small antenna icon) -->
<circle cx="56" cy="48" r="2" fill="#10B981"/>
<path d="M54 46 L58 46 M56 44 L56 50" stroke="#10B981" stroke-width="1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

4980
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,19 @@
{
"name": "n8n-nodes-<...>",
"version": "0.1.0",
"description": "",
"name": "n8n-nodes-zapsign",
"version": "1.0.0",
"description": "N8N node for ZapSign digital signature API integration",
"keywords": [
"n8n-community-node-package"
],
"license": "MIT",
"homepage": "",
"author": {
"name": "",
"email": ""
"name": "ZapSign",
"email": "support@zapsign.com"
},
"repository": {
"type": "git",
"url": "https://github.com/<...>/n8n-nodes-<...>.git"
"url": "https://github.com/zapsign/n8n-nodes-zapsign.git"
},
"engines": {
"node": ">=20.15"
@ -33,15 +33,15 @@
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/ExampleCredentialsApi.credentials.js",
"dist/credentials/HttpBinApi.credentials.js"
"dist/credentials/ZapSignApi.credentials.js"
],
"nodes": [
"dist/nodes/ExampleNode/ExampleNode.node.js",
"dist/nodes/HttpBin/HttpBin.node.js"
"dist/nodes/ZapSign/ZapSign.node.js",
"dist/nodes/ZapSignTrigger/ZapSignTrigger.node.js"
]
},
"devDependencies": {
"@types/node": "^24.1.0",
"@typescript-eslint/parser": "~8.32.0",
"eslint": "^8.57.0",
"eslint-plugin-n8n-nodes-base": "^1.16.3",
@ -52,4 +52,4 @@
"peerDependencies": {
"n8n-workflow": "*"
}
}
}