n8n-nodes-huntsales 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/credentials/HuntSalesApi.credentials.d.ts +7 -0
- package/dist/credentials/HuntSalesApi.credentials.js +32 -0
- package/dist/nodes/HuntSales/HuntSales.node.d.ts +5 -0
- package/dist/nodes/HuntSales/HuntSales.node.js +141 -0
- package/dist/nodes/HuntSales/HuntSalesTrigger.node.d.ts +12 -0
- package/dist/nodes/HuntSales/HuntSalesTrigger.node.js +107 -0
- package/dist/nodes/HuntSales/huntsales.svg +4 -0
- package/index.js +3 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# n8n-nodes-huntsales
|
|
2
|
+
|
|
3
|
+
An [n8n](https://n8n.io) community node for [HuntSales](https://huntsales.io) CRM.
|
|
4
|
+
|
|
5
|
+
- **HuntSales** (action node): query and create contacts, claim prospects, and read credits over the HuntSales MCP API.
|
|
6
|
+
- **HuntSales Trigger**: start a workflow when a HuntSales CRM event happens (reply received, deal won, watchlist match, and more). It registers its own signed webhook endpoint in HuntSales, so there is nothing to wire up by hand.
|
|
7
|
+
|
|
8
|
+
## Credentials
|
|
9
|
+
|
|
10
|
+
Create an API key in HuntSales under **Admin, then Developer**. The action node needs read/write scopes for the operations you use. The **Trigger** node also needs the **`webhooks:manage`** scope so it can register and remove its own endpoint.
|
|
11
|
+
|
|
12
|
+
Enter the key (and your base URL, default `https://huntsales.io`) in the **HuntSales API** credential.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
In n8n: **Settings, then Community Nodes, then Install**, and enter `n8n-nodes-huntsales`.
|
|
17
|
+
|
|
18
|
+
## Build and publish (maintainers)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd packages/n8n-nodes-huntsales
|
|
22
|
+
npm install
|
|
23
|
+
npm run build # tsc -> dist/
|
|
24
|
+
npm publish --access public
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`npm publish` is a manual step performed by a maintainer with npm access. This repository ships the source only; it is not published automatically.
|
|
28
|
+
|
|
29
|
+
## Webhook signatures
|
|
30
|
+
|
|
31
|
+
Every delivered event carries `X-HuntSales-Signature: sha256=<hmac(secret, body)>`. The signing secret is returned once when the endpoint is created (the Trigger stores it in workflow static data). Verify it in a downstream step if you want to authenticate deliveries.
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HuntSalesApi = void 0;
|
|
4
|
+
// HuntSales API key credential. Create a key in HuntSales under
|
|
5
|
+
// Admin, then Developer. For the trigger node, the key needs the
|
|
6
|
+
// "webhooks:manage" scope so n8n can register its own webhook endpoint.
|
|
7
|
+
class HuntSalesApi {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.name = "huntSalesApi";
|
|
10
|
+
this.displayName = "HuntSales API";
|
|
11
|
+
this.documentationUrl = "https://huntsales.io/docs/n8n";
|
|
12
|
+
this.properties = [
|
|
13
|
+
{
|
|
14
|
+
displayName: "API Key",
|
|
15
|
+
name: "apiKey",
|
|
16
|
+
type: "string",
|
|
17
|
+
typeOptions: { password: true },
|
|
18
|
+
default: "",
|
|
19
|
+
required: true,
|
|
20
|
+
description: "An hs_live_... key from HuntSales, Admin, Developer.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
displayName: "Base URL",
|
|
24
|
+
name: "baseUrl",
|
|
25
|
+
type: "string",
|
|
26
|
+
default: "https://huntsales.io",
|
|
27
|
+
description: "Your HuntSales base URL (change only for a self-hosted region).",
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.HuntSalesApi = HuntSalesApi;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from "n8n-workflow";
|
|
2
|
+
export declare class HuntSales implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HuntSales = void 0;
|
|
4
|
+
// Call a HuntSales MCP tool over JSON-RPC 2.0.
|
|
5
|
+
async function callTool(ctx, toolName, args) {
|
|
6
|
+
const creds = await ctx.getCredentials("huntSalesApi");
|
|
7
|
+
const baseUrl = String(creds.baseUrl || "https://huntsales.io").replace(/\/$/, "");
|
|
8
|
+
const body = {
|
|
9
|
+
jsonrpc: "2.0",
|
|
10
|
+
id: 1,
|
|
11
|
+
method: "tools/call",
|
|
12
|
+
params: { name: toolName, arguments: args },
|
|
13
|
+
};
|
|
14
|
+
const res = (await ctx.helpers.httpRequest({
|
|
15
|
+
method: "POST",
|
|
16
|
+
url: `${baseUrl}/api/mcp`,
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
Authorization: `Bearer ${creds.apiKey}`,
|
|
20
|
+
},
|
|
21
|
+
body,
|
|
22
|
+
json: true,
|
|
23
|
+
}));
|
|
24
|
+
if (res.error) {
|
|
25
|
+
throw new Error(res.error.message || "HuntSales MCP error");
|
|
26
|
+
}
|
|
27
|
+
return res.result;
|
|
28
|
+
}
|
|
29
|
+
class HuntSales {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.description = {
|
|
32
|
+
displayName: "HuntSales",
|
|
33
|
+
name: "huntSales",
|
|
34
|
+
icon: "file:huntsales.svg",
|
|
35
|
+
group: ["transform"],
|
|
36
|
+
version: 1,
|
|
37
|
+
subtitle: '={{$parameter["operation"]}}',
|
|
38
|
+
description: "Query and create contacts, claim prospects, and read credits",
|
|
39
|
+
defaults: { name: "HuntSales" },
|
|
40
|
+
inputs: ["main"],
|
|
41
|
+
outputs: ["main"],
|
|
42
|
+
credentials: [{ name: "huntSalesApi", required: true }],
|
|
43
|
+
properties: [
|
|
44
|
+
{
|
|
45
|
+
displayName: "Operation",
|
|
46
|
+
name: "operation",
|
|
47
|
+
type: "options",
|
|
48
|
+
noDataExpression: true,
|
|
49
|
+
options: [
|
|
50
|
+
{ name: "Query Contacts", value: "queryContacts" },
|
|
51
|
+
{ name: "Create Contact", value: "createContact" },
|
|
52
|
+
{ name: "Claim Prospects", value: "claimProspects" },
|
|
53
|
+
{ name: "Get Credits", value: "getCredits" },
|
|
54
|
+
],
|
|
55
|
+
default: "queryContacts",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
displayName: "Search",
|
|
59
|
+
name: "search",
|
|
60
|
+
type: "string",
|
|
61
|
+
default: "",
|
|
62
|
+
displayOptions: { show: { operation: ["queryContacts"] } },
|
|
63
|
+
description: "Name, email or company to search for",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
displayName: "Contact Name",
|
|
67
|
+
name: "contactName",
|
|
68
|
+
type: "string",
|
|
69
|
+
default: "",
|
|
70
|
+
displayOptions: { show: { operation: ["createContact"] } },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
displayName: "Email",
|
|
74
|
+
name: "email",
|
|
75
|
+
type: "string",
|
|
76
|
+
default: "",
|
|
77
|
+
displayOptions: { show: { operation: ["createContact"] } },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
displayName: "Confirm (actually run)",
|
|
81
|
+
name: "confirm",
|
|
82
|
+
type: "boolean",
|
|
83
|
+
default: false,
|
|
84
|
+
displayOptions: { show: { operation: ["claimProspects"] } },
|
|
85
|
+
description: "Off returns a dry-run preview; on actually claims and spends quota",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
displayName: "Job Titles (comma separated)",
|
|
89
|
+
name: "jobTitles",
|
|
90
|
+
type: "string",
|
|
91
|
+
default: "",
|
|
92
|
+
displayOptions: { show: { operation: ["claimProspects"] } },
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
displayName: "Limit",
|
|
96
|
+
name: "limit",
|
|
97
|
+
type: "number",
|
|
98
|
+
default: 25,
|
|
99
|
+
displayOptions: { show: { operation: ["claimProspects"] } },
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async execute() {
|
|
105
|
+
var _a;
|
|
106
|
+
const items = this.getInputData();
|
|
107
|
+
const out = [];
|
|
108
|
+
for (let i = 0; i < items.length; i++) {
|
|
109
|
+
const operation = this.getNodeParameter("operation", i);
|
|
110
|
+
let result;
|
|
111
|
+
if (operation === "queryContacts") {
|
|
112
|
+
result = await callTool(this, "query_contacts", {
|
|
113
|
+
search: this.getNodeParameter("search", i),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
else if (operation === "createContact") {
|
|
117
|
+
result = await callTool(this, "create_contact", {
|
|
118
|
+
contact_name: this.getNodeParameter("contactName", i),
|
|
119
|
+
email: this.getNodeParameter("email", i),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else if (operation === "claimProspects") {
|
|
123
|
+
const titles = this.getNodeParameter("jobTitles", i)
|
|
124
|
+
.split(",")
|
|
125
|
+
.map((s) => s.trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
result = await callTool(this, "claim_prospects", {
|
|
128
|
+
job_titles: titles,
|
|
129
|
+
limit: this.getNodeParameter("limit", i),
|
|
130
|
+
confirm: this.getNodeParameter("confirm", i),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else if (operation === "getCredits") {
|
|
134
|
+
result = await callTool(this, "get_credits", {});
|
|
135
|
+
}
|
|
136
|
+
out.push({ json: (_a = result) !== null && _a !== void 0 ? _a : {} });
|
|
137
|
+
}
|
|
138
|
+
return [out];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.HuntSales = HuntSales;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from "n8n-workflow";
|
|
2
|
+
export declare class HuntSalesTrigger implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
webhookMethods: {
|
|
5
|
+
default: {
|
|
6
|
+
checkExists(this: IHookFunctions): Promise<boolean>;
|
|
7
|
+
create(this: IHookFunctions): Promise<boolean>;
|
|
8
|
+
delete(this: IHookFunctions): Promise<boolean>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HuntSalesTrigger = void 0;
|
|
4
|
+
// HuntSales Trigger: registers an outbound webhook endpoint in HuntSales (via an
|
|
5
|
+
// API key with the webhooks:manage scope) and fires the workflow when the chosen
|
|
6
|
+
// events arrive. Signature verification of X-HuntSales-Signature can be added by
|
|
7
|
+
// the user with the endpoint's signing secret.
|
|
8
|
+
class HuntSalesTrigger {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.description = {
|
|
11
|
+
displayName: "HuntSales Trigger",
|
|
12
|
+
name: "huntSalesTrigger",
|
|
13
|
+
icon: "file:huntsales.svg",
|
|
14
|
+
group: ["trigger"],
|
|
15
|
+
version: 1,
|
|
16
|
+
description: "Starts the workflow on a HuntSales CRM event",
|
|
17
|
+
defaults: { name: "HuntSales Trigger" },
|
|
18
|
+
inputs: [],
|
|
19
|
+
outputs: ["main"],
|
|
20
|
+
credentials: [{ name: "huntSalesApi", required: true }],
|
|
21
|
+
webhooks: [
|
|
22
|
+
{
|
|
23
|
+
name: "default",
|
|
24
|
+
httpMethod: "POST",
|
|
25
|
+
responseMode: "onReceived",
|
|
26
|
+
path: "webhook",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
properties: [
|
|
30
|
+
{
|
|
31
|
+
displayName: "Events",
|
|
32
|
+
name: "events",
|
|
33
|
+
type: "multiOptions",
|
|
34
|
+
required: true,
|
|
35
|
+
default: ["reply.received"],
|
|
36
|
+
options: [
|
|
37
|
+
{ name: "Reply received", value: "reply.received" },
|
|
38
|
+
{ name: "Contact created", value: "contact.created" },
|
|
39
|
+
{ name: "Stage changed", value: "contact.stage_changed" },
|
|
40
|
+
{ name: "Contact assigned", value: "contact.assigned" },
|
|
41
|
+
{ name: "Campaign launched", value: "campaign.launched" },
|
|
42
|
+
{ name: "Campaign completed", value: "campaign.completed" },
|
|
43
|
+
{ name: "Email opened", value: "email.opened" },
|
|
44
|
+
{ name: "Email clicked", value: "email.clicked" },
|
|
45
|
+
{ name: "Email bounced", value: "email.bounced" },
|
|
46
|
+
{ name: "Contact unsubscribed", value: "contact.unsubscribed" },
|
|
47
|
+
{ name: "Call completed", value: "call.completed" },
|
|
48
|
+
{ name: "Meeting scored", value: "meeting.scored" },
|
|
49
|
+
{ name: "Deal created", value: "deal.created" },
|
|
50
|
+
{ name: "Deal won", value: "deal.won" },
|
|
51
|
+
{ name: "Watchlist match", value: "watchlist.match" },
|
|
52
|
+
{ name: "Credits low", value: "credits.low" },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
this.webhookMethods = {
|
|
58
|
+
default: {
|
|
59
|
+
async checkExists() {
|
|
60
|
+
const data = this.getWorkflowStaticData("node");
|
|
61
|
+
return Boolean(data.endpointId);
|
|
62
|
+
},
|
|
63
|
+
async create() {
|
|
64
|
+
var _a;
|
|
65
|
+
const creds = await this.getCredentials("huntSalesApi");
|
|
66
|
+
const baseUrl = String(creds.baseUrl || "https://huntsales.io").replace(/\/$/, "");
|
|
67
|
+
const webhookUrl = this.getNodeWebhookUrl("default");
|
|
68
|
+
const events = this.getNodeParameter("events");
|
|
69
|
+
const res = (await this.helpers.httpRequest({
|
|
70
|
+
method: "POST",
|
|
71
|
+
url: `${baseUrl}/api/webhook-endpoints`,
|
|
72
|
+
headers: {
|
|
73
|
+
"Content-Type": "application/json",
|
|
74
|
+
Authorization: `Bearer ${creds.apiKey}`,
|
|
75
|
+
},
|
|
76
|
+
body: { url: webhookUrl, events },
|
|
77
|
+
json: true,
|
|
78
|
+
}));
|
|
79
|
+
const data = this.getWorkflowStaticData("node");
|
|
80
|
+
data.endpointId = (_a = res.endpoint) === null || _a === void 0 ? void 0 : _a.id;
|
|
81
|
+
data.secret = res.secret;
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
async delete() {
|
|
85
|
+
const data = this.getWorkflowStaticData("node");
|
|
86
|
+
if (!data.endpointId)
|
|
87
|
+
return true;
|
|
88
|
+
const creds = await this.getCredentials("huntSalesApi");
|
|
89
|
+
const baseUrl = String(creds.baseUrl || "https://huntsales.io").replace(/\/$/, "");
|
|
90
|
+
await this.helpers.httpRequest({
|
|
91
|
+
method: "DELETE",
|
|
92
|
+
url: `${baseUrl}/api/webhook-endpoints?id=${data.endpointId}`,
|
|
93
|
+
headers: { Authorization: `Bearer ${creds.apiKey}` },
|
|
94
|
+
});
|
|
95
|
+
delete data.endpointId;
|
|
96
|
+
delete data.secret;
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async webhook() {
|
|
103
|
+
const body = this.getBodyData();
|
|
104
|
+
return { workflowData: [this.helpers.returnJsonArray([body])] };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.HuntSalesTrigger = HuntSalesTrigger;
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-huntsales",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "n8n community node for the HuntSales CRM: query and create contacts, claim prospects, run campaigns, and trigger on CRM events.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"huntsales",
|
|
8
|
+
"crm"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"homepage": "https://huntsales.io",
|
|
12
|
+
"main": "index.js",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"lint": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"n8n": {
|
|
22
|
+
"n8nNodesApiVersion": 1,
|
|
23
|
+
"credentials": [
|
|
24
|
+
"dist/credentials/HuntSalesApi.credentials.js"
|
|
25
|
+
],
|
|
26
|
+
"nodes": [
|
|
27
|
+
"dist/nodes/HuntSales/HuntSales.node.js",
|
|
28
|
+
"dist/nodes/HuntSales/HuntSalesTrigger.node.js"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"n8n-workflow": "*"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"n8n-workflow": "^1.60.0",
|
|
36
|
+
"typescript": "^5.5.0"
|
|
37
|
+
}
|
|
38
|
+
}
|