mirror of
https://github.com/n8n-io/n8n-nodes-starter.git
synced 2025-10-29 14:22:26 -05:00
Create ZapSign n8n node with comprehensive documentation and implementation
Co-authored-by: andre <andre@zapsign.com.br>
This commit is contained in:
parent
67ee5b8e80
commit
b76fd3d7f3
16 changed files with 6991 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.com) is a digital signature platform that enables you to create, send, and manage legally binding electronic signatures for your documents.
|
||||||
|
|
||||||
To make your custom node available to the community, you must create it as an npm package, and [submit it to the npm registry](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry).
|
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
|
||||||
|
|
||||||
If you would like your node to be available on n8n cloud you can also [submit your node for verification](https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/).
|
## Installation
|
||||||
|
|
||||||
## Prerequisites
|
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
|
||||||
|
|
||||||
You need the following installed on your development machine:
|
### Community Nodes (Recommended)
|
||||||
|
|
||||||
* [git](https://git-scm.com/downloads)
|
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.com)
|
||||||
|
2. Go to your ZapSign dashboard
|
||||||
|
3. Navigate to API settings and generate an API key
|
||||||
|
4. In n8n, create new ZapSign API credentials with:
|
||||||
|
- **API Key**: Your ZapSign API key
|
||||||
|
- **Environment**: Choose between Production or Sandbox
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
|
||||||
|
### ZapSign Node
|
||||||
|
|
||||||
|
The ZapSign node supports the following resources and operations:
|
||||||
|
|
||||||
|
#### Document
|
||||||
|
- **Create**: Upload and create a new document
|
||||||
|
- **Get**: Retrieve document details
|
||||||
|
- **Get All**: List all documents
|
||||||
|
- **Send**: Send document for signature
|
||||||
|
- **Cancel**: Cancel a document
|
||||||
|
- **Download**: Download a signed document
|
||||||
|
|
||||||
|
#### Signer
|
||||||
|
- **Add**: Add a signer to a document
|
||||||
|
- **Get All**: Get all signers of a document
|
||||||
|
- **Remove**: Remove a signer from a document
|
||||||
|
- **Update**: Update signer information
|
||||||
|
|
||||||
|
#### Template
|
||||||
|
- **Get All**: List all available templates
|
||||||
|
- **Create Document From Template**: Create a new document from a template
|
||||||
|
|
||||||
|
#### Webhook
|
||||||
|
- **Create**: Create a webhook for event notifications
|
||||||
|
- **Get All**: List all webhooks
|
||||||
|
- **Delete**: Delete a webhook
|
||||||
|
|
||||||
|
### ZapSign Trigger Node
|
||||||
|
|
||||||
|
The ZapSign Trigger node allows you to start workflows when ZapSign events occur:
|
||||||
|
|
||||||
|
#### Supported Events
|
||||||
|
- **Document Created**: When a document is created
|
||||||
|
- **Document Sent**: When a document is sent for signature
|
||||||
|
- **Document Viewed**: When a document is viewed by a signer
|
||||||
|
- **Document Signed**: When a document is signed by any signer
|
||||||
|
- **Document Completed**: When all signers have signed the document
|
||||||
|
- **Document Cancelled**: When a document is cancelled
|
||||||
|
- **Document Expired**: When a document expires
|
||||||
|
- **Signer Added**: When a signer is added to a document
|
||||||
|
- **Signer Signed**: When a specific signer signs
|
||||||
|
- **Signer Declined**: When a signer declines to sign
|
||||||
|
|
||||||
|
## Example Workflows
|
||||||
|
|
||||||
|
### Document Signature Workflow
|
||||||
|
|
||||||
|
1. **HTTP Request**: Receive document upload request
|
||||||
|
2. **ZapSign**: Create document with uploaded file
|
||||||
|
3. **ZapSign**: Add signers to the document
|
||||||
|
4. **ZapSign**: Send document for signature
|
||||||
|
5. **ZapSign Trigger**: Wait for document completion
|
||||||
|
6. **Email**: Send notification when document is signed
|
||||||
|
|
||||||
|
### Template-Based Document Creation
|
||||||
|
|
||||||
|
1. **Schedule Trigger**: Run daily
|
||||||
|
2. **Google Sheets**: Get contract data
|
||||||
|
3. **ZapSign**: Create document from template
|
||||||
|
4. **ZapSign**: Add signers from spreadsheet
|
||||||
|
5. **ZapSign**: Send for signature
|
||||||
|
6. **Slack**: Notify team
|
||||||
|
|
||||||
|
### Webhook Event Processing
|
||||||
|
|
||||||
|
1. **ZapSign Trigger**: Listen for document events
|
||||||
|
2. **Switch**: Route based on event type
|
||||||
|
3. **Database**: Update document status
|
||||||
|
4. **Email**: Send appropriate notifications
|
||||||
|
|
||||||
|
## Authentication Methods
|
||||||
|
|
||||||
|
ZapSign supports multiple authentication methods for signers:
|
||||||
|
|
||||||
|
- **Email**: Simple email verification
|
||||||
|
- **SMS**: SMS code verification
|
||||||
|
- **WhatsApp**: WhatsApp code verification
|
||||||
|
|
||||||
|
Additional security features:
|
||||||
|
- **Document Authentication**: Require ID document upload
|
||||||
|
- **Facial Recognition**: Verify identity through facial recognition
|
||||||
|
- **Biometric GOV+**: Government database validation (Brazil)
|
||||||
|
|
||||||
|
## API Endpoints Structure
|
||||||
|
|
||||||
|
The node assumes the following ZapSign API structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
Base URL: https://api.zapsign.com (Production)
|
||||||
|
https://sandbox.api.zapsign.com (Sandbox)
|
||||||
|
|
||||||
|
Documents:
|
||||||
|
- POST /v1/documents - Create document
|
||||||
|
- GET /v1/documents - List documents
|
||||||
|
- GET /v1/documents/{id} - Get document
|
||||||
|
- POST /v1/documents/{id}/send - Send document
|
||||||
|
- POST /v1/documents/{id}/cancel - Cancel document
|
||||||
|
- GET /v1/documents/{id}/download - Download document
|
||||||
|
|
||||||
|
Signers:
|
||||||
|
- POST /v1/documents/{id}/signers - Add signer
|
||||||
|
- GET /v1/documents/{id}/signers - List signers
|
||||||
|
- PUT /v1/documents/{id}/signers/{email} - Update signer
|
||||||
|
- DELETE /v1/documents/{id}/signers/{email} - Remove signer
|
||||||
|
|
||||||
|
Templates:
|
||||||
|
- GET /v1/templates - List templates
|
||||||
|
- POST /v1/documents/from-template - Create from template
|
||||||
|
|
||||||
|
Webhooks:
|
||||||
|
- POST /v1/webhooks - Create webhook
|
||||||
|
- GET /v1/webhooks - List webhooks
|
||||||
|
- DELETE /v1/webhooks/{id} - Delete webhook
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The node includes comprehensive error handling:
|
||||||
|
|
||||||
|
- **Authentication errors**: Invalid API key or expired tokens
|
||||||
|
- **Rate limiting**: Automatic retry with exponential backoff
|
||||||
|
- **Validation errors**: Missing required fields or invalid data
|
||||||
|
- **Network errors**: Connection timeouts and retries
|
||||||
|
|
||||||
|
All errors are properly formatted and include helpful context for debugging.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
- n8n v0.187.0 and above
|
||||||
|
- Node.js v18.10 and above
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
|
||||||
|
- [ZapSign API Documentation](https://docs.zapsign.com.br/)
|
||||||
|
- [ZapSign Website](https://zapsign.com)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For support with this community node:
|
||||||
|
|
||||||
|
1. Check the [ZapSign API documentation](https://docs.zapsign.com.br/)
|
||||||
|
2. Review the [n8n community forum](https://community.n8n.io/)
|
||||||
|
3. Open an issue on this repository
|
||||||
|
|
||||||
|
For ZapSign-specific questions, contact ZapSign support at support@zapsign.com
|
||||||
|
|
||||||
## License
|
## 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" : "https://api.zapsign.com"}}',
|
||||||
|
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)];
|
||||||
|
}
|
||||||
|
}
|
||||||
26
nodes/ZapSign/zapsign.svg
Normal file
26
nodes/ZapSign/zapsign.svg
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#4F46E5;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#7C3AED;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Document background -->
|
||||||
|
<rect x="12" y="8" width="40" height="48" rx="4" ry="4" fill="#ffffff" stroke="url(#grad)" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- Document lines -->
|
||||||
|
<line x1="20" y1="20" x2="44" y2="20" stroke="#E5E7EB" stroke-width="1"/>
|
||||||
|
<line x1="20" y1="26" x2="44" y2="26" stroke="#E5E7EB" stroke-width="1"/>
|
||||||
|
<line x1="20" y1="32" x2="38" y2="32" stroke="#E5E7EB" stroke-width="1"/>
|
||||||
|
|
||||||
|
<!-- Signature line -->
|
||||||
|
<line x1="20" y1="42" x2="44" y2="42" stroke="#D1D5DB" stroke-width="1" stroke-dasharray="2,2"/>
|
||||||
|
|
||||||
|
<!-- Signature/pen icon -->
|
||||||
|
<path d="M26 38 L32 44 L42 34 L40 32 Z" fill="url(#grad)"/>
|
||||||
|
<circle cx="43" cy="33" r="2" fill="url(#grad)"/>
|
||||||
|
|
||||||
|
<!-- Lightning bolt for "zap" -->
|
||||||
|
<path d="M48 16 L46 20 L48 20 L46 24 L50 20 L48 20 Z" fill="#F59E0B"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
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'
|
||||||
|
: 'https://api.zapsign.com';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.helpers.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${baseUrl}/v1/webhooks`,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${credentials.apiKey}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if webhook with this URL already exists
|
||||||
|
const webhooks = Array.isArray(response) ? response : response.data || [];
|
||||||
|
for (const webhook of webhooks) {
|
||||||
|
if (webhook.url === webhookUrl) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async create(this: IHookFunctions): Promise<boolean> {
|
||||||
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||||
|
const events = this.getNodeParameter('events') as string[];
|
||||||
|
|
||||||
|
const credentials = await this.getCredentials('zapSignApi');
|
||||||
|
const baseUrl = credentials.environment === 'sandbox'
|
||||||
|
? 'https://sandbox.api.zapsign.com'
|
||||||
|
: 'https://api.zapsign.com';
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
url: webhookUrl,
|
||||||
|
events,
|
||||||
|
name: `n8n-webhook-${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const responseData = await this.helpers.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${baseUrl}/v1/webhooks`,
|
||||||
|
body,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${credentials.apiKey}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (responseData.id === undefined) {
|
||||||
|
// We did not get back the ID which we need to delete the webhook
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookData.webhookId = responseData.id as string;
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async delete(this: IHookFunctions): Promise<boolean> {
|
||||||
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
|
|
||||||
|
if (webhookData.webhookId !== undefined) {
|
||||||
|
const credentials = await this.getCredentials('zapSignApi');
|
||||||
|
const baseUrl = credentials.environment === 'sandbox'
|
||||||
|
? 'https://sandbox.api.zapsign.com'
|
||||||
|
: 'https://api.zapsign.com';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.helpers.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `${baseUrl}/v1/webhooks/${webhookData.webhookId}`,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${credentials.apiKey}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from the static workflow data so that it is clear
|
||||||
|
// that no webhooks are registered anymore
|
||||||
|
delete webhookData.webhookId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||||
|
const bodyData = this.getBodyData();
|
||||||
|
const events = this.getNodeParameter('events') as string[];
|
||||||
|
const documentFilter = this.getNodeParameter('documentFilter') as IDataObject;
|
||||||
|
|
||||||
|
// Validate that this is a ZapSign webhook
|
||||||
|
if (!bodyData.event || !bodyData.data) {
|
||||||
|
return {
|
||||||
|
webhookResponse: {
|
||||||
|
status: 400,
|
||||||
|
body: 'Invalid webhook payload',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventType = bodyData.event as string;
|
||||||
|
|
||||||
|
// Check if we should handle this event
|
||||||
|
if (!events.includes(eventType)) {
|
||||||
|
return {
|
||||||
|
webhookResponse: {
|
||||||
|
status: 200,
|
||||||
|
body: 'Event type not configured',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply filters if configured
|
||||||
|
const data = bodyData.data as IDataObject;
|
||||||
|
if (documentFilter.documentId && (data.document as IDataObject)?.id !== documentFilter.documentId) {
|
||||||
|
return {
|
||||||
|
webhookResponse: {
|
||||||
|
status: 200,
|
||||||
|
body: 'Document ID filter not matched',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (documentFilter.signerEmail && (data.signer as IDataObject)?.email !== documentFilter.signerEmail) {
|
||||||
|
return {
|
||||||
|
webhookResponse: {
|
||||||
|
status: 200,
|
||||||
|
body: 'Signer email filter not matched',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (documentFilter.brandId && (data.document as IDataObject)?.brand_id !== documentFilter.brandId) {
|
||||||
|
return {
|
||||||
|
webhookResponse: {
|
||||||
|
status: 200,
|
||||||
|
body: 'Brand ID filter not matched',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich the payload with additional metadata
|
||||||
|
const enrichedData = {
|
||||||
|
...bodyData,
|
||||||
|
metadata: {
|
||||||
|
event_type: eventType,
|
||||||
|
received_at: new Date().toISOString(),
|
||||||
|
webhook_source: 'zapsign',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
workflowData: [this.helpers.returnJsonArray([enrichedData])],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
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
22
package.json
22
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.com"
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
|
@ -52,4 +52,4 @@
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"n8n-workflow": "*"
|
"n8n-workflow": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue