whatsapp-cloud 0.0.3 → 0.0.5
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/CHANGELOG.md +13 -0
- package/README.md +11 -0
- package/agent_docs/DESIGN.md +707 -0
- package/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
- package/agent_docs/MESSAGES_NAMESPACE_ANALYSIS.md +368 -0
- package/agent_docs/NAMING_DECISION.md +78 -0
- package/agent_docs/STRUCTURE.md +711 -0
- package/agent_docs/messages-namespace-design.md +357 -0
- package/cloud-api-docs/webhooks/endpoint.md +112 -0
- package/cloud-api-docs/webhooks/overview.md +154 -0
- package/package.json +10 -3
- package/src/client/HttpClient.ts +159 -0
- package/src/client/WhatsAppClient.ts +58 -0
- package/src/client/index.ts +2 -0
- package/src/errors.ts +58 -0
- package/src/examples/main.ts +9 -0
- package/src/examples/template.ts +134 -0
- package/src/index.ts +16 -1
- package/src/schemas/accounts/index.ts +1 -0
- package/src/schemas/accounts/phone-number.ts +20 -0
- package/src/schemas/business/account.ts +43 -0
- package/src/schemas/business/index.ts +2 -0
- package/src/schemas/client.ts +50 -0
- package/src/schemas/debug.ts +25 -0
- package/src/schemas/index.ts +6 -0
- package/src/schemas/messages/index.ts +2 -0
- package/src/schemas/messages/request.ts +82 -0
- package/src/schemas/messages/response.ts +19 -0
- package/src/schemas/templates/component.ts +145 -0
- package/src/schemas/templates/index.ts +4 -0
- package/src/schemas/templates/request.ts +78 -0
- package/src/schemas/templates/response.ts +64 -0
- package/src/services/accounts/AccountsClient.ts +34 -0
- package/src/services/accounts/AccountsService.ts +45 -0
- package/src/services/accounts/index.ts +2 -0
- package/src/services/accounts/methods/list-phone-numbers.ts +15 -0
- package/src/services/business/BusinessClient.ts +34 -0
- package/src/services/business/BusinessService.ts +45 -0
- package/src/services/business/index.ts +3 -0
- package/src/services/business/methods/list-accounts.ts +17 -0
- package/src/services/index.ts +2 -0
- package/src/services/messages/MessagesClient.ts +34 -0
- package/src/services/messages/MessagesService.ts +97 -0
- package/src/services/messages/index.ts +8 -0
- package/src/services/messages/methods/send-image.ts +33 -0
- package/src/services/messages/methods/send-location.ts +32 -0
- package/src/services/messages/methods/send-reaction.ts +33 -0
- package/src/services/messages/methods/send-text.ts +32 -0
- package/src/services/messages/utils/build-message-payload.ts +32 -0
- package/src/services/templates/TemplatesClient.ts +35 -0
- package/src/services/templates/TemplatesService.ts +117 -0
- package/src/services/templates/index.ts +3 -0
- package/src/services/templates/methods/create.ts +27 -0
- package/src/services/templates/methods/delete.ts +38 -0
- package/src/services/templates/methods/get.ts +23 -0
- package/src/services/templates/methods/list.ts +36 -0
- package/src/services/templates/methods/update.ts +35 -0
- package/src/types/accounts/index.ts +1 -0
- package/src/types/accounts/phone-number.ts +9 -0
- package/src/types/business/account.ts +10 -0
- package/src/types/business/index.ts +2 -0
- package/src/types/client.ts +8 -0
- package/src/types/debug.ts +8 -0
- package/src/types/index.ts +6 -0
- package/src/types/messages/index.ts +2 -0
- package/src/types/messages/request.ts +27 -0
- package/src/types/messages/response.ts +7 -0
- package/src/types/templates/component.ts +33 -0
- package/src/types/templates/index.ts +4 -0
- package/src/types/templates/request.ts +28 -0
- package/src/types/templates/response.ts +34 -0
- package/src/utils/zod-error.ts +28 -0
- package/tsconfig.json +6 -4
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# Incoming Messages & Webhooks - Brainstorming Document
|
|
2
|
+
|
|
3
|
+
## 🎯 Goal
|
|
4
|
+
|
|
5
|
+
Add incoming message handling to the WhatsApp Cloud SDK. Since this is a **client SDK** (not a server framework), we need to provide:
|
|
6
|
+
1. **Webhook verification utilities** - Help verify GET requests from Meta
|
|
7
|
+
2. **Signature validation utilities** - Validate POST request signatures
|
|
8
|
+
3. **Webhook payload parsing** - Parse and validate incoming webhook payloads
|
|
9
|
+
4. **Message handlers** - Type-safe handler system for processing incoming messages
|
|
10
|
+
5. **Type definitions** - Complete type safety for incoming messages
|
|
11
|
+
|
|
12
|
+
## 📋 Key Requirements from Meta Docs
|
|
13
|
+
|
|
14
|
+
### Webhook Verification (GET)
|
|
15
|
+
- Meta sends: `GET /webhook?hub.mode=subscribe&hub.challenge=<CHALLENGE>&hub.verify_token=<TOKEN>`
|
|
16
|
+
- We must: Compare `hub.verify_token` with our stored token
|
|
17
|
+
- Response: Return `hub.challenge` as plain text with 200 status if valid
|
|
18
|
+
|
|
19
|
+
### Webhook Signature Validation (POST)
|
|
20
|
+
- Meta sends: `X-Hub-Signature-256: sha256=<HASH>` header
|
|
21
|
+
- We must: Generate HMAC-SHA256 hash using `app_secret` as key
|
|
22
|
+
- Validation: Compare our hash with the header value
|
|
23
|
+
|
|
24
|
+
### Webhook Payload Structure
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"object": "whatsapp_business_account",
|
|
28
|
+
"entry": [{
|
|
29
|
+
"id": "102290129340398",
|
|
30
|
+
"changes": [{
|
|
31
|
+
"value": {
|
|
32
|
+
"messaging_product": "whatsapp",
|
|
33
|
+
"metadata": {
|
|
34
|
+
"display_phone_number": "15550783881",
|
|
35
|
+
"phone_number_id": "106540352242922"
|
|
36
|
+
},
|
|
37
|
+
"contacts": [{ "profile": { "name": "Sheena Nelson" }, "wa_id": "16505551234" }],
|
|
38
|
+
"messages": [{ "from": "16505551234", "id": "wamid...", "timestamp": "1749416383", "type": "text", "text": { "body": "Hello!" } }],
|
|
39
|
+
"statuses": [{ ... }] // For outgoing message status updates
|
|
40
|
+
},
|
|
41
|
+
"field": "messages"
|
|
42
|
+
}]
|
|
43
|
+
}]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🏗️ Architecture Proposal
|
|
48
|
+
|
|
49
|
+
### 1. Webhooks Service Namespace
|
|
50
|
+
|
|
51
|
+
Similar to `messages`, `accounts`, `business`, we add a `webhooks` namespace:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const client = new WhatsAppClient({
|
|
55
|
+
accessToken: "...",
|
|
56
|
+
appSecret: "...", // Required for signature validation
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Webhook utilities
|
|
60
|
+
client.webhooks.verify(request) // Verify GET request
|
|
61
|
+
client.webhooks.validateSignature(request, body) // Validate POST signature
|
|
62
|
+
client.webhooks.parse(payload) // Parse webhook payload
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Incoming Message Types & Schemas
|
|
66
|
+
|
|
67
|
+
Create schemas for incoming messages (mirroring outgoing structure):
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
src/
|
|
71
|
+
├── schemas/
|
|
72
|
+
│ └── webhooks/
|
|
73
|
+
│ ├── index.ts
|
|
74
|
+
│ ├── payload.ts # WebhookPayloadSchema
|
|
75
|
+
│ ├── incoming-message.ts # IncomingMessage schemas (text, image, audio, etc.)
|
|
76
|
+
│ └── status.ts # Message status update schemas
|
|
77
|
+
├── types/
|
|
78
|
+
│ └── webhooks/
|
|
79
|
+
│ ├── index.ts
|
|
80
|
+
│ ├── payload.ts
|
|
81
|
+
│ ├── incoming-message.ts
|
|
82
|
+
│ └── status.ts
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. Handler System
|
|
86
|
+
|
|
87
|
+
Provide a handler system that users can implement:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// User's handler implementation
|
|
91
|
+
const handlers = {
|
|
92
|
+
text: async (message: IncomingTextMessage, context: MessageContext) => {
|
|
93
|
+
// Process text message
|
|
94
|
+
return { messages: [{ type: "text", text: { body: "Hello back!" } }] };
|
|
95
|
+
},
|
|
96
|
+
image: async (message: IncomingImageMessage, context: MessageContext) => {
|
|
97
|
+
// Process image message
|
|
98
|
+
return { messages: [] };
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// SDK provides the router/dispatcher
|
|
103
|
+
const result = await client.webhooks.handle(payload, handlers);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 🔧 Implementation Details
|
|
107
|
+
|
|
108
|
+
### Option A: Framework-Agnostic Utilities (Recommended)
|
|
109
|
+
|
|
110
|
+
**Pros:**
|
|
111
|
+
- Works with any framework (Express, Hono, Next.js, etc.)
|
|
112
|
+
- Users implement their own endpoints
|
|
113
|
+
- SDK provides validation/parsing utilities
|
|
114
|
+
|
|
115
|
+
**Cons:**
|
|
116
|
+
- Users need to wire up routes themselves
|
|
117
|
+
- More boilerplate for users
|
|
118
|
+
|
|
119
|
+
**Structure:**
|
|
120
|
+
```typescript
|
|
121
|
+
// src/services/webhooks/WebhooksService.ts
|
|
122
|
+
export class WebhooksService {
|
|
123
|
+
/**
|
|
124
|
+
* Verify webhook GET request from Meta
|
|
125
|
+
*/
|
|
126
|
+
verify(request: { query: { hub_mode?: string; hub_verify_token?: string; hub_challenge?: string } }, verifyToken: string): string | null {
|
|
127
|
+
if (request.query.hub_mode === "subscribe" && request.query.hub_verify_token === verifyToken) {
|
|
128
|
+
return request.query.hub_challenge || null;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validate webhook POST signature
|
|
135
|
+
*/
|
|
136
|
+
validateSignature(body: string, signature: string, appSecret: string): boolean {
|
|
137
|
+
// HMAC-SHA256 validation
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse webhook payload
|
|
142
|
+
*/
|
|
143
|
+
parse(payload: unknown): WebhookPayload {
|
|
144
|
+
// Validate and parse using Zod schema
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Extract incoming messages from payload
|
|
149
|
+
*/
|
|
150
|
+
extractMessages(payload: WebhookPayload): IncomingMessage[] {
|
|
151
|
+
// Flatten messages from nested structure
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Extract status updates from payload
|
|
156
|
+
*/
|
|
157
|
+
extractStatuses(payload: WebhookPayload): MessageStatus[] {
|
|
158
|
+
// Extract status updates
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Option B: Framework Adapters
|
|
164
|
+
|
|
165
|
+
**Pros:**
|
|
166
|
+
- Easier to use for specific frameworks
|
|
167
|
+
- Less boilerplate
|
|
168
|
+
|
|
169
|
+
**Cons:**
|
|
170
|
+
- Need to maintain multiple adapters
|
|
171
|
+
- Framework-specific dependencies
|
|
172
|
+
|
|
173
|
+
**Structure:**
|
|
174
|
+
```typescript
|
|
175
|
+
// src/adapters/express.ts
|
|
176
|
+
export function createExpressWebhookHandler(client: WhatsAppClient, handlers: MessageHandlers) {
|
|
177
|
+
return async (req: Request, res: Response) => {
|
|
178
|
+
// Handle GET verification
|
|
179
|
+
if (req.method === "GET") {
|
|
180
|
+
const challenge = client.webhooks.verify(req.query, process.env.VERIFY_TOKEN);
|
|
181
|
+
if (challenge) return res.send(challenge);
|
|
182
|
+
return res.status(403).send("Forbidden");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Handle POST messages
|
|
186
|
+
const isValid = client.webhooks.validateSignature(req.body, req.headers["x-hub-signature-256"], process.env.APP_SECRET);
|
|
187
|
+
if (!isValid) return res.status(401).send("Invalid signature");
|
|
188
|
+
|
|
189
|
+
const payload = client.webhooks.parse(req.body);
|
|
190
|
+
await client.webhooks.handle(payload, handlers);
|
|
191
|
+
return res.json({ success: true });
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Option C: Hybrid Approach (Best of Both)
|
|
197
|
+
|
|
198
|
+
Provide utilities + optional framework helpers:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Core utilities (always available)
|
|
202
|
+
client.webhooks.verify(...)
|
|
203
|
+
client.webhooks.validateSignature(...)
|
|
204
|
+
client.webhooks.parse(...)
|
|
205
|
+
|
|
206
|
+
// Optional framework helpers (separate exports)
|
|
207
|
+
import { createExpressHandler } from "@whatsapp-cloud/express";
|
|
208
|
+
import { createHonoHandler } from "@whatsapp-cloud/hono";
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## 📦 Proposed File Structure
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
src/
|
|
215
|
+
├── services/
|
|
216
|
+
│ └── webhooks/
|
|
217
|
+
│ ├── index.ts
|
|
218
|
+
│ ├── WebhooksService.ts # Core service class
|
|
219
|
+
│ ├── WebhooksClient.ts # (if needed, similar to MessagesClient)
|
|
220
|
+
│ ├── utils/
|
|
221
|
+
│ │ ├── verify.ts # GET verification logic
|
|
222
|
+
│ │ ├── validate-signature.ts # POST signature validation
|
|
223
|
+
│ │ ├── parse-payload.ts # Payload parsing
|
|
224
|
+
│ │ └── extract-messages.ts # Extract messages from payload
|
|
225
|
+
│ └── handlers/
|
|
226
|
+
│ ├── index.ts
|
|
227
|
+
│ ├── types.ts # Handler types
|
|
228
|
+
│ └── dispatcher.ts # Message type dispatcher
|
|
229
|
+
├── schemas/
|
|
230
|
+
│ └── webhooks/
|
|
231
|
+
│ ├── index.ts
|
|
232
|
+
│ ├── payload.ts # WebhookPayloadSchema
|
|
233
|
+
│ ├── incoming-message.ts # Incoming message schemas
|
|
234
|
+
│ └── status.ts # Status update schemas
|
|
235
|
+
└── types/
|
|
236
|
+
└── webhooks/
|
|
237
|
+
├── index.ts
|
|
238
|
+
├── payload.ts
|
|
239
|
+
├── incoming-message.ts
|
|
240
|
+
└── status.ts
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## 🎨 API Design Examples
|
|
244
|
+
|
|
245
|
+
### Basic Usage (Framework-Agnostic)
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { WhatsAppClient } from "@whatsapp-cloud/sdk";
|
|
249
|
+
import express from "express";
|
|
250
|
+
|
|
251
|
+
const client = new WhatsAppClient({
|
|
252
|
+
accessToken: process.env.ACCESS_TOKEN,
|
|
253
|
+
appSecret: process.env.APP_SECRET, // For signature validation
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const app = express();
|
|
257
|
+
app.use(express.json());
|
|
258
|
+
|
|
259
|
+
// GET /webhook - Verification
|
|
260
|
+
app.get("/webhook", (req, res) => {
|
|
261
|
+
const challenge = client.webhooks.verify(req.query, process.env.VERIFY_TOKEN);
|
|
262
|
+
if (challenge) {
|
|
263
|
+
res.send(challenge);
|
|
264
|
+
} else {
|
|
265
|
+
res.status(403).send("Forbidden");
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// POST /webhook - Incoming messages
|
|
270
|
+
app.post("/webhook", async (req, res) => {
|
|
271
|
+
const signature = req.headers["x-hub-signature-256"] as string;
|
|
272
|
+
const bodyString = JSON.stringify(req.body);
|
|
273
|
+
|
|
274
|
+
// Validate signature
|
|
275
|
+
if (!client.webhooks.validateSignature(bodyString, signature, process.env.APP_SECRET)) {
|
|
276
|
+
return res.status(401).send("Invalid signature");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Parse payload
|
|
280
|
+
const payload = client.webhooks.parse(req.body);
|
|
281
|
+
|
|
282
|
+
// Extract messages
|
|
283
|
+
const messages = client.webhooks.extractMessages(payload);
|
|
284
|
+
const statuses = client.webhooks.extractStatuses(payload);
|
|
285
|
+
|
|
286
|
+
// Process messages
|
|
287
|
+
for (const message of messages) {
|
|
288
|
+
if (message.type === "text") {
|
|
289
|
+
// Handle text message
|
|
290
|
+
await client.messages.sendText({
|
|
291
|
+
to: message.from,
|
|
292
|
+
text: { body: "Echo: " + message.text.body },
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
res.json({ success: true });
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### With Handler System
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
// Define handlers
|
|
305
|
+
const handlers = {
|
|
306
|
+
text: async (message: IncomingTextMessage, context: MessageContext) => {
|
|
307
|
+
console.log(`Received: ${message.text.body}`);
|
|
308
|
+
return {
|
|
309
|
+
messages: [{
|
|
310
|
+
type: "text" as const,
|
|
311
|
+
text: { body: `You said: ${message.text.body}` },
|
|
312
|
+
}],
|
|
313
|
+
};
|
|
314
|
+
},
|
|
315
|
+
image: async (message: IncomingImageMessage, context: MessageContext) => {
|
|
316
|
+
// Download and process image
|
|
317
|
+
const imageData = await client.webhooks.downloadMedia(message.image.id);
|
|
318
|
+
return { messages: [] };
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Use dispatcher
|
|
323
|
+
app.post("/webhook", async (req, res) => {
|
|
324
|
+
// ... validation ...
|
|
325
|
+
const payload = client.webhooks.parse(req.body);
|
|
326
|
+
await client.webhooks.handle(payload, handlers);
|
|
327
|
+
res.json({ success: true });
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## 💾 Database Storage Considerations
|
|
332
|
+
|
|
333
|
+
### When to Store Messages?
|
|
334
|
+
|
|
335
|
+
**Option 1: Store on Send (Outgoing)**
|
|
336
|
+
```typescript
|
|
337
|
+
// User sends message
|
|
338
|
+
const response = await client.messages.sendText({ to: "+1234567890", text: { body: "Hello" } });
|
|
339
|
+
|
|
340
|
+
// Store immediately
|
|
341
|
+
await db.messages.create({
|
|
342
|
+
id: response.messages[0].id,
|
|
343
|
+
to: "+1234567890",
|
|
344
|
+
from: phoneNumberId,
|
|
345
|
+
body: "Hello",
|
|
346
|
+
status: "sent", // Initial status
|
|
347
|
+
sentAt: new Date(),
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Pros:**
|
|
352
|
+
- Immediate record in database
|
|
353
|
+
- Can track messages even if webhook fails
|
|
354
|
+
- Good for UI showing "sending..." state
|
|
355
|
+
|
|
356
|
+
**Cons:**
|
|
357
|
+
- Status might be outdated (webhook updates it)
|
|
358
|
+
- Need to handle webhook updates separately
|
|
359
|
+
|
|
360
|
+
**Option 2: Store on Webhook (Status Update)**
|
|
361
|
+
```typescript
|
|
362
|
+
// User sends message
|
|
363
|
+
await client.messages.sendText({ to: "+1234567890", text: { body: "Hello" } });
|
|
364
|
+
|
|
365
|
+
// Don't store yet, wait for webhook
|
|
366
|
+
|
|
367
|
+
// In webhook handler
|
|
368
|
+
app.post("/webhook", async (req, res) => {
|
|
369
|
+
const payload = client.webhooks.parse(req.body);
|
|
370
|
+
const statuses = client.webhooks.extractStatuses(payload);
|
|
371
|
+
|
|
372
|
+
for (const status of statuses) {
|
|
373
|
+
if (status.status === "sent") {
|
|
374
|
+
// Now store the message
|
|
375
|
+
await db.messages.create({
|
|
376
|
+
id: status.id,
|
|
377
|
+
to: status.recipient_id,
|
|
378
|
+
from: phoneNumberId,
|
|
379
|
+
body: "...", // Problem: we don't have the body!
|
|
380
|
+
status: status.status,
|
|
381
|
+
sentAt: new Date(parseInt(status.timestamp) * 1000),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Pros:**
|
|
389
|
+
- Single source of truth (webhook)
|
|
390
|
+
- Always has accurate status
|
|
391
|
+
|
|
392
|
+
**Cons:**
|
|
393
|
+
- Status webhook doesn't include message body/content
|
|
394
|
+
- Can't show "sending..." state in UI
|
|
395
|
+
- If webhook fails, message is lost
|
|
396
|
+
|
|
397
|
+
**Option 3: Hybrid Approach (Recommended)**
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// 1. Store on send with "pending" status
|
|
401
|
+
const response = await client.messages.sendText({ to: "+1234567890", text: { body: "Hello" } });
|
|
402
|
+
|
|
403
|
+
await db.messages.create({
|
|
404
|
+
id: response.messages[0].id,
|
|
405
|
+
to: "+1234567890",
|
|
406
|
+
from: phoneNumberId,
|
|
407
|
+
body: "Hello",
|
|
408
|
+
status: "pending", // Initial status
|
|
409
|
+
sentAt: new Date(),
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// 2. Update on webhook status
|
|
413
|
+
app.post("/webhook", async (req, res) => {
|
|
414
|
+
const payload = client.webhooks.parse(req.body);
|
|
415
|
+
const statuses = client.webhooks.extractStatuses(payload);
|
|
416
|
+
|
|
417
|
+
for (const status of statuses) {
|
|
418
|
+
await db.messages.update({
|
|
419
|
+
where: { id: status.id },
|
|
420
|
+
data: {
|
|
421
|
+
status: status.status, // "sent", "delivered", "read", "failed"
|
|
422
|
+
deliveredAt: status.status === "delivered" ? new Date(parseInt(status.timestamp) * 1000) : undefined,
|
|
423
|
+
readAt: status.status === "read" ? new Date(parseInt(status.timestamp) * 1000) : undefined,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**For Incoming Messages:**
|
|
431
|
+
```typescript
|
|
432
|
+
// Always store on webhook (only source of truth)
|
|
433
|
+
app.post("/webhook", async (req, res) => {
|
|
434
|
+
const payload = client.webhooks.parse(req.body);
|
|
435
|
+
const messages = client.webhooks.extractMessages(payload);
|
|
436
|
+
|
|
437
|
+
for (const message of messages) {
|
|
438
|
+
await db.messages.create({
|
|
439
|
+
id: message.id,
|
|
440
|
+
from: message.from,
|
|
441
|
+
to: phoneNumberId,
|
|
442
|
+
type: message.type,
|
|
443
|
+
body: message.type === "text" ? message.text.body : null,
|
|
444
|
+
receivedAt: new Date(parseInt(message.timestamp) * 1000),
|
|
445
|
+
status: "received",
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## 🎯 Recommended Approach
|
|
452
|
+
|
|
453
|
+
### Phase 1: Core Utilities (MVP)
|
|
454
|
+
1. ✅ Webhook verification utility
|
|
455
|
+
2. ✅ Signature validation utility
|
|
456
|
+
3. ✅ Payload parsing with Zod schemas
|
|
457
|
+
4. ✅ Type definitions for incoming messages
|
|
458
|
+
5. ✅ Extract messages/statuses from payload
|
|
459
|
+
|
|
460
|
+
### Phase 2: Handler System
|
|
461
|
+
1. ✅ Handler type definitions
|
|
462
|
+
2. ✅ Message dispatcher/router
|
|
463
|
+
3. ✅ Media download utility (for images/audio)
|
|
464
|
+
|
|
465
|
+
### Phase 3: Framework Helpers (Optional)
|
|
466
|
+
1. Express adapter
|
|
467
|
+
2. Hono adapter
|
|
468
|
+
3. Next.js API route helper
|
|
469
|
+
|
|
470
|
+
## 🤔 Open Questions
|
|
471
|
+
|
|
472
|
+
1. **Should we include media download utilities?**
|
|
473
|
+
- Yes, it's a common need and requires auth token
|
|
474
|
+
- Provide: `client.webhooks.downloadMedia(mediaId)`
|
|
475
|
+
|
|
476
|
+
2. **How to handle multiple phone numbers?**
|
|
477
|
+
- Webhook payload includes `phone_number_id` in metadata
|
|
478
|
+
- Users can filter/route based on this
|
|
479
|
+
|
|
480
|
+
3. **Should we provide a built-in handler dispatcher?**
|
|
481
|
+
- Yes, but make it optional
|
|
482
|
+
- Users can also manually iterate messages
|
|
483
|
+
|
|
484
|
+
4. **Error handling strategy?**
|
|
485
|
+
- Validation errors throw (invalid signature, malformed payload)
|
|
486
|
+
- Handler errors should be caught and logged (don't break webhook response)
|
|
487
|
+
|
|
488
|
+
5. **Status updates - separate handler?**
|
|
489
|
+
- Yes, provide separate handler type for status updates
|
|
490
|
+
- Many apps only care about messages, not statuses
|
|
491
|
+
|
|
492
|
+
## 📝 Next Steps
|
|
493
|
+
|
|
494
|
+
1. Create schemas for incoming messages (mirror outgoing structure)
|
|
495
|
+
2. Implement webhook verification utility
|
|
496
|
+
3. Implement signature validation utility
|
|
497
|
+
4. Create WebhooksService class
|
|
498
|
+
5. Add to WhatsAppClient namespace
|
|
499
|
+
6. Write examples and documentation
|
|
500
|
+
|