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 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,7 @@
1
+ import type { ICredentialType, INodeProperties } from "n8n-workflow";
2
+ export declare class HuntSalesApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ }
@@ -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;
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2
+ <rect width="64" height="64" rx="14" fill="#1f5fe0"/>
3
+ <path d="M20 16h6v13h12V16h6v32h-6V35H26v13h-6z" fill="#fff"/>
4
+ </svg>
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // n8n loads nodes and credentials from the compiled files listed in the "n8n"
2
+ // field of package.json (dist/**). This entry point is intentionally empty.
3
+ module.exports = {};
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
+ }