n8n-nodes-warecover-v1 2.8.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,65 @@
1
+ # Warecover — WhatsApp Node for n8n
2
+
3
+ Send WhatsApp messages from your n8n workflows using your Warecover account.
4
+
5
+ ---
6
+
7
+ ## Install
8
+
9
+ 1. Open n8n → **Settings → Community Nodes**
10
+ 2. Click **Install**
11
+ 3. Enter: `n8n-nodes-warecover-v2`
12
+ 4. Restart n8n
13
+
14
+ ---
15
+
16
+ ## Connect Your Account
17
+
18
+ 1. Go to **Credentials → New → Warecover API**
19
+ 2. Enter your:
20
+ - **Access Token** — from Warecover → Channel → API Details
21
+ - **Phone Number ID** — from same page
22
+ 3. Save
23
+
24
+ > Your Access Token is shown **only once** in Warecover. Save it immediately!
25
+
26
+ ---
27
+
28
+ ## Available Actions
29
+
30
+ | Action | Notes |
31
+ |---|---|
32
+ | Send Text Message | ⚠️ 24-hour session required |
33
+ | Send Template Message | ✅ Works anytime |
34
+ | Send Image | ⚠️ 24-hour session required |
35
+ | Send Document | ⚠️ 24-hour session required |
36
+ | Send Audio | ⚠️ 24-hour session required |
37
+ | Send Video | ⚠️ 24-hour session required |
38
+ | Send Location | ⚠️ 24-hour session required |
39
+ | Send Buttons | ⚠️ 24-hour session required |
40
+ | Send List Menu | ⚠️ 24-hour session required |
41
+ | Mark as Read | — |
42
+
43
+ ---
44
+
45
+ ## 24-Hour Session Rule
46
+
47
+ WhatsApp only allows sending messages **if the recipient messaged you in the last 24 hours** — except for Template Messages which work anytime.
48
+
49
+ **Tip:** Start with a Template Message. Once they reply, you can send any message type.
50
+
51
+ ---
52
+
53
+ ## Phone Number Format
54
+
55
+ Use international format without `+`:
56
+
57
+ | Country | Example |
58
+ |---|---|
59
+ | India | `919209576337` |
60
+ | USA | `14155552671` |
61
+ | UAE | `971501234567` |
62
+
63
+ ---
64
+
65
+ Visit **warecover.com** for more info.
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WarecoverApi = void 0;
4
+
5
+ class WarecoverApi {
6
+ constructor() {
7
+ this.name = 'warecoverApi';
8
+ this.displayName = 'Warecover';
9
+ this.documentationUrl = 'https://webhook.warecover.com/docs';
10
+ this.properties = [
11
+ // ── Welcome notice ──────────────────────────────────
12
+ {
13
+ displayName: 'New to Warecover? Create your account at webhook.warecover.com',
14
+ name: 'warecoverNotice',
15
+ type: 'notice',
16
+ default: '',
17
+ },
18
+
19
+ // ── API Key ─────────────────────────────────────────
20
+ {
21
+ displayName: 'API Key',
22
+ name: 'apiKey',
23
+ type: 'string',
24
+ typeOptions: { password: true },
25
+ default: '',
26
+ required: true,
27
+ placeholder: 'wc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
28
+ description: 'Your Warecover API Key. Go to <a href="https://webhook.warecover.com/api-keys" target="_blank">webhook.warecover.com → API Keys</a> → click <strong>Generate Key</strong>. The key is shown only once — copy it immediately.',
29
+ },
30
+
31
+ // ── Access Token ─────────────────────────────────────
32
+ {
33
+ displayName: 'Access Token',
34
+ name: 'accessToken',
35
+ type: 'string',
36
+ typeOptions: { password: true },
37
+ default: '',
38
+ required: true,
39
+ placeholder: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
40
+ description: 'Your WhatsApp channel access token. Go to <a href="https://webhook.warecover.com" target="_blank">Warecover Dashboard</a> → Channel → API Details → copy <strong>Access Token</strong>.',
41
+ },
42
+
43
+ // ── Phone Number ID ──────────────────────────────────
44
+ {
45
+ displayName: 'Phone Number ID',
46
+ name: 'phoneNumberId',
47
+ type: 'string',
48
+ default: '',
49
+ required: true,
50
+ placeholder: '935752596280211',
51
+ description: 'Your WhatsApp Phone Number ID. Go to <a href="https://webhook.warecover.com" target="_blank">Warecover Dashboard</a> → Channel → API Details → copy <strong>Phone Number ID</strong>.',
52
+ },
53
+ ];
54
+
55
+ this.authenticate = {
56
+ type: 'generic',
57
+ properties: {
58
+ headers: {
59
+ Authorization: '=Bearer {{$credentials.accessToken}}',
60
+ 'X-Warecover-Key': '={{$credentials.apiKey}}',
61
+ },
62
+ },
63
+ };
64
+
65
+ this.test = {
66
+ request: {
67
+ baseURL: 'https://crm.warecover.com/api/meta/v19.0',
68
+ url: '/{{$credentials.phoneNumberId}}',
69
+ method: 'GET',
70
+ },
71
+ };
72
+ }
73
+ }
74
+ exports.WarecoverApi = WarecoverApi;
@@ -0,0 +1,762 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Warecover = void 0;
4
+
5
+ class Warecover {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: 'Warecover',
9
+ name: 'warecover',
10
+ icon: 'file:warecover.svg',
11
+ group: ['output'],
12
+ version: 1,
13
+ subtitle: '={{$parameter["operation"]}}',
14
+ description: 'Send WhatsApp messages via Warecover',
15
+ defaults: { name: 'Warecover' },
16
+ inputs: ['main'],
17
+ outputs: ['main'],
18
+ credentials: [{ name: 'warecoverApi', required: true }],
19
+ properties: [
20
+ {
21
+ displayName: 'Operation',
22
+ name: 'operation',
23
+ type: 'options',
24
+ noDataExpression: true,
25
+ options: [
26
+ // ─────────────────────────────────────────
27
+ // n8n renders section headers when:
28
+ // action is omitted OR empty string
29
+ // name is the header text
30
+ // value starts with _ (makes it non-selectable)
31
+ // ─────────────────────────────────────────
32
+
33
+ // MESSAGING
34
+ {
35
+ name: 'Messaging',
36
+ value: '_sep_messaging',
37
+ action: '',
38
+ description: '',
39
+ },
40
+ {
41
+ name: 'Send Text Message',
42
+ value: 'sendText',
43
+ action: 'Send a text message',
44
+ description: 'Send a plain text message. Requires active 24-hour session.',
45
+ },
46
+ {
47
+ name: 'Send Template Message',
48
+ value: 'sendTemplate',
49
+ action: 'Send a pre-approved template message',
50
+ description: 'Works outside 24-hour window. No active session needed.',
51
+ },
52
+ {
53
+ name: 'Send Image',
54
+ value: 'sendImage',
55
+ action: 'Send an image',
56
+ description: 'Send a JPG, PNG or WebP image with optional caption.',
57
+ },
58
+ {
59
+ name: 'Send Document / PDF',
60
+ value: 'sendDocument',
61
+ action: 'Send a file or PDF',
62
+ description: 'Send any document — PDF, DOCX, XLSX, etc.',
63
+ },
64
+ {
65
+ name: 'Send Audio',
66
+ value: 'sendAudio',
67
+ action: 'Send an audio file',
68
+ description: 'Send an MP3 or OGG audio message.',
69
+ },
70
+ {
71
+ name: 'Send Video',
72
+ value: 'sendVideo',
73
+ action: 'Send a video',
74
+ description: 'Send an MP4 video with optional caption.',
75
+ },
76
+ {
77
+ name: 'Send Reply Buttons',
78
+ value: 'sendButtons',
79
+ action: 'Send up to 3 interactive reply buttons',
80
+ description: 'Customer taps a button — use in Switch node to route the flow.',
81
+ },
82
+ {
83
+ name: 'Send CTA URL Button',
84
+ value: 'sendCtaButton',
85
+ action: 'Send a message with a URL button',
86
+ description: 'Button opens a URL — ideal for payment links, forms, or websites.',
87
+ },
88
+ {
89
+ name: 'Send List Menu',
90
+ value: 'sendList',
91
+ action: 'Send a scrollable list of options',
92
+ description: 'Scrollable menu — up to 10 options. Good for product menus or service selection.',
93
+ },
94
+ {
95
+ name: 'Mark Message as Read',
96
+ value: 'markRead',
97
+ action: 'Mark an incoming message as read',
98
+ description: 'Sends a read receipt — customer sees double blue ticks.',
99
+ },
100
+
101
+ // ORDER HANDLING
102
+ {
103
+ name: 'Order Handling',
104
+ value: '_sep_order',
105
+ action: '',
106
+ description: '',
107
+ },
108
+ {
109
+ name: 'Read Incoming Order',
110
+ value: 'parseOrder',
111
+ action: 'Extract products, totals and customer from a catalog order',
112
+ description: 'Parses a WhatsApp catalog order webhook — outputs product list, totals, and customer info.',
113
+ },
114
+ {
115
+ name: 'Send Order Confirmation',
116
+ value: 'sendOrderConfirmation',
117
+ action: 'Send an order confirmation message to the customer',
118
+ description: 'Send a customizable confirmation message after receiving an order.',
119
+ },
120
+
121
+ // ADDRESS COLLECTION
122
+ {
123
+ name: 'Address Collection',
124
+ value: '_sep_address',
125
+ action: '',
126
+ description: '',
127
+ },
128
+ {
129
+ name: 'Ask for Address (Native Form)',
130
+ value: 'requestAddressForm',
131
+ action: 'Send a native WhatsApp address form',
132
+ description: 'Customer sees a "Provide address" button — fills structured form. India & Singapore only.',
133
+ },
134
+ {
135
+ name: 'Read Address Form Reply',
136
+ value: 'parseAddressForm',
137
+ action: 'Extract address from a native form submission',
138
+ description: 'Parses name, phone, address, city, state, pincode from the form webhook.',
139
+ },
140
+ {
141
+ name: 'Ask for Address (Text)',
142
+ value: 'requestAddressText',
143
+ action: 'Ask customer to type their address',
144
+ description: 'Send a message asking the customer to type their address manually.',
145
+ },
146
+ {
147
+ name: 'Read Address from Chat Reply',
148
+ value: 'parseAddressText',
149
+ action: 'Extract address from a plain text reply',
150
+ description: 'Reads the address the customer typed in chat.',
151
+ },
152
+
153
+ // LOCATION
154
+ {
155
+ name: 'Location',
156
+ value: '_sep_location',
157
+ action: '',
158
+ description: '',
159
+ },
160
+ {
161
+ name: 'Ask for Live Location',
162
+ value: 'requestLocation',
163
+ action: 'Ask customer to share their live location',
164
+ description: 'Sends a "Share Location" prompt — customer taps to share GPS coordinates.',
165
+ },
166
+ {
167
+ name: 'Send Location Pin',
168
+ value: 'sendLocation',
169
+ action: 'Send a map location pin to the customer',
170
+ description: 'Send latitude/longitude — customer sees it as a map pin in WhatsApp.',
171
+ },
172
+ {
173
+ name: 'Read Incoming Location',
174
+ value: 'parseLocation',
175
+ action: 'Extract location coordinates from a location message',
176
+ description: 'Parses latitude, longitude, and address from a location webhook.',
177
+ },
178
+
179
+ // CHAT MANAGEMENT
180
+ {
181
+ name: 'Chat Management',
182
+ value: '_sep_chat',
183
+ action: '',
184
+ description: '',
185
+ },
186
+ {
187
+ name: 'Close Chat (Mark as Done)',
188
+ value: 'closeChatDone',
189
+ action: 'Mark the chat as done to allow the next flow to trigger',
190
+ description: 'Use after completing an order, booking, or after no-response timeout.',
191
+ },
192
+
193
+ // DETECT & ROUTE
194
+ {
195
+ name: 'Detect & Route',
196
+ value: '_sep_route',
197
+ action: '',
198
+ description: '',
199
+ },
200
+ {
201
+ name: 'Detect Button or List Click',
202
+ value: 'detectButton',
203
+ action: 'Detect which button or list item the customer clicked',
204
+ description: 'Returns button_id — connect to a Switch node to route the flow based on customer choice.',
205
+ },
206
+ {
207
+ name: 'Read Incoming Message',
208
+ value: 'parseContact',
209
+ action: 'Extract phone, name and message type from any incoming webhook',
210
+ description: 'Outputs customer_phone, customer_name, message_type, and routing flags (is_order, is_button_reply, is_address_form, etc.).',
211
+ },
212
+ ],
213
+ default: 'sendText',
214
+ },
215
+
216
+ // Section selected notice
217
+ {
218
+ displayName: 'Please select an operation from the list above.',
219
+ name: 'sepNotice',
220
+ type: 'notice',
221
+ default: '',
222
+ displayOptions: {
223
+ show: {
224
+ operation: [
225
+ '_sep_messaging','_sep_order','_sep_address',
226
+ '_sep_location','_sep_chat','_sep_route'
227
+ ]
228
+ }
229
+ },
230
+ },
231
+
232
+ // 24-HOUR NOTICE
233
+ {
234
+ displayName: '⚠️ 24-Hour Session Required',
235
+ name: 'sessionWindowNotice',
236
+ type: 'notice',
237
+ default: 'This message can only be sent within 24 hours after the recipient last messaged you. Use "Send Template Message" to reach users outside this window.',
238
+ displayOptions: { show: { operation: ['sendText','sendImage','sendDocument','sendAudio','sendVideo','sendButtons','sendCtaButton','sendList','requestLocation','requestAddressForm','requestAddressText'] } },
239
+ },
240
+
241
+ // CLOSE CHAT
242
+ {
243
+ displayName: 'Customer Phone',
244
+ name: 'closeChatPhone',
245
+ type: 'string',
246
+ required: true,
247
+ default: '',
248
+ placeholder: '={{$json.customer_phone}}',
249
+ description: 'Phone number of the customer whose chat should be marked as done',
250
+ displayOptions: { show: { operation: ['closeChatDone'] } },
251
+ },
252
+
253
+ // TO
254
+ {
255
+ displayName: 'To (Phone Number)',
256
+ name: 'to',
257
+ type: 'string',
258
+ required: true,
259
+ default: '',
260
+ placeholder: '919209576337',
261
+ description: 'Phone in international format without + sign. India example: 919209576337',
262
+ displayOptions: { show: { operation: ['sendText','sendTemplate','sendImage','sendDocument','sendAudio','sendVideo','sendButtons','sendCtaButton','sendList','requestLocation','sendLocation','requestAddressForm','requestAddressText','sendOrderConfirmation'] } },
263
+ },
264
+
265
+ // SEND TEXT
266
+ { displayName: 'Message', name: 'text', type: 'string', typeOptions: { rows: 5 }, required: true, default: '', description: 'Supports WhatsApp formatting: *bold* _italic_ ~strikethrough~', displayOptions: { show: { operation: ['sendText'] } } },
267
+ { displayName: 'Show URL Preview', name: 'previewUrl', type: 'boolean', default: false, displayOptions: { show: { operation: ['sendText'] } } },
268
+
269
+ // SEND TEMPLATE
270
+ { displayName: 'Template Name', name: 'templateName', type: 'string', required: true, default: '', placeholder: 'order_confirmation', description: 'Exact name of the approved template in Meta Business Manager', displayOptions: { show: { operation: ['sendTemplate'] } } },
271
+ {
272
+ displayName: 'Language',
273
+ name: 'languageCode',
274
+ type: 'options',
275
+ options: [
276
+ { name: 'English (en)', value: 'en' },
277
+ { name: 'English US (en_US)', value: 'en_US' },
278
+ { name: 'Hindi (hi)', value: 'hi' },
279
+ { name: 'Marathi (mr)', value: 'mr' },
280
+ { name: 'Gujarati (gu)', value: 'gu' },
281
+ { name: 'Tamil (ta)', value: 'ta' },
282
+ { name: 'Telugu (te)', value: 'te' },
283
+ { name: 'Kannada (kn)', value: 'kn' },
284
+ ],
285
+ default: 'en',
286
+ displayOptions: { show: { operation: ['sendTemplate'] } },
287
+ },
288
+ { displayName: 'Template Variables (JSON)', name: 'templateComponents', type: 'json', default: '[]', description: 'Leave [] for templates with no variables.', displayOptions: { show: { operation: ['sendTemplate'] } } },
289
+
290
+ // SEND IMAGE
291
+ { displayName: 'Image URL', name: 'imageUrl', type: 'string', required: true, default: '', placeholder: 'https://example.com/photo.jpg', displayOptions: { show: { operation: ['sendImage'] } } },
292
+ { displayName: 'Caption', name: 'imageCaption', type: 'string', default: '', displayOptions: { show: { operation: ['sendImage'] } } },
293
+
294
+ // SEND DOCUMENT
295
+ { displayName: 'Document URL', name: 'documentUrl', type: 'string', required: true, default: '', placeholder: 'https://example.com/file.pdf', displayOptions: { show: { operation: ['sendDocument'] } } },
296
+ { displayName: 'File Name (shown to user)', name: 'documentFilename', type: 'string', default: 'document.pdf', displayOptions: { show: { operation: ['sendDocument'] } } },
297
+ { displayName: 'Caption', name: 'documentCaption', type: 'string', default: '', displayOptions: { show: { operation: ['sendDocument'] } } },
298
+
299
+ // SEND AUDIO
300
+ { displayName: 'Audio URL', name: 'audioUrl', type: 'string', required: true, default: '', placeholder: 'https://example.com/audio.mp3', displayOptions: { show: { operation: ['sendAudio'] } } },
301
+
302
+ // SEND VIDEO
303
+ { displayName: 'Video URL', name: 'videoUrl', type: 'string', required: true, default: '', placeholder: 'https://example.com/video.mp4', displayOptions: { show: { operation: ['sendVideo'] } } },
304
+ { displayName: 'Caption', name: 'videoCaption', type: 'string', default: '', displayOptions: { show: { operation: ['sendVideo'] } } },
305
+
306
+ // SEND BUTTONS
307
+ { displayName: 'Message Body', name: 'btnBodyText', type: 'string', typeOptions: { rows: 4 }, required: true, default: '', description: 'Main message text shown above the buttons', displayOptions: { show: { operation: ['sendButtons'] } } },
308
+ { displayName: 'Header Text', name: 'btnHeaderText', type: 'string', default: '', displayOptions: { show: { operation: ['sendButtons'] } } },
309
+ { displayName: 'Footer Text', name: 'btnFooterText', type: 'string', default: '', displayOptions: { show: { operation: ['sendButtons'] } } },
310
+ {
311
+ displayName: 'Buttons',
312
+ name: 'buttons',
313
+ type: 'fixedCollection',
314
+ typeOptions: { multipleValues: true, maxValue: 3 },
315
+ default: { button: [{ id: 'btn_1', title: 'Option 1' }] },
316
+ description: 'Max 3 buttons. Button ID is returned when user clicks — use in Switch node to route.',
317
+ options: [{
318
+ name: 'button', displayName: 'Button',
319
+ values: [
320
+ { displayName: 'Button ID', name: 'id', type: 'string', default: '', placeholder: 'btn_cod', description: 'Unique ID returned in webhook when user clicks' },
321
+ { displayName: 'Button Label', name: 'title', type: 'string', default: '', placeholder: 'Cash on Delivery', description: 'Max 20 characters' },
322
+ ],
323
+ }],
324
+ displayOptions: { show: { operation: ['sendButtons'] } },
325
+ },
326
+
327
+ // SEND CTA URL BUTTON
328
+ { displayName: 'Header Text', name: 'ctaHeaderText', type: 'string', default: '', description: 'Bold title above the message body', displayOptions: { show: { operation: ['sendCtaButton'] } } },
329
+ { displayName: 'Message Body', name: 'ctaBodyText', type: 'string', typeOptions: { rows: 5 }, required: true, default: '', description: 'Main message content', displayOptions: { show: { operation: ['sendCtaButton'] } } },
330
+ { displayName: 'Footer Text', name: 'ctaFooterText', type: 'string', default: '', displayOptions: { show: { operation: ['sendCtaButton'] } } },
331
+ { displayName: 'Button Label', name: 'ctaDisplayText', type: 'string', required: true, default: '', placeholder: 'Pay Now', description: 'Text shown on the URL button (max 20 chars)', displayOptions: { show: { operation: ['sendCtaButton'] } } },
332
+ { displayName: 'Button URL', name: 'ctaUrl', type: 'string', required: true, default: '', placeholder: 'https://rzp.io/l/abc123', description: 'URL that opens when user taps the button', displayOptions: { show: { operation: ['sendCtaButton'] } } },
333
+
334
+ // SEND LIST
335
+ { displayName: 'Message Body', name: 'listBodyText', type: 'string', typeOptions: { rows: 4 }, required: true, default: '', displayOptions: { show: { operation: ['sendList'] } } },
336
+ { displayName: 'List Button Label', name: 'listButtonLabel', type: 'string', required: true, default: 'Select Option', displayOptions: { show: { operation: ['sendList'] } } },
337
+ { displayName: 'Header Text', name: 'listHeaderText', type: 'string', default: '', displayOptions: { show: { operation: ['sendList'] } } },
338
+ { displayName: 'Footer Text', name: 'listFooterText', type: 'string', default: '', displayOptions: { show: { operation: ['sendList'] } } },
339
+ { displayName: 'List Sections (JSON)', name: 'listSections', type: 'json', required: true, default: '[{"title":"Options","rows":[{"id":"opt_1","title":"Option 1","description":"Description here"}]}]', displayOptions: { show: { operation: ['sendList'] } } },
340
+
341
+ // MARK AS READ
342
+ { displayName: 'Message ID', name: 'messageId', type: 'string', required: true, default: '', placeholder: 'wamid.HBgM...', displayOptions: { show: { operation: ['markRead'] } } },
343
+
344
+ // READ INCOMING ORDER
345
+ { displayName: 'Webhook Body', name: 'webhookBody', type: 'json', required: true, default: '={{$json.body}}', description: 'Full webhook body. Outputs: customer_phone, customer_name, products[], total_items, total_amount, order_summary.', displayOptions: { show: { operation: ['parseOrder'] } } },
346
+
347
+ // SEND ORDER CONFIRMATION
348
+ { displayName: 'Customer Phone', name: 'confirmTo', type: 'string', required: true, default: '', placeholder: '={{$json.customer_phone}}', displayOptions: { show: { operation: ['sendOrderConfirmation'] } } },
349
+ { displayName: 'Confirmation Message', name: 'orderConfirmText', type: 'string', typeOptions: { rows: 6 }, required: true, default: 'Order received. Our team will confirm it shortly.', displayOptions: { show: { operation: ['sendOrderConfirmation'] } } },
350
+
351
+ // ASK FOR ADDRESS — NATIVE FORM
352
+ {
353
+ displayName: '✅ India & Singapore Only',
354
+ name: 'addressFormNotice',
355
+ type: 'notice',
356
+ default: 'Sends WhatsApp native address form — customer sees a "Provide address" button. Only works for Indian & Singapore phone numbers.',
357
+ displayOptions: { show: { operation: ['requestAddressForm'] } },
358
+ },
359
+ { displayName: 'Message Body', name: 'addressFormBody', type: 'string', typeOptions: { rows: 3 }, required: true, default: 'Please provide your delivery address to complete your order.', displayOptions: { show: { operation: ['requestAddressForm'] } } },
360
+ {
361
+ displayName: 'Country',
362
+ name: 'addressCountry',
363
+ type: 'options',
364
+ options: [
365
+ { name: 'India (IN)', value: 'IN' },
366
+ { name: 'Singapore (SG)', value: 'SG' },
367
+ ],
368
+ default: 'IN',
369
+ required: true,
370
+ displayOptions: { show: { operation: ['requestAddressForm'] } },
371
+ },
372
+ { displayName: 'Pre-fill Customer Name', name: 'prefillName', type: 'string', default: '', placeholder: '={{$json.customer_name}}', displayOptions: { show: { operation: ['requestAddressForm'] } } },
373
+ { displayName: 'Pre-fill Phone Number', name: 'prefillPhone', type: 'string', default: '', placeholder: '+917558510593', displayOptions: { show: { operation: ['requestAddressForm'] } } },
374
+
375
+ // READ ADDRESS FORM SUBMISSION
376
+ { displayName: 'Webhook Body', name: 'addressFormWebhookBody', type: 'json', required: true, default: '={{$json.body}}', description: 'Outputs: name, phone, address, city, state, pincode, floor, landmark, full_address.', displayOptions: { show: { operation: ['parseAddressForm'] } } },
377
+
378
+ // ASK FOR ADDRESS — TEXT
379
+ { displayName: 'Message Body', name: 'addressTextBody', type: 'string', typeOptions: { rows: 5 }, required: true, default: 'Please type your complete delivery address including House No, Street, City and Pincode.', displayOptions: { show: { operation: ['requestAddressText'] } } },
380
+
381
+ // READ ADDRESS FROM CHAT REPLY
382
+ { displayName: 'Webhook Body', name: 'addressTextWebhookBody', type: 'json', required: true, default: '={{$json.body}}', displayOptions: { show: { operation: ['parseAddressText'] } } },
383
+
384
+ // ASK FOR LOCATION
385
+ { displayName: 'Message Body', name: 'locationRequestBody', type: 'string', typeOptions: { rows: 3 }, required: true, default: 'Please share your live location so we can deliver to you.', displayOptions: { show: { operation: ['requestLocation'] } } },
386
+
387
+ // SEND LOCATION PIN
388
+ { displayName: 'Latitude', name: 'latitude', type: 'number', required: true, default: 0, displayOptions: { show: { operation: ['sendLocation'] } } },
389
+ { displayName: 'Longitude', name: 'longitude', type: 'number', required: true, default: 0, displayOptions: { show: { operation: ['sendLocation'] } } },
390
+ { displayName: 'Location Name', name: 'locationName', type: 'string', default: '', displayOptions: { show: { operation: ['sendLocation'] } } },
391
+ { displayName: 'Address', name: 'locationAddress', type: 'string', default: '', displayOptions: { show: { operation: ['sendLocation'] } } },
392
+
393
+ // READ INCOMING LOCATION
394
+ { displayName: 'Webhook Body', name: 'locationWebhookBody', type: 'json', required: true, default: '={{$json.body}}', displayOptions: { show: { operation: ['parseLocation'] } } },
395
+
396
+ // DETECT BUTTON / LIST CLICK
397
+ { displayName: 'Webhook Body', name: 'buttonWebhookBody', type: 'json', required: true, default: '={{$json.body}}', description: 'Outputs: button_id, button_text, customer_phone. Use button_id in Switch node to route.', displayOptions: { show: { operation: ['detectButton'] } } },
398
+
399
+ // READ INCOMING MESSAGE INFO
400
+ { displayName: 'Webhook Body', name: 'contactWebhookBody', type: 'json', required: true, default: '={{$json.body}}', description: 'Outputs: customer_phone, customer_name, message_type, is_order, is_button_reply, is_address_form, is_location, is_text.', displayOptions: { show: { operation: ['parseContact'] } } },
401
+ ],
402
+ };
403
+ }
404
+
405
+ async execute() {
406
+ const items = this.getInputData();
407
+ const returnData = [];
408
+ const credentials = await this.getCredentials('warecoverApi');
409
+ const accessToken = credentials.accessToken;
410
+ const phoneNumberId = credentials.phoneNumberId;
411
+ const apiKey = credentials.apiKey;
412
+ const baseURL = `https://crm.warecover.com/api/meta/v19.0`;
413
+
414
+ // ── API Key Validation ──────────────────────────────────────────
415
+ // Validate key against Warecover before any API call
416
+ try {
417
+ const keyCheck = await this.helpers.httpRequest({
418
+ method: 'POST',
419
+ url: 'https://webhook.warecover.com/api/n8n/verify-key',
420
+ headers: { 'Content-Type': 'application/json' },
421
+ body: { api_key: apiKey, phone_number_id: phoneNumberId },
422
+ json: true,
423
+ });
424
+ if (!keyCheck || keyCheck.valid === false) {
425
+ throw new Error('Invalid Warecover API Key. Please go to Warecover Dashboard → API Keys and generate a valid key.');
426
+ }
427
+ } catch (keyError) {
428
+ // If endpoint not yet deployed, check key format at minimum
429
+ if (!apiKey || String(apiKey).length < 10) {
430
+ throw new Error('Warecover API Key is missing or invalid. Go to Warecover Dashboard → API Keys → Generate Key.');
431
+ }
432
+ // If server returned explicit invalid, re-throw
433
+ if (keyError.message && keyError.message.includes('Invalid Warecover')) {
434
+ throw keyError;
435
+ }
436
+ // Network/server error — allow through (key format passed)
437
+ }
438
+ // ───────────────────────────────────────────────────────────────
439
+
440
+ const sendWA = async (body) => {
441
+ return await this.helpers.httpRequest({
442
+ method: 'POST',
443
+ url: `${baseURL}/${phoneNumberId}/messages`,
444
+ headers: { Authorization: `Bearer ${accessToken}`, 'X-Warecover-Key': String(apiKey), 'Content-Type': 'application/json' },
445
+ body, json: true,
446
+ });
447
+ };
448
+
449
+ const parseBody = (raw) => {
450
+ try { return typeof raw === 'string' ? JSON.parse(raw) : (raw || {}); } catch (_) { return {}; }
451
+ };
452
+
453
+ const extractWebhook = (body) => {
454
+ const entry = body.entry?.[0];
455
+ const changes = entry?.changes?.[0];
456
+ const value = changes?.value;
457
+ return {
458
+ value,
459
+ messages: value?.messages?.[0],
460
+ contacts: value?.contacts?.[0],
461
+ metadata: value?.metadata,
462
+ statuses: value?.statuses?.[0],
463
+ };
464
+ };
465
+
466
+ for (let i = 0; i < items.length; i++) {
467
+ try {
468
+ const operation = this.getNodeParameter('operation', i);
469
+ let result = {};
470
+
471
+ if (operation.startsWith('_sep')) {
472
+ returnData.push({ json: { info: 'Select a real operation.' }, pairedItem: { item: i } });
473
+ continue;
474
+ }
475
+
476
+ if (operation === 'sendText') {
477
+ const to = this.getNodeParameter('to', i);
478
+ const text = this.getNodeParameter('text', i);
479
+ const previewUrl = this.getNodeParameter('previewUrl', i);
480
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'text', text: { body: text, preview_url: previewUrl } });
481
+
482
+ } else if (operation === 'sendTemplate') {
483
+ const to = this.getNodeParameter('to', i);
484
+ const templateName = this.getNodeParameter('templateName', i);
485
+ const languageCode = this.getNodeParameter('languageCode', i);
486
+ const componentsRaw = this.getNodeParameter('templateComponents', i);
487
+ let components = [];
488
+ try { components = typeof componentsRaw === 'string' ? JSON.parse(componentsRaw) : componentsRaw; } catch (_) { components = []; }
489
+ result = await sendWA({ messaging_product: 'whatsapp', to, type: 'template', template: { name: templateName, language: { code: languageCode }, ...(components.length > 0 ? { components } : {}) } });
490
+
491
+ } else if (operation === 'sendImage') {
492
+ const to = this.getNodeParameter('to', i);
493
+ const link = this.getNodeParameter('imageUrl', i);
494
+ const caption = this.getNodeParameter('imageCaption', i);
495
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'image', image: { link, ...(caption ? { caption } : {}) } });
496
+
497
+ } else if (operation === 'sendDocument') {
498
+ const to = this.getNodeParameter('to', i);
499
+ const link = this.getNodeParameter('documentUrl', i);
500
+ const filename = this.getNodeParameter('documentFilename', i);
501
+ const caption = this.getNodeParameter('documentCaption', i);
502
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'document', document: { link, filename, ...(caption ? { caption } : {}) } });
503
+
504
+ } else if (operation === 'sendAudio') {
505
+ const to = this.getNodeParameter('to', i);
506
+ const link = this.getNodeParameter('audioUrl', i);
507
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'audio', audio: { link } });
508
+
509
+ } else if (operation === 'sendVideo') {
510
+ const to = this.getNodeParameter('to', i);
511
+ const link = this.getNodeParameter('videoUrl', i);
512
+ const caption = this.getNodeParameter('videoCaption', i);
513
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'video', video: { link, ...(caption ? { caption } : {}) } });
514
+
515
+ } else if (operation === 'sendButtons') {
516
+ const to = this.getNodeParameter('to', i);
517
+ const bodyText = this.getNodeParameter('btnBodyText', i);
518
+ const headerText = this.getNodeParameter('btnHeaderText', i);
519
+ const footerText = this.getNodeParameter('btnFooterText', i);
520
+ const buttonsData = this.getNodeParameter('buttons', i);
521
+ const buttons = (buttonsData.button || []).map((btn) => ({ type: 'reply', reply: { id: btn.id, title: btn.title } }));
522
+ result = await sendWA({
523
+ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'interactive',
524
+ interactive: {
525
+ type: 'button',
526
+ ...(headerText ? { header: { type: 'text', text: headerText } } : {}),
527
+ body: { text: bodyText },
528
+ ...(footerText ? { footer: { text: footerText } } : {}),
529
+ action: { buttons },
530
+ },
531
+ });
532
+ result.buttons_sent = buttons.map(b => b.reply);
533
+
534
+ } else if (operation === 'sendCtaButton') {
535
+ const to = this.getNodeParameter('to', i);
536
+ const headerText = this.getNodeParameter('ctaHeaderText', i);
537
+ const bodyText = this.getNodeParameter('ctaBodyText', i);
538
+ const footerText = this.getNodeParameter('ctaFooterText', i);
539
+ const displayText = this.getNodeParameter('ctaDisplayText', i);
540
+ const url = this.getNodeParameter('ctaUrl', i);
541
+ result = await sendWA({
542
+ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'interactive',
543
+ interactive: {
544
+ type: 'cta_url',
545
+ ...(headerText ? { header: { type: 'text', text: headerText } } : {}),
546
+ body: { text: bodyText },
547
+ ...(footerText ? { footer: { text: footerText } } : {}),
548
+ action: { name: 'cta_url', parameters: { display_text: displayText, url } },
549
+ },
550
+ });
551
+
552
+ } else if (operation === 'sendList') {
553
+ const to = this.getNodeParameter('to', i);
554
+ const bodyText = this.getNodeParameter('listBodyText', i);
555
+ const buttonLabel = this.getNodeParameter('listButtonLabel', i);
556
+ const headerText = this.getNodeParameter('listHeaderText', i);
557
+ const footerText = this.getNodeParameter('listFooterText', i);
558
+ const sectionsRaw = this.getNodeParameter('listSections', i);
559
+ let sections = [];
560
+ try { sections = typeof sectionsRaw === 'string' ? JSON.parse(sectionsRaw) : sectionsRaw; } catch (_) { sections = []; }
561
+ result = await sendWA({
562
+ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'interactive',
563
+ interactive: {
564
+ type: 'list',
565
+ ...(headerText ? { header: { type: 'text', text: headerText } } : {}),
566
+ body: { text: bodyText },
567
+ ...(footerText ? { footer: { text: footerText } } : {}),
568
+ action: { button: buttonLabel, sections },
569
+ },
570
+ });
571
+
572
+ } else if (operation === 'markRead') {
573
+ const messageId = this.getNodeParameter('messageId', i);
574
+ result = await sendWA({ messaging_product: 'whatsapp', status: 'read', message_id: messageId });
575
+
576
+ } else if (operation === 'parseOrder') {
577
+ const body = parseBody(this.getNodeParameter('webhookBody', i));
578
+ const { messages, contacts, metadata } = extractWebhook(body);
579
+ if (!messages || messages.type !== 'order') {
580
+ result = { is_order: false, message_type: messages?.type || 'unknown' };
581
+ } else {
582
+ const order = messages.order;
583
+ const productItems = order.product_items || [];
584
+ let totalAmount = 0; let totalItems = 0;
585
+ const products = productItems.map((item) => {
586
+ const qty = Number(item.quantity) || 0;
587
+ const price = Number(item.item_price) || 0;
588
+ totalAmount += qty * price; totalItems += qty;
589
+ return { product_id: item.product_retailer_id, quantity: qty, price, currency: item.currency || 'INR', subtotal: qty * price };
590
+ });
591
+ result = {
592
+ is_order: true,
593
+ customer_phone: contacts?.wa_id || messages.from,
594
+ customer_name: contacts?.profile?.name || '',
595
+ message_id: messages.id,
596
+ timestamp: messages.timestamp,
597
+ catalog_id: order.catalog_id,
598
+ order_text: order.text || '',
599
+ products,
600
+ total_items: totalItems,
601
+ total_amount: totalAmount,
602
+ currency: productItems?.[0]?.currency || 'INR',
603
+ phone_number_id: metadata?.phone_number_id || '',
604
+ display_phone_number: metadata?.display_phone_number || '',
605
+ order_summary: `Order Summary\n--------------------------\n${products.map((p, i) => `${i+1}. Item: ${p.product_id}\n Qty: ${p.quantity} Price: Rs. ${p.price} Subtotal: Rs. ${p.subtotal}`).join('\n\n')}\n\n--------------------------\nTotal Items : ${totalItems}\nTotal Amount: Rs. ${totalAmount}`,
606
+ };
607
+ }
608
+
609
+ } else if (operation === 'sendOrderConfirmation') {
610
+ const to = this.getNodeParameter('to', i);
611
+ const confirmText = this.getNodeParameter('orderConfirmText', i);
612
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'text', text: { body: confirmText } });
613
+
614
+ } else if (operation === 'requestAddressForm') {
615
+ const to = this.getNodeParameter('to', i);
616
+ const bodyText = this.getNodeParameter('addressFormBody', i);
617
+ const country = this.getNodeParameter('addressCountry', i);
618
+ const prefillName = this.getNodeParameter('prefillName', i);
619
+ const prefillPhone = this.getNodeParameter('prefillPhone', i);
620
+ const params = { country };
621
+ if (prefillName || prefillPhone) {
622
+ params.values = {};
623
+ if (prefillName) params.values.name = prefillName;
624
+ if (prefillPhone) params.values.phone_number = prefillPhone;
625
+ }
626
+ result = await sendWA({
627
+ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'interactive',
628
+ interactive: { type: 'address_message', body: { text: bodyText }, action: { name: 'address_message', parameters: JSON.stringify(params) } },
629
+ });
630
+
631
+ } else if (operation === 'parseAddressForm') {
632
+ const body = parseBody(this.getNodeParameter('addressFormWebhookBody', i));
633
+ const { messages, contacts } = extractWebhook(body);
634
+ if (messages?.type === 'interactive' && messages?.interactive?.type === 'nfm_reply' && messages?.interactive?.nfm_reply?.name === 'address_message') {
635
+ const nfmReply = messages.interactive.nfm_reply;
636
+ let addressValues = {};
637
+ try { const parsed = JSON.parse(nfmReply.response_json || '{}'); addressValues = parsed.values || parsed; } catch (_) { addressValues = {}; }
638
+ result = {
639
+ is_address_form: true,
640
+ customer_phone: messages.from,
641
+ customer_name: addressValues.name || contacts?.profile?.name || '',
642
+ message_id: messages.id,
643
+ timestamp: messages.timestamp,
644
+ name: addressValues.name || '',
645
+ phone: addressValues.phone_number || '',
646
+ address: addressValues.address || '',
647
+ city: addressValues.city || '',
648
+ state: addressValues.state || '',
649
+ pincode: addressValues.in_pin_code || '',
650
+ floor: addressValues.floor_number || '',
651
+ landmark: addressValues.landmark_area || '',
652
+ building: addressValues.building_name || '',
653
+ sg_post_code: addressValues.sg_post_code || '',
654
+ full_address: nfmReply.body || [addressValues.name, addressValues.phone_number, addressValues.address, addressValues.floor_number, addressValues.landmark_area, addressValues.city, addressValues.state, addressValues.in_pin_code].filter(Boolean).join(', '),
655
+ raw_values: addressValues,
656
+ };
657
+ } else {
658
+ result = { is_address_form: false, message_type: messages?.type || 'unknown', customer_phone: messages?.from || '' };
659
+ }
660
+
661
+ } else if (operation === 'requestAddressText') {
662
+ const to = this.getNodeParameter('to', i);
663
+ const bodyText = this.getNodeParameter('addressTextBody', i);
664
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'text', text: { body: bodyText } });
665
+
666
+ } else if (operation === 'parseAddressText') {
667
+ const body = parseBody(this.getNodeParameter('addressTextWebhookBody', i));
668
+ const { messages, contacts } = extractWebhook(body);
669
+ if (messages?.type === 'text') {
670
+ const textBody = messages.text?.body || '';
671
+ result = { is_address: true, address_text: textBody, address_lines: textBody.split('\n').map(l => l.trim()).filter(Boolean), customer_phone: messages.from, customer_name: contacts?.profile?.name || '', message_id: messages.id, timestamp: messages.timestamp };
672
+ } else {
673
+ result = { is_address: false, message_type: messages?.type || 'unknown', customer_phone: messages?.from || '' };
674
+ }
675
+
676
+ } else if (operation === 'requestLocation') {
677
+ const to = this.getNodeParameter('to', i);
678
+ const bodyText = this.getNodeParameter('locationRequestBody', i);
679
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'interactive', interactive: { type: 'location_request_message', body: { text: bodyText }, action: { name: 'send_location' } } });
680
+
681
+ } else if (operation === 'sendLocation') {
682
+ const to = this.getNodeParameter('to', i);
683
+ const latitude = this.getNodeParameter('latitude', i);
684
+ const longitude = this.getNodeParameter('longitude', i);
685
+ const name = this.getNodeParameter('locationName', i);
686
+ const address = this.getNodeParameter('locationAddress', i);
687
+ result = await sendWA({ messaging_product: 'whatsapp', recipient_type: 'individual', to, type: 'location', location: { latitude, longitude, name, address } });
688
+
689
+ } else if (operation === 'parseLocation') {
690
+ const body = parseBody(this.getNodeParameter('locationWebhookBody', i));
691
+ const { messages, contacts } = extractWebhook(body);
692
+ if (messages?.type === 'location') {
693
+ const loc = messages.location;
694
+ result = { is_location: true, customer_phone: messages.from, customer_name: contacts?.profile?.name || '', message_id: messages.id, timestamp: messages.timestamp, latitude: loc.latitude, longitude: loc.longitude, name: loc.name || '', address: loc.address || '', google_maps_url: `https://www.google.com/maps?q=${loc.latitude},${loc.longitude}` };
695
+ } else {
696
+ result = { is_location: false, message_type: messages?.type || 'unknown', customer_phone: messages?.from || '' };
697
+ }
698
+
699
+ } else if (operation === 'detectButton') {
700
+ const body = parseBody(this.getNodeParameter('buttonWebhookBody', i));
701
+ const { messages, contacts } = extractWebhook(body);
702
+ if (messages?.type === 'interactive') {
703
+ const interactive = messages.interactive;
704
+ const btnReply = interactive.button_reply;
705
+ const listReply = interactive.list_reply;
706
+ result = { is_button_click: true, interaction_type: interactive.type, button_id: btnReply?.id || listReply?.id || '', button_text: btnReply?.title || listReply?.title || '', list_description: listReply?.description || '', customer_phone: messages.from, customer_name: contacts?.profile?.name || '', message_id: messages.id, timestamp: messages.timestamp };
707
+ } else if (messages?.type === 'text') {
708
+ result = { is_button_click: false, is_text_reply: true, button_id: '', text: messages.text?.body || '', customer_phone: messages.from, customer_name: contacts?.profile?.name || '', message_id: messages.id, timestamp: messages.timestamp };
709
+ } else {
710
+ result = { is_button_click: false, is_text_reply: false, button_id: '', message_type: messages?.type || 'unknown', customer_phone: messages?.from || '' };
711
+ }
712
+
713
+ } else if (operation === 'closeChatDone') {
714
+ const phone = this.getNodeParameter('closeChatPhone', i);
715
+ // Warecover API: mark chat as done
716
+ result = await this.helpers.httpRequest({
717
+ method: 'POST',
718
+ url: `https://crm.warecover.com/api/chat/done`,
719
+ headers: { Authorization: `Bearer ${accessToken}`, 'X-Warecover-Key': String(apiKey), 'Content-Type': 'application/json' },
720
+ body: { phone_number_id: phoneNumberId, customer_phone: phone },
721
+ json: true,
722
+ });
723
+ result.chat_closed = true;
724
+ result.customer_phone = phone;
725
+
726
+ } else if (operation === 'parseContact') {
727
+ const body = parseBody(this.getNodeParameter('contactWebhookBody', i));
728
+ const { messages, contacts, metadata, statuses } = extractWebhook(body);
729
+ const isAddressForm = messages?.type === 'interactive' && messages?.interactive?.type === 'nfm_reply' && messages?.interactive?.nfm_reply?.name === 'address_message';
730
+ result = {
731
+ customer_phone: messages?.from || statuses?.recipient_id || '',
732
+ customer_name: contacts?.profile?.name || '',
733
+ customer_wa_id: contacts?.wa_id || '',
734
+ message_id: messages?.id || '',
735
+ message_type: messages?.type || (statuses ? 'status_update' : 'unknown'),
736
+ timestamp: messages?.timestamp || statuses?.timestamp || '',
737
+ text_body: messages?.text?.body || '',
738
+ phone_number_id: metadata?.phone_number_id || '',
739
+ display_phone_number: metadata?.display_phone_number || '',
740
+ is_status_update: !!statuses,
741
+ status: statuses?.status || '',
742
+ is_order: messages?.type === 'order',
743
+ is_button_reply: messages?.type === 'interactive' && !isAddressForm,
744
+ is_address_form: isAddressForm,
745
+ is_location: messages?.type === 'location',
746
+ is_text: messages?.type === 'text',
747
+ };
748
+ }
749
+
750
+ returnData.push({ json: result, pairedItem: { item: i } });
751
+ } catch (error) {
752
+ if (this.continueOnFail()) {
753
+ returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
754
+ continue;
755
+ }
756
+ throw error;
757
+ }
758
+ }
759
+ return [returnData];
760
+ }
761
+ }
762
+ exports.Warecover = Warecover;
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
2
+ <rect width="60" height="60" rx="12" fill="#25D366"/>
3
+ <circle cx="30" cy="28" r="14" fill="#ffffff"/>
4
+ <circle cx="30" cy="28" r="11" fill="#25D366"/>
5
+ <rect x="18" y="38" width="8" height="8" rx="4" fill="#25D366"/>
6
+ <rect x="22" y="22" width="3" height="12" rx="1.5" fill="#ffffff"/>
7
+ <rect x="28" y="22" width="3" height="12" rx="1.5" fill="#ffffff"/>
8
+ <rect x="34" y="22" width="3" height="12" rx="1.5" fill="#ffffff"/>
9
+ </svg>
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "n8n-nodes-warecover-v1",
3
+ "version": "2.8.0",
4
+ "description": "WhatsApp automation for n8n \u2014 send messages, buttons, CTA links, orders, address forms, location via Warecover",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "warecover",
8
+ "whatsapp",
9
+ "meta",
10
+ "whatsapp-api",
11
+ "whatsapp-business",
12
+ "whatsapp-automation",
13
+ "whatsapp-bot",
14
+ "whatsapp-messaging",
15
+ "whatsapp-crm",
16
+ "messaging",
17
+ "sms",
18
+ "notification",
19
+ "automation",
20
+ "ai-agent",
21
+ "chatbot",
22
+ "crm",
23
+ "customer-support",
24
+ "business-messaging",
25
+ "meta-api",
26
+ "cloud-api",
27
+ "send-message",
28
+ "template-message",
29
+ "webhook",
30
+ "workflow",
31
+ "n8n-node",
32
+ "n8n-integration",
33
+ "india",
34
+ "saas"
35
+ ],
36
+ "license": "MIT",
37
+ "author": {
38
+ "name": "Warecover"
39
+ },
40
+ "main": "index.js",
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "dev": "tsc --watch"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "n8n": {
49
+ "n8nNodesApiVersion": 1,
50
+ "credentials": [
51
+ "dist/credentials/WarecoverApi.credentials.js"
52
+ ],
53
+ "nodes": [
54
+ "dist/nodes/Warecover/Warecover.node.js"
55
+ ]
56
+ },
57
+ "devDependencies": {
58
+ "typescript": "~5.1.0",
59
+ "n8n-workflow": "*"
60
+ },
61
+ "peerDependencies": {
62
+ "n8n-workflow": "*"
63
+ }
64
+ }