mirror of
				https://github.com/n8n-io/n8n-nodes-starter.git
				synced 2025-10-30 14:52:27 -05:00 
			
		
		
		
	Merge pull request #1 from ZapSign/cursor/implement-zapsign-api-n8n-module-3938
Implement ZapSign API n8n module
This commit is contained in:
		
				commit
				
					
						80aa02d365
					
				
			
		
					 16 changed files with 6971 additions and 579 deletions
				
			
		
							
								
								
									
										230
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										230
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,48 +1,212 @@ | ||||||
|  | # 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.co) 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) | 1. Go to **Settings > Community Nodes**. | ||||||
| * 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). | 2. Select **Install**. | ||||||
| * Install n8n with: | 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. | ||||||
|   npm install n8n -g | 5. Select **Install**. | ||||||
|   ``` |  | ||||||
| * Recommended: follow n8n's guide to [set up your development environment](https://docs.n8n.io/integrations/creating-nodes/build/node-development-environment/). |  | ||||||
| 
 | 
 | ||||||
| ## 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. | To get started install the package in your n8n root directory: | ||||||
| 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. |  | ||||||
| 
 | 
 | ||||||
| ## 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.co) | ||||||
|  | 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.br/ (Production) | ||||||
|  |          https://sandbox.api.zapsign.com.br/ (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.co) | ||||||
|  | 
 | ||||||
|  | ## 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.co | ||||||
| 
 | 
 | ||||||
| ## License | ## 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
									
								
							
							
						
						
									
										490
									
								
								USAGE_EXAMPLES.md
									
										
									
									
									
										Normal 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/). | ||||||
|  | @ -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: '', |  | ||||||
| 		}, |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
|  | @ -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', |  | ||||||
| 		}, |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
							
								
								
									
										61
									
								
								credentials/ZapSignApi.credentials.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								credentials/ZapSignApi.credentials.ts
									
										
									
									
									
										Normal 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.br/" : "https://api.zapsign.com.br/"}}', | ||||||
|  | 			url: '/v1/me', | ||||||
|  | 			method: 'GET', | ||||||
|  | 		}, | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | @ -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]; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -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/" |  | ||||||
| 			} |  | ||||||
| 		] |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -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, |  | ||||||
| 		], |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
|  | @ -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, |  | ||||||
| ]; |  | ||||||
|  | @ -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 | 
							
								
								
									
										889
									
								
								nodes/ZapSign/ZapSign.node.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										889
									
								
								nodes/ZapSign/ZapSign.node.ts
									
										
									
									
									
										Normal 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)]; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								nodes/ZapSign/zapsign.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								nodes/ZapSign/zapsign.svg
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										307
									
								
								nodes/ZapSignTrigger/ZapSignTrigger.node.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								nodes/ZapSignTrigger/ZapSignTrigger.node.ts
									
										
									
									
									
										Normal 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.br/'  | ||||||
|  | 					: 'https://api.zapsign.com.br/'; | ||||||
|  | 
 | ||||||
|  | 				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.br/'  | ||||||
|  | 					: 'https://api.zapsign.com.br/'; | ||||||
|  | 
 | ||||||
|  | 				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.br/'  | ||||||
|  | 						: 'https://api.zapsign.com.br/'; | ||||||
|  | 
 | ||||||
|  | 					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])], | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								nodes/ZapSignTrigger/zapsign.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								nodes/ZapSignTrigger/zapsign.svg
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										4980
									
								
								package-lock.json
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										20
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								package.json
									
										
									
									
									
								
							|  | @ -1,19 +1,19 @@ | ||||||
| { | { | ||||||
|   "name": "n8n-nodes-<...>", |   "name": "n8n-nodes-zapsign", | ||||||
|   "version": "0.1.0", |   "version": "1.0.0", | ||||||
|   "description": "", |   "description": "N8N node for ZapSign digital signature API integration", | ||||||
|   "keywords": [ |   "keywords": [ | ||||||
|     "n8n-community-node-package" |     "n8n-community-node-package" | ||||||
|   ], |   ], | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "homepage": "", |   "homepage": "", | ||||||
|   "author": { |   "author": { | ||||||
|     "name": "", |     "name": "ZapSign", | ||||||
|     "email": "" |     "email": "support@zapsign.co" | ||||||
|   }, |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
|     "url": "https://github.com/<...>/n8n-nodes-<...>.git" |     "url": "https://github.com/zapsign/n8n-nodes-zapsign.git" | ||||||
|   }, |   }, | ||||||
|   "engines": { |   "engines": { | ||||||
|     "node": ">=20.15" |     "node": ">=20.15" | ||||||
|  | @ -33,15 +33,15 @@ | ||||||
|   "n8n": { |   "n8n": { | ||||||
|     "n8nNodesApiVersion": 1, |     "n8nNodesApiVersion": 1, | ||||||
|     "credentials": [ |     "credentials": [ | ||||||
|       "dist/credentials/ExampleCredentialsApi.credentials.js", |       "dist/credentials/ZapSignApi.credentials.js" | ||||||
|       "dist/credentials/HttpBinApi.credentials.js" |  | ||||||
|     ], |     ], | ||||||
|     "nodes": [ |     "nodes": [ | ||||||
|       "dist/nodes/ExampleNode/ExampleNode.node.js", |       "dist/nodes/ZapSign/ZapSign.node.js", | ||||||
|       "dist/nodes/HttpBin/HttpBin.node.js" |       "dist/nodes/ZapSignTrigger/ZapSignTrigger.node.js" | ||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|  |     "@types/node": "^24.1.0", | ||||||
|     "@typescript-eslint/parser": "~8.32.0", |     "@typescript-eslint/parser": "~8.32.0", | ||||||
|     "eslint": "^8.57.0", |     "eslint": "^8.57.0", | ||||||
|     "eslint-plugin-n8n-nodes-base": "^1.16.3", |     "eslint-plugin-n8n-nodes-base": "^1.16.3", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue