@swalha1999/whatsapp 0.1.3
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 +638 -0
- package/dist/index-9aM5qSPE.d.mts +22 -0
- package/dist/index-9aM5qSPE.d.mts.map +1 -0
- package/dist/index-CTZd2C9y.d.ts +22 -0
- package/dist/index-CTZd2C9y.d.ts.map +1 -0
- package/dist/index-DuBGO2yg.d.ts +117 -0
- package/dist/index-DuBGO2yg.d.ts.map +1 -0
- package/dist/index-vhzQr4xs.d.mts +117 -0
- package/dist/index-vhzQr4xs.d.mts.map +1 -0
- package/dist/index.d.mts +51 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +265 -0
- package/dist/index.mjs +259 -0
- package/dist/index.mjs.map +1 -0
- package/dist/templates-gL73pawi.mjs +70 -0
- package/dist/templates-gL73pawi.mjs.map +1 -0
- package/dist/templates-v50WAWdN.js +76 -0
- package/dist/templates.d.mts +3 -0
- package/dist/templates.d.ts +3 -0
- package/dist/templates.js +3 -0
- package/dist/templates.mjs +3 -0
- package/dist/types-D8Ot82od.d.ts +281 -0
- package/dist/types-D8Ot82od.d.ts.map +1 -0
- package/dist/types-DwL3dbmb.d.mts +281 -0
- package/dist/types-DwL3dbmb.d.mts.map +1 -0
- package/dist/webhook-BDH8vDt4.js +206 -0
- package/dist/webhook-Bkqz-Kn9.mjs +166 -0
- package/dist/webhook-Bkqz-Kn9.mjs.map +1 -0
- package/dist/webhook.d.mts +3 -0
- package/dist/webhook.d.ts +3 -0
- package/dist/webhook.js +5 -0
- package/dist/webhook.mjs +3 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# @swalha1999/whatsapp
|
|
2
|
+
|
|
3
|
+
A lightweight, type-safe WhatsApp Business API client for Node.js.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Send text messages and template messages
|
|
8
|
+
- Send media: images, videos, audio, documents, stickers
|
|
9
|
+
- Send interactive messages: buttons, lists
|
|
10
|
+
- Send location, reactions, and contact cards
|
|
11
|
+
- Message categories: marketing, utility, authentication, service
|
|
12
|
+
- Parse incoming webhook payloads (text, media, location, reactions, stickers, contacts)
|
|
13
|
+
- Verify webhook subscriptions
|
|
14
|
+
- Fluent template builder API
|
|
15
|
+
- Full TypeScript support
|
|
16
|
+
- Zero dependencies (uses native fetch)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Using pnpm
|
|
22
|
+
pnpm add @swalha1999/whatsapp
|
|
23
|
+
|
|
24
|
+
# Using npm
|
|
25
|
+
npm install @swalha1999/whatsapp
|
|
26
|
+
|
|
27
|
+
# Using yarn
|
|
28
|
+
yarn add @swalha1999/whatsapp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { createWhatsApp } from '@swalha1999/whatsapp'
|
|
35
|
+
|
|
36
|
+
const whatsapp = createWhatsApp({
|
|
37
|
+
apiToken: process.env.WHATSAPP_TOKEN!,
|
|
38
|
+
phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Send a message
|
|
42
|
+
const result = await whatsapp.sendText({
|
|
43
|
+
to: '1234567890',
|
|
44
|
+
body: 'Hello!',
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
console.log(result.success ? 'Sent!' : result.error?.message)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API Reference
|
|
51
|
+
|
|
52
|
+
### `createWhatsApp(config)`
|
|
53
|
+
|
|
54
|
+
Creates a WhatsApp client instance.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { createWhatsApp } from '@swalha1999/whatsapp'
|
|
58
|
+
|
|
59
|
+
const whatsapp = createWhatsApp({
|
|
60
|
+
apiToken: 'your_token',
|
|
61
|
+
phoneNumberId: 'your_phone_id',
|
|
62
|
+
apiVersion: 'v22.0', // optional, default: 'v22.0'
|
|
63
|
+
baseUrl: 'https://...', // optional, default: 'https://graph.facebook.com'
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `whatsapp.sendText(params)`
|
|
68
|
+
|
|
69
|
+
Send a text message.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const result = await whatsapp.sendText({
|
|
73
|
+
to: '1234567890', // recipient phone number
|
|
74
|
+
body: 'Hello!', // message text
|
|
75
|
+
previewUrl: true, // optional: enable URL preview
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Result
|
|
79
|
+
{
|
|
80
|
+
success: true,
|
|
81
|
+
messageId: 'wamid.xxx'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Or on error
|
|
85
|
+
{
|
|
86
|
+
success: false,
|
|
87
|
+
messageId: '',
|
|
88
|
+
error: { code: 131047, message: 'Re-engagement message' }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `whatsapp.sendTemplate(params)`
|
|
93
|
+
|
|
94
|
+
Send a template message.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const result = await whatsapp.sendTemplate({
|
|
98
|
+
to: '1234567890',
|
|
99
|
+
templateName: 'order_confirmation',
|
|
100
|
+
languageCode: 'en',
|
|
101
|
+
components: [
|
|
102
|
+
{
|
|
103
|
+
type: 'header',
|
|
104
|
+
parameters: [{ type: 'image', image: { link: 'https://...' } }]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: 'body',
|
|
108
|
+
parameters: [
|
|
109
|
+
{ type: 'text', text: 'John' },
|
|
110
|
+
{ type: 'text', text: 'Order #123' }
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `whatsapp.sendImage(params)`
|
|
118
|
+
|
|
119
|
+
Send an image message.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const result = await whatsapp.sendImage({
|
|
123
|
+
to: '1234567890',
|
|
124
|
+
image: { link: 'https://example.com/image.jpg' },
|
|
125
|
+
// Or use media ID: image: { id: 'media_id' },
|
|
126
|
+
caption: 'Check this out!', // optional
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `whatsapp.sendVideo(params)`
|
|
131
|
+
|
|
132
|
+
Send a video message.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const result = await whatsapp.sendVideo({
|
|
136
|
+
to: '1234567890',
|
|
137
|
+
video: { link: 'https://example.com/video.mp4' },
|
|
138
|
+
caption: 'Watch this!', // optional
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `whatsapp.sendAudio(params)`
|
|
143
|
+
|
|
144
|
+
Send an audio message.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const result = await whatsapp.sendAudio({
|
|
148
|
+
to: '1234567890',
|
|
149
|
+
audio: { link: 'https://example.com/audio.mp3' },
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `whatsapp.sendDocument(params)`
|
|
154
|
+
|
|
155
|
+
Send a document.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const result = await whatsapp.sendDocument({
|
|
159
|
+
to: '1234567890',
|
|
160
|
+
document: { link: 'https://example.com/file.pdf' },
|
|
161
|
+
filename: 'report.pdf', // optional
|
|
162
|
+
caption: 'Here is the report', // optional
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `whatsapp.sendSticker(params)`
|
|
167
|
+
|
|
168
|
+
Send a sticker.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const result = await whatsapp.sendSticker({
|
|
172
|
+
to: '1234567890',
|
|
173
|
+
sticker: { id: 'sticker_media_id' },
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### `whatsapp.sendLocation(params)`
|
|
178
|
+
|
|
179
|
+
Send a location.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const result = await whatsapp.sendLocation({
|
|
183
|
+
to: '1234567890',
|
|
184
|
+
latitude: 37.7749,
|
|
185
|
+
longitude: -122.4194,
|
|
186
|
+
name: 'San Francisco', // optional
|
|
187
|
+
address: '123 Main St, San Francisco, CA', // optional
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `whatsapp.sendReaction(params)`
|
|
192
|
+
|
|
193
|
+
Send a reaction to a message.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const result = await whatsapp.sendReaction({
|
|
197
|
+
to: '1234567890',
|
|
198
|
+
messageId: 'wamid.xxx',
|
|
199
|
+
emoji: '👍',
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### `whatsapp.sendContacts(params)`
|
|
204
|
+
|
|
205
|
+
Send contact cards.
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const result = await whatsapp.sendContacts({
|
|
209
|
+
to: '1234567890',
|
|
210
|
+
contacts: [
|
|
211
|
+
{
|
|
212
|
+
name: { formatted_name: 'John Doe', first_name: 'John', last_name: 'Doe' },
|
|
213
|
+
phones: [{ phone: '+1234567890', type: 'CELL' }],
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### `whatsapp.sendInteractiveButtons(params)`
|
|
220
|
+
|
|
221
|
+
Send an interactive button message.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const result = await whatsapp.sendInteractiveButtons({
|
|
225
|
+
to: '1234567890',
|
|
226
|
+
body: 'Please choose an option:',
|
|
227
|
+
buttons: [
|
|
228
|
+
{ id: 'yes', title: 'Yes' },
|
|
229
|
+
{ id: 'no', title: 'No' },
|
|
230
|
+
{ id: 'maybe', title: 'Maybe' },
|
|
231
|
+
],
|
|
232
|
+
header: 'Confirmation', // optional
|
|
233
|
+
footer: 'Reply within 24 hours', // optional
|
|
234
|
+
})
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `whatsapp.sendInteractiveList(params)`
|
|
238
|
+
|
|
239
|
+
Send an interactive list message.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const result = await whatsapp.sendInteractiveList({
|
|
243
|
+
to: '1234567890',
|
|
244
|
+
body: 'Select a product:',
|
|
245
|
+
buttonText: 'View Products',
|
|
246
|
+
sections: [
|
|
247
|
+
{
|
|
248
|
+
title: 'Electronics',
|
|
249
|
+
rows: [
|
|
250
|
+
{ id: 'phone', title: 'Smartphone', description: 'Latest model' },
|
|
251
|
+
{ id: 'laptop', title: 'Laptop', description: 'High performance' },
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
title: 'Accessories',
|
|
256
|
+
rows: [
|
|
257
|
+
{ id: 'case', title: 'Phone Case' },
|
|
258
|
+
{ id: 'charger', title: 'Fast Charger' },
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
header: 'Our Products', // optional
|
|
263
|
+
footer: 'Prices may vary', // optional
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### `whatsapp.markAsRead(messageId)`
|
|
268
|
+
|
|
269
|
+
Mark a message as read.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const success = await whatsapp.markAsRead('wamid.xxx')
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Template Builder
|
|
278
|
+
|
|
279
|
+
Use the fluent builder API to construct template components easily:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { createTemplateBuilder } from '@swalha1999/whatsapp'
|
|
283
|
+
|
|
284
|
+
const components = createTemplateBuilder()
|
|
285
|
+
// Add header with media
|
|
286
|
+
.addHeader('image', 'https://example.com/image.jpg')
|
|
287
|
+
// Or text header
|
|
288
|
+
// .addTextHeader('Welcome!')
|
|
289
|
+
|
|
290
|
+
// Add body parameters ({{1}}, {{2}}, etc.)
|
|
291
|
+
.addBodyParam('John Doe')
|
|
292
|
+
.addBodyParam('Order #12345')
|
|
293
|
+
.addBodyParam('$99.99')
|
|
294
|
+
|
|
295
|
+
// Add quick reply buttons
|
|
296
|
+
.addQuickReplyButton(0, 'confirm_payload')
|
|
297
|
+
.addQuickReplyButton(1, 'cancel_payload')
|
|
298
|
+
|
|
299
|
+
.build()
|
|
300
|
+
|
|
301
|
+
await whatsapp.sendTemplate({
|
|
302
|
+
to: '1234567890',
|
|
303
|
+
templateName: 'order_update',
|
|
304
|
+
languageCode: 'en',
|
|
305
|
+
components,
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Builder Methods
|
|
310
|
+
|
|
311
|
+
| Method | Description |
|
|
312
|
+
|--------|-------------|
|
|
313
|
+
| `addHeader(type, url)` | Add media header (image, video, document) |
|
|
314
|
+
| `addTextHeader(text)` | Add text header |
|
|
315
|
+
| `addBodyParam(text)` | Add body parameter |
|
|
316
|
+
| `addQuickReplyButton(index, payload)` | Add quick reply button |
|
|
317
|
+
| `addUrlButton(index, dynamicSuffix)` | Add URL button with dynamic suffix |
|
|
318
|
+
| `build()` | Build and return components array |
|
|
319
|
+
|
|
320
|
+
### URL Buttons Example
|
|
321
|
+
|
|
322
|
+
For templates with URL buttons that have dynamic suffixes:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const components = createTemplateBuilder()
|
|
326
|
+
.addHeader('image', 'https://example.com/image.jpg')
|
|
327
|
+
.addBodyParam('John')
|
|
328
|
+
.addQuickReplyButton(0, 'rsvp_yes')
|
|
329
|
+
.addUrlButton(1, 'https://instagram.com/user')
|
|
330
|
+
.build()
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Webhook Handling
|
|
336
|
+
|
|
337
|
+
### Verify Webhook Subscription
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { verifyWebhook } from '@swalha1999/whatsapp'
|
|
341
|
+
|
|
342
|
+
// Express example
|
|
343
|
+
app.get('/webhook', (req, res) => {
|
|
344
|
+
const result = verifyWebhook(
|
|
345
|
+
{
|
|
346
|
+
mode: req.query['hub.mode'],
|
|
347
|
+
token: req.query['hub.verify_token'],
|
|
348
|
+
challenge: req.query['hub.challenge'],
|
|
349
|
+
},
|
|
350
|
+
process.env.VERIFY_TOKEN!
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if (result.valid) {
|
|
354
|
+
res.send(result.challenge)
|
|
355
|
+
} else {
|
|
356
|
+
res.sendStatus(403)
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Verify Webhook Signature (HMAC-SHA256)
|
|
362
|
+
|
|
363
|
+
For enhanced security, verify the webhook signature using your app secret:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { verifyWebhookSignature, parseWebhookPayload } from '@swalha1999/whatsapp'
|
|
367
|
+
|
|
368
|
+
export async function POST(request: Request) {
|
|
369
|
+
const signature = request.headers.get('x-hub-signature-256') || ''
|
|
370
|
+
const rawBody = await request.text()
|
|
371
|
+
|
|
372
|
+
if (!verifyWebhookSignature(rawBody, signature, process.env.APP_SECRET!)) {
|
|
373
|
+
return new Response('Invalid signature', { status: 401 })
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const payload = JSON.parse(rawBody)
|
|
377
|
+
const events = parseWebhookPayload(payload)
|
|
378
|
+
// ... handle events
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Parse Incoming Messages
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import { parseWebhookPayload } from '@swalha1999/whatsapp'
|
|
386
|
+
|
|
387
|
+
app.post('/webhook', (req, res) => {
|
|
388
|
+
const events = parseWebhookPayload(req.body)
|
|
389
|
+
|
|
390
|
+
for (const event of events) {
|
|
391
|
+
// Handle incoming messages
|
|
392
|
+
if (event.type === 'message' && event.message) {
|
|
393
|
+
const { id, from, timestamp, content } = event.message
|
|
394
|
+
|
|
395
|
+
switch (content.type) {
|
|
396
|
+
case 'text':
|
|
397
|
+
console.log(`Text from ${from}: ${content.body}`)
|
|
398
|
+
break
|
|
399
|
+
|
|
400
|
+
case 'button':
|
|
401
|
+
console.log(`Button clicked: ${content.payload}`)
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
case 'interactive':
|
|
405
|
+
console.log(`Reply: ${content.replyId} - ${content.replyTitle}`)
|
|
406
|
+
break
|
|
407
|
+
|
|
408
|
+
case 'media':
|
|
409
|
+
console.log(`Media: ${content.mediaId} (${content.mimeType})`)
|
|
410
|
+
break
|
|
411
|
+
|
|
412
|
+
case 'location':
|
|
413
|
+
console.log(`Location: ${content.latitude}, ${content.longitude}`)
|
|
414
|
+
break
|
|
415
|
+
|
|
416
|
+
case 'sticker':
|
|
417
|
+
console.log(`Sticker: ${content.stickerId}`)
|
|
418
|
+
break
|
|
419
|
+
|
|
420
|
+
case 'reaction':
|
|
421
|
+
console.log(`Reaction: ${content.emoji} to ${content.messageId}`)
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
case 'contacts':
|
|
425
|
+
console.log(`Contacts: ${content.contacts.map(c => c.formattedName).join(', ')}`)
|
|
426
|
+
break
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Handle status updates
|
|
431
|
+
if (event.type === 'status' && event.status) {
|
|
432
|
+
const { messageId, status, recipientId, error } = event.status
|
|
433
|
+
console.log(`Message ${messageId} is now ${status}`)
|
|
434
|
+
|
|
435
|
+
if (error) {
|
|
436
|
+
console.error(`Error: ${error.code} - ${error.message}`)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
res.sendStatus(200)
|
|
442
|
+
})
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Types
|
|
448
|
+
|
|
449
|
+
### Message Content Types
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
type MessageContent =
|
|
453
|
+
| { type: 'text'; body: string }
|
|
454
|
+
| { type: 'button'; payload: string; text: string }
|
|
455
|
+
| { type: 'interactive'; replyId: string; replyTitle: string; replyDescription?: string }
|
|
456
|
+
| { type: 'media'; mediaType: 'image' | 'video' | 'audio' | 'document'; mediaId: string; mimeType: string; caption?: string; filename?: string }
|
|
457
|
+
| { type: 'location'; latitude: number; longitude: number; name?: string; address?: string }
|
|
458
|
+
| { type: 'sticker'; stickerId: string; mimeType: string; animated?: boolean }
|
|
459
|
+
| { type: 'reaction'; emoji: string; messageId: string }
|
|
460
|
+
| { type: 'contacts'; contacts: ParsedContactCard[] }
|
|
461
|
+
| { type: 'unknown' }
|
|
462
|
+
|
|
463
|
+
interface ParsedContactCard {
|
|
464
|
+
formattedName: string
|
|
465
|
+
firstName?: string
|
|
466
|
+
lastName?: string
|
|
467
|
+
phones?: Array<{ phone: string; type?: string }>
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Status Types
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
type StatusType = 'sent' | 'delivered' | 'read' | 'failed'
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Parsed Webhook
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
interface ParsedWebhook {
|
|
481
|
+
type: 'message' | 'status' | 'unknown'
|
|
482
|
+
phoneNumberId: string
|
|
483
|
+
message?: ParsedMessage
|
|
484
|
+
status?: ParsedStatus
|
|
485
|
+
contact?: ParsedContact // Contact info for incoming messages
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
interface ParsedContact {
|
|
489
|
+
name: string // Profile name
|
|
490
|
+
waId: string // WhatsApp ID
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
interface ParsedMessage {
|
|
494
|
+
id: string
|
|
495
|
+
from: string
|
|
496
|
+
timestamp: Date
|
|
497
|
+
type: string
|
|
498
|
+
content: MessageContent
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
interface ParsedStatus {
|
|
502
|
+
messageId: string
|
|
503
|
+
status: 'sent' | 'delivered' | 'read' | 'failed'
|
|
504
|
+
recipientId: string
|
|
505
|
+
timestamp: Date
|
|
506
|
+
error?: { code: number; message: string }
|
|
507
|
+
conversation?: {
|
|
508
|
+
id: string
|
|
509
|
+
origin: 'user_initiated' | 'business_initiated' | 'referral_conversion'
|
|
510
|
+
expiresAt?: Date
|
|
511
|
+
}
|
|
512
|
+
pricing?: {
|
|
513
|
+
billable: boolean
|
|
514
|
+
category: string
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Error Handling
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import { createWhatsApp, WhatsAppError } from '@swalha1999/whatsapp'
|
|
525
|
+
|
|
526
|
+
const whatsapp = createWhatsApp({ ... })
|
|
527
|
+
|
|
528
|
+
const result = await whatsapp.sendText({
|
|
529
|
+
to: '1234567890',
|
|
530
|
+
body: 'Hello!',
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
if (!result.success) {
|
|
534
|
+
console.error(`Error ${result.error?.code}: ${result.error?.message}`)
|
|
535
|
+
|
|
536
|
+
// Common error codes:
|
|
537
|
+
// 131047 - Re-engagement message required (24h window expired)
|
|
538
|
+
// 131051 - Invalid recipient
|
|
539
|
+
// 132000 - Template not found
|
|
540
|
+
// 100 - Invalid parameter
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## Batch Sending
|
|
547
|
+
|
|
548
|
+
Send messages in batches with automatic delays to avoid rate limits:
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
import { createWhatsApp, batchSend, createTemplateBuilder } from '@swalha1999/whatsapp'
|
|
552
|
+
|
|
553
|
+
const whatsapp = createWhatsApp({
|
|
554
|
+
apiToken: process.env.WHATSAPP_TOKEN!,
|
|
555
|
+
phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
const contacts = [
|
|
559
|
+
{ phone: '1234567890', name: 'John' },
|
|
560
|
+
{ phone: '0987654321', name: 'Jane' },
|
|
561
|
+
// ... more contacts
|
|
562
|
+
]
|
|
563
|
+
|
|
564
|
+
const result = await batchSend(
|
|
565
|
+
contacts,
|
|
566
|
+
(contact) => whatsapp.sendTemplate({
|
|
567
|
+
to: contact.phone,
|
|
568
|
+
templateName: 'invitation',
|
|
569
|
+
languageCode: 'en',
|
|
570
|
+
components: createTemplateBuilder().addBodyParam(contact.name).build(),
|
|
571
|
+
}),
|
|
572
|
+
{
|
|
573
|
+
batchSize: 70, // Send 70 messages per batch (default: 50)
|
|
574
|
+
delayMs: 1000, // Wait 1 second between batches (default: 1000)
|
|
575
|
+
onProgress: (done, total) => console.log(`Progress: ${done}/${total}`),
|
|
576
|
+
onError: (error, contact, index) => console.error(`Failed for ${contact.phone}`),
|
|
577
|
+
}
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
console.log(`Sent ${result.successful}/${result.total} messages`)
|
|
581
|
+
console.log(`Failed: ${result.failed}`)
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### BatchResult
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
interface BatchResult {
|
|
588
|
+
total: number // Total items processed
|
|
589
|
+
successful: number // Successfully sent
|
|
590
|
+
failed: number // Failed to send
|
|
591
|
+
results: SendResult[] // All results
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## Next.js App Router Example
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
// app/api/whatsapp/webhook/route.ts
|
|
601
|
+
import { parseWebhookPayload, verifyWebhook } from '@swalha1999/whatsapp'
|
|
602
|
+
|
|
603
|
+
export async function GET(request: Request) {
|
|
604
|
+
const url = new URL(request.url)
|
|
605
|
+
|
|
606
|
+
const result = verifyWebhook(
|
|
607
|
+
{
|
|
608
|
+
mode: url.searchParams.get('hub.mode') || '',
|
|
609
|
+
token: url.searchParams.get('hub.verify_token') || '',
|
|
610
|
+
challenge: url.searchParams.get('hub.challenge') || '',
|
|
611
|
+
},
|
|
612
|
+
process.env.WHATSAPP_VERIFY_TOKEN!
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
return result.valid
|
|
616
|
+
? new Response(result.challenge)
|
|
617
|
+
: new Response('Forbidden', { status: 403 })
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export async function POST(request: Request) {
|
|
621
|
+
const payload = await request.json()
|
|
622
|
+
const events = parseWebhookPayload(payload)
|
|
623
|
+
|
|
624
|
+
for (const event of events) {
|
|
625
|
+
if (event.type === 'message') {
|
|
626
|
+
// Handle message
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return new Response('OK')
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## License
|
|
637
|
+
|
|
638
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TemplateComponent } from "./types-DwL3dbmb.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/templates/types.d.ts
|
|
4
|
+
interface TemplateBuilder {
|
|
5
|
+
addHeader(type: 'image' | 'video' | 'document', url: string): TemplateBuilder;
|
|
6
|
+
addTextHeader(text: string): TemplateBuilder;
|
|
7
|
+
addBodyParam(text: string): TemplateBuilder;
|
|
8
|
+
addQuickReplyButton(index: number, payload: string): TemplateBuilder;
|
|
9
|
+
addUrlButton(index: number, dynamicSuffix: string): TemplateBuilder;
|
|
10
|
+
build(): TemplateComponent[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/templates/builders.d.ts
|
|
15
|
+
//# sourceMappingURL=types.d.ts.map
|
|
16
|
+
declare function createTemplateBuilder(): TemplateBuilder;
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=builders.d.ts.map
|
|
20
|
+
|
|
21
|
+
export { TemplateBuilder, createTemplateBuilder as createTemplateBuilder$1 };
|
|
22
|
+
//# sourceMappingURL=index-9aM5qSPE.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-9aM5qSPE.d.mts","names":[],"sources":["../src/templates/types.ts","../src/templates/builders.ts"],"sourcesContent":null,"mappings":";;;UAEiB,eAAA;gEAC+C;EAD/C,aAAA,CAAA,IAAe,EAAA,MAAA,CAAA,EAED,eAFC;EAAA,YAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAGF,eAHE;EAAA,mBACgC,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAGT,eAHS;EAAe,YAChD,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,CAAA,EAGuB,eAHvB;EAAe,KAChB,EAAA,EAGnB,iBAHmB,EAAA;;;;;;iBCFd,qBAAA,CAAA,GAAyB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TemplateComponent } from "./types-D8Ot82od.js";
|
|
2
|
+
|
|
3
|
+
//#region src/templates/types.d.ts
|
|
4
|
+
interface TemplateBuilder {
|
|
5
|
+
addHeader(type: 'image' | 'video' | 'document', url: string): TemplateBuilder;
|
|
6
|
+
addTextHeader(text: string): TemplateBuilder;
|
|
7
|
+
addBodyParam(text: string): TemplateBuilder;
|
|
8
|
+
addQuickReplyButton(index: number, payload: string): TemplateBuilder;
|
|
9
|
+
addUrlButton(index: number, dynamicSuffix: string): TemplateBuilder;
|
|
10
|
+
build(): TemplateComponent[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/templates/builders.d.ts
|
|
15
|
+
//# sourceMappingURL=types.d.ts.map
|
|
16
|
+
declare function createTemplateBuilder(): TemplateBuilder;
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=builders.d.ts.map
|
|
20
|
+
|
|
21
|
+
export { TemplateBuilder, createTemplateBuilder };
|
|
22
|
+
//# sourceMappingURL=index-CTZd2C9y.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-CTZd2C9y.d.ts","names":[],"sources":["../src/templates/types.ts","../src/templates/builders.ts"],"sourcesContent":null,"mappings":";;;UAEiB,eAAA;gEAC+C;EAD/C,aAAA,CAAA,IAAe,EAAA,MAAA,CAAA,EAED,eAFC;EAAA,YAAA,CAAA,IAAA,EAAA,MAAA,CAAA,EAGF,eAHE;EAAA,mBACgC,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAGT,eAHS;EAAe,YAChD,CAAA,KAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,CAAA,EAGuB,eAHvB;EAAe,KAChB,EAAA,EAGnB,iBAHmB,EAAA;;;;;;iBCFd,qBAAA,CAAA,GAAyB"}
|