whatsapp-cloud 0.0.4 → 0.0.6
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/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
- package/cloud-api-docs/webhooks/endpoint.md +112 -0
- package/cloud-api-docs/webhooks/overview.md +154 -0
- package/docs/DEVELOPMENT.md +154 -0
- package/docs/webhooks.md +76 -0
- package/package.json +6 -2
- package/src/client/HttpClient.ts +43 -6
- package/src/client/WhatsAppClient.ts +6 -0
- package/src/examples/main.ts +9 -0
- package/src/examples/template.ts +134 -0
- package/src/index.ts +7 -0
- package/src/schemas/client.ts +2 -2
- package/src/schemas/index.ts +2 -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/schemas/webhooks/incoming-message.ts +38 -0
- package/src/schemas/webhooks/index.ts +3 -0
- package/src/schemas/webhooks/payload.ts +56 -0
- package/src/services/accounts/AccountsClient.ts +6 -14
- package/src/services/accounts/AccountsService.ts +19 -21
- package/src/services/accounts/methods/list-phone-numbers.ts +1 -2
- package/src/services/business/BusinessClient.ts +1 -9
- package/src/services/business/BusinessService.ts +19 -21
- package/src/services/business/methods/list-accounts.ts +1 -2
- package/src/services/messages/MessagesClient.ts +2 -6
- package/src/services/messages/MessagesService.ts +42 -22
- 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/services/webhooks/WebhooksService.ts +214 -0
- package/src/services/webhooks/index.ts +3 -0
- package/src/services/webhooks/utils/extract-messages.ts +25 -0
- package/src/services/webhooks/utils/extract-statuses.ts +25 -0
- package/src/services/webhooks/utils/verify.ts +29 -0
- package/src/types/index.ts +2 -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/types/webhooks/incoming-message.ts +16 -0
- package/src/types/webhooks/index.ts +3 -0
- package/src/types/webhooks/payload.ts +8 -0
- package/tsconfig.json +2 -3
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Nav-Logo
|
|
2
|
+
Build with us
|
|
3
|
+
Docs
|
|
4
|
+
Blog
|
|
5
|
+
Resources
|
|
6
|
+
Developer centers
|
|
7
|
+
Meine Apps
|
|
8
|
+
Dokumente
|
|
9
|
+
Übersicht
|
|
10
|
+
Webhooks
|
|
11
|
+
Webhooks
|
|
12
|
+
Aktualisiert: 07.11.2025
|
|
13
|
+
In diesem Dokument werden Webhooks beschrieben und wie sie auf der WhatsApp Business-Plattform verwendet werden.
|
|
14
|
+
Webhooks sind HTTP-Anfragen mit JSON-Payloads, die von Meta-Servern an einen von dir bestimmten Server gesendet werden. Die WhatsApp Business-Plattform verwendet Webhooks, um dich über eingehende Nachrichten, den Status ausgehender Nachrichten und andere wichtige Informationen zu informieren, wie z. B. Änderungen an deinem Kontostatus, Upgrades der Nachrichtenfunktion und Änderungen an der Qualitätsbewertungen deiner Vorlagen.
|
|
15
|
+
Hier siehst du einen Webhook, der eine Nachricht beschreibt, die von einem*einer WhatsApp-Benutzer*in an ein Unternehmen gesendet wurde:
|
|
16
|
+
{
|
|
17
|
+
"object": "whatsapp_business_account",
|
|
18
|
+
"entry": [
|
|
19
|
+
{
|
|
20
|
+
"id": "102290129340398",
|
|
21
|
+
"changes": [
|
|
22
|
+
{
|
|
23
|
+
"value": {
|
|
24
|
+
"messaging_product": "whatsapp",
|
|
25
|
+
"metadata": {
|
|
26
|
+
"display_phone_number": "15550783881",
|
|
27
|
+
"phone_number_id": "106540352242922"
|
|
28
|
+
},
|
|
29
|
+
"contacts": [
|
|
30
|
+
{
|
|
31
|
+
"profile": {
|
|
32
|
+
"name": "Sheena Nelson"
|
|
33
|
+
},
|
|
34
|
+
"wa_id": "16505551234"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"messages": [
|
|
38
|
+
{
|
|
39
|
+
"from": "16505551234",
|
|
40
|
+
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQTRBNjU5OUFFRTAzODEwMTQ0RgA=",
|
|
41
|
+
"timestamp": "1749416383",
|
|
42
|
+
"type": "text"
|
|
43
|
+
"text": {
|
|
44
|
+
"body": "Does it come in another color?"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"field": "messages"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Einen Webhook-Endpunkt erstellen
|
|
57
|
+
Um Webhooks zu empfangen, musst du einen Webhook-Endpunkt erstellen und konfigurieren. Im Dokument Webhook-Endpunkt erstellen erfährst du, wie du deinen eigenen Endpunkt erstellst.
|
|
58
|
+
Wenn du noch nicht bereit bist, deinen eigenen Endpunkt zu erstellen, kannst du einen Test-Webhook-Endpunkt erstellen, der Webhook-Payloads an die Konsole exportiert. Beachte jedoch, dass du deinen eigenen Endpunkt erstellen musst, bevor du deine App in einer Produktionsumgebung verwenden kannst.
|
|
59
|
+
Berechtigungen
|
|
60
|
+
Du benötigst die folgenden Berechtigungen, um Webhooks zu erhalten:
|
|
61
|
+
whatsapp_business_messaging – für messages-Webhooks
|
|
62
|
+
whatsapp_business_management – für alle anderen Webhooks
|
|
63
|
+
Wenn du ein*e direkte*r Entwickler*in bist, gewähre deiner App über deine*n Systemnutzer*in diese Berechtigungen, wenn du dein Systemtoken generierst.
|
|
64
|
+
Wenn du als Lösungsanbieter*in diese Berechtigungen benötigst, um deinen Unternehmenskunden entsprechende Services zur Verfügung zu stellen, musst du vor der App-Review die Genehmigung für erweiterten Zugriff auf die Berechtigungen erhalten. Erst dann können deine Unternehmenskunden deiner App während des Onboardings diese Berechtigungen erteilen.
|
|
65
|
+
Felder
|
|
66
|
+
Sobald du deinen Webhook-Endpunkt erstellt und konfiguriert hast, kannst du die folgenden Webhook-Felder abonnieren.
|
|
67
|
+
Name des Feldes Beschreibung
|
|
68
|
+
account_alerts
|
|
69
|
+
Der account_alerts-Webhook benachrichtigt dich über Änderungen des Messaging-Limits, des Unternehmensprofils und des Status als offizielles Unternehmenskonto einer Unternehmenstelefonnummer.
|
|
70
|
+
account_review_update
|
|
71
|
+
Der account_review_update-Webhook benachrichtigt dich, wenn ein WhatsApp-Unternehmenskonto hinsichtlich der Einhaltung unserer Richtlinien überprüft wurde.
|
|
72
|
+
account_update
|
|
73
|
+
Der account_update-Webhook benachrichtigt dich über Änderungen an der partnergeführten Unternehmensverifizierung eines WhatsApp-Unternehmenskontos, seiner Berechtigung für den Tarif für internationale Authentifizierung oder seinem Hauptgeschäftsstandort, wenn das Konto mit einem*einer Lösungsanbieter*in geteilt wird, bei Richtlinien- oder Nutzungsbedingungsverstößen oder wenn es gelöscht wird.
|
|
74
|
+
automatic_events
|
|
75
|
+
Der automatic_events-Webhook benachrichtigt dich, wenn wir ein Kauf- oder Lead-Event in einem Chat-Thread zwischen dir und einem*einer WhatsApp-Nutzer*in erkennen, der*die dir über deine Click-to-WhatsApp Ad eine Nachricht gesendet hat. Dazu musst du Berichte für automatische Events aktiviert haben.
|
|
76
|
+
business_capability_update
|
|
77
|
+
Der business_capability_update-Webhook informiert dich über Änderungen der Funktionen für WhatsApp-Unternehmenskonto oder Business-Portfolio (Messaging-Limits, Telefonnummer-Limits usw.).
|
|
78
|
+
history
|
|
79
|
+
Der history-Webhook wird verwendet, um den Chatverlauf in der WhatsApp Business-App eines Unternehmenskunden zu synchronisieren, der von einem*einer Lösungsanbieter*in freigeschaltet wurde.
|
|
80
|
+
message_template_components_update
|
|
81
|
+
Der message_template_components_update-Webhook benachrichtigt dich über Änderungen an den Komponenten einer Vorlage.
|
|
82
|
+
message_template_quality_update
|
|
83
|
+
Der message_template_quality_update-Webhook benachrichtigt dich über Änderungen an der Qualitätsbewertung einer Vorlage.
|
|
84
|
+
message_template_status_update
|
|
85
|
+
Der message_template_status_update-Webhook benachrichtigt dich über Änderungen am Status einer vorhandenen Vorlage.
|
|
86
|
+
messages
|
|
87
|
+
Der messages-Webhook beschreibt Nachrichten, die von einem*einer WhatsApp-Nutzer*in an ein Unternehmen gesendet werden, und den Status von Nachrichten, die von einem Unternehmen an eine*n WhatsApp-Nutzer*in gesendet werden.
|
|
88
|
+
partner_solutions
|
|
89
|
+
Der partner_solutions-Webhook beschreibt Änderungen am Status einer Multi-Partner-Lösung.
|
|
90
|
+
payment_configuration_update
|
|
91
|
+
Der payment_configuration_update-Webhook informiert dich über Änderungen an Zahlungskonfigurationen für die Payments API für Indien und die Payments API für Brasilien.
|
|
92
|
+
phone_number_name_update
|
|
93
|
+
Der phone_number_name_update-Webhook informiert dich über die Verifizierung des Display-Namens der Unternehmenstelefonnummer.
|
|
94
|
+
phone_number_quality_update
|
|
95
|
+
Der phone_number_quality_update-Webhook benachrichtigt dich über Änderungen an der Durchsatzrate einer Unternehmenstelefonnummer.
|
|
96
|
+
security
|
|
97
|
+
Der security-Webhook benachrichtigt dich über Änderungen an den Sicherheitseinstellungen einer Unternehmenstelefonnummer.
|
|
98
|
+
smb_app_state_sync
|
|
99
|
+
Der smb_app_state_sync-Webhook wird zum Synchronisieren von Kontakten von Nutzer*innen der WhatsApp Business-App verwendet, die über eine*n Lösungsanbieter*in freigeschaltet wurden.
|
|
100
|
+
smb_message_echoes
|
|
101
|
+
Der smb_message_echoes-Webhook benachrichtigt dich über Nachrichten, die über die WhatsApp Business-App oder ein begleitendes („verknüpftes“) Gerät von einem Unternehmenskunden gesendet wurden, der über eine*n Lösungsanbieter*in für die Cloud API freigeschaltet wurde.
|
|
102
|
+
template_category_update
|
|
103
|
+
Der template_category_update-Webhook benachrichtigt dich über Änderungen an der Kategorie einer Vorlage.
|
|
104
|
+
user_preferences
|
|
105
|
+
Der user_preferences-Webhook benachrichtigt dich über Änderungen an den Marketing-Nachrichten-Einstellungen eines*einer WhatsApp-Nutzer*in.
|
|
106
|
+
Webhooks überschreiben
|
|
107
|
+
Du kannst einen alternativen Webhook-Endpunkt für messages-Webhooks für dein WhatsApp-Unternehmenskonto (WABA) oder deine Unternehmenstelefonnummer verwenden. Dieser kann für Testzwecke nützlich sein oder wenn du als Lösungsanbieter eindeutige Webhook-Endpunkte für alle deine registrierten Kund\*innen verwenden möchtest.
|
|
108
|
+
In unserem Dokument zu Webhook-Überschreibungen erfährst du, wie du Webhooks überschreiben kannst.
|
|
109
|
+
Payload-Größe
|
|
110
|
+
Payload-Webhooks können bis zu 3 MB groß sein.
|
|
111
|
+
Fehler bei der Webhook-Auslieferung
|
|
112
|
+
Wenn wir eine Webhook-Anfrage an deinen Endpunkt senden und dein Server mit einem anderen HTTP-Statuscode als 200 antwortet oder wenn wir den Webhook aus einem anderen Grund nicht zustellen können, versuchen wir es so lange mit abnehmender Häufigkeit, bis die Anfrage erfolgreich ist, und zwar bis zu 7 Tage lang.
|
|
113
|
+
Beachte, dass Wiederholungsversuche an alle Apps gesendet werden, die Webhooks (und ihre entsprechenden Felder) für das WhatsApp Business-Konto abonniert haben. Dies kann zu doppelten Webhook-Benachrichtigungen führen.
|
|
114
|
+
IP-Adressen
|
|
115
|
+
Du kannst die IP-Adressen unserer Webhook-Server abrufen, indem du in deinem Terminal den folgenden Befehl ausführst:
|
|
116
|
+
whois -h whois.radb.net — '-i origin AS32934' | grep '^route' | awk '{print $2}' | sort
|
|
117
|
+
Wir ändern diese IP-Adressen in regelmäßigen Abständen. Wenn du also unsere Server auf der Positivliste hast, solltest du diese Liste gelegentlich neu erstellen und deine Positivliste entsprechend aktualisieren.
|
|
118
|
+
Problembehandlung
|
|
119
|
+
Wenn du keine Webhooks erhältst:
|
|
120
|
+
Vergewissere dich, dass dein Endpunkt Anfragen akzeptiert.
|
|
121
|
+
Sende eine Test-Payload an deinen Endpunkt über das Panel App-Dashboard > WhatsApp > Konfigurationen.
|
|
122
|
+
Stelle sicher, dass der Live-Modus für deine App aktiviert ist. Wenn deine App im Entwicklungsmodus ist, werden einige Webhooks nicht gesendet.
|
|
123
|
+
Mehr dazu
|
|
124
|
+
Siehe unseren Blogeintrag Implementierung von Webhooks mit Node.js für WhatsApp Business.
|
|
125
|
+
War diese Seite hilfreich?
|
|
126
|
+
„Daumen hoch“-Symbol
|
|
127
|
+
„Daumen runter“-Symbol
|
|
128
|
+
Meta
|
|
129
|
+
FacebookInstagramXLinkedInYouTube
|
|
130
|
+
Build with Meta
|
|
131
|
+
AI
|
|
132
|
+
Meta Horizon
|
|
133
|
+
Social technologies
|
|
134
|
+
Wearables
|
|
135
|
+
News
|
|
136
|
+
Meta for Developers
|
|
137
|
+
Blog
|
|
138
|
+
Success stories
|
|
139
|
+
Support
|
|
140
|
+
Developer Support
|
|
141
|
+
Bug tool
|
|
142
|
+
Platform status
|
|
143
|
+
Developer community forum
|
|
144
|
+
Report an incident
|
|
145
|
+
About us
|
|
146
|
+
About
|
|
147
|
+
Careers
|
|
148
|
+
Terms and policies
|
|
149
|
+
Responsible platform initiatives
|
|
150
|
+
Platform terms
|
|
151
|
+
Developer policies
|
|
152
|
+
Privacy policy
|
|
153
|
+
Cookies
|
|
154
|
+
English (US)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
## Local Development with npm/pnpm link
|
|
4
|
+
|
|
5
|
+
To test the SDK in your own project without publishing to npm:
|
|
6
|
+
|
|
7
|
+
### Step 1: Link the package (in whatsapp-cloud directory)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd /Users/lukas/Developer/whatsapp-cloud
|
|
11
|
+
pnpm build # Build the package first
|
|
12
|
+
pnpm link --global # Creates a global symlink
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or with npm:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm link
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Step 2: Link in your project
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd /path/to/your/project
|
|
25
|
+
pnpm link whatsapp-cloud
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or with npm:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm link whatsapp-cloud
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**What this does:** Creates a symlink in your project's `node_modules` pointing to your local package. You don't need to:
|
|
35
|
+
|
|
36
|
+
- Add it to `package.json` (link handles it)
|
|
37
|
+
- Run `pnpm install` (link is enough)
|
|
38
|
+
- Publish to npm
|
|
39
|
+
|
|
40
|
+
### Step 3: Use it in your project
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { WhatsAppClient, type IncomingTextMessage } from "whatsapp-cloud";
|
|
44
|
+
|
|
45
|
+
const client = new WhatsAppClient({
|
|
46
|
+
accessToken: "...",
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Important Notes
|
|
51
|
+
|
|
52
|
+
- **Rebuild after changes**: After making changes to whatsapp-cloud, run `pnpm build` in the whatsapp-cloud directory
|
|
53
|
+
- **Hot reload**: Some bundlers (like Next.js) may need a restart to pick up changes
|
|
54
|
+
- **Unlink**: When done, unlink with `pnpm unlink whatsapp-cloud` in your project
|
|
55
|
+
|
|
56
|
+
## Production / CI/CD
|
|
57
|
+
|
|
58
|
+
For production builds and CI/CD, you have a few options:
|
|
59
|
+
|
|
60
|
+
### Option 1: Publish to npm (Recommended)
|
|
61
|
+
|
|
62
|
+
Once ready, publish the package:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd /Users/lukas/Developer/whatsapp-cloud
|
|
66
|
+
pnpm publish
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then in your project's `package.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"whatsapp-cloud": "^0.0.5"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Option 2: Git dependency (For private repos)
|
|
80
|
+
|
|
81
|
+
If your repo is private, use git dependency:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"whatsapp-cloud": "git+https://github.com/your-username/whatsapp-cloud.git"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Option 3: Local file path (For monorepos)
|
|
92
|
+
|
|
93
|
+
If both projects are in the same repo/monorepo:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"whatsapp-cloud": "file:../whatsapp-cloud"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Option 4: Conditional linking (Dev vs Prod)
|
|
104
|
+
|
|
105
|
+
Use environment detection:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"dependencies": {
|
|
110
|
+
"whatsapp-cloud": process.env.NODE_ENV === "development"
|
|
111
|
+
? "link:../whatsapp-cloud"
|
|
112
|
+
: "^0.0.5"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Note:** For CI/CD, you'll need Option 1 (npm publish) or Option 2 (git dependency). `pnpm link` only works locally.
|
|
118
|
+
|
|
119
|
+
## Type Exports
|
|
120
|
+
|
|
121
|
+
All types are properly exported and can be imported in React/Next.js projects:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Import client
|
|
125
|
+
import { WhatsAppClient } from "whatsapp-cloud";
|
|
126
|
+
|
|
127
|
+
// Import types
|
|
128
|
+
import type {
|
|
129
|
+
IncomingTextMessage,
|
|
130
|
+
IncomingMessage,
|
|
131
|
+
WebhookPayload,
|
|
132
|
+
MessageContext,
|
|
133
|
+
CreateTemplateRequest,
|
|
134
|
+
// ... all other types
|
|
135
|
+
} from "whatsapp-cloud";
|
|
136
|
+
|
|
137
|
+
// Import schemas (for validation)
|
|
138
|
+
import {
|
|
139
|
+
incomingTextMessageSchema,
|
|
140
|
+
webhookPayloadSchema,
|
|
141
|
+
// ... all other schemas
|
|
142
|
+
} from "whatsapp-cloud";
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Verifying Exports
|
|
146
|
+
|
|
147
|
+
To verify all types are exported correctly:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# In your project
|
|
151
|
+
pnpm exec tsc --noEmit --skipLibCheck
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
This will check that all imported types are available.
|
package/docs/webhooks.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Webhooks
|
|
2
|
+
|
|
3
|
+
Handle incoming WhatsApp messages and status updates via webhooks.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { WhatsAppClient } from "@whatsapp-cloud/sdk";
|
|
9
|
+
|
|
10
|
+
const client = new WhatsAppClient({
|
|
11
|
+
accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// In your webhook endpoint
|
|
15
|
+
app.post("/webhook", async (req, res) => {
|
|
16
|
+
// handle() returns IMMEDIATELY - handlers run in background
|
|
17
|
+
client.webhooks.handle(req.body, {
|
|
18
|
+
text: async (message, context) => {
|
|
19
|
+
// This can take as long as needed (20s, 1min, etc.)
|
|
20
|
+
// Webhook already returned 200, so Meta is happy
|
|
21
|
+
|
|
22
|
+
// Process text message
|
|
23
|
+
console.log(`Received: ${message.text.body} from ${message.from}`);
|
|
24
|
+
|
|
25
|
+
// Store in database
|
|
26
|
+
await db.messages.create({
|
|
27
|
+
id: message.id,
|
|
28
|
+
from: message.from,
|
|
29
|
+
body: message.text.body,
|
|
30
|
+
phoneNumberId: context.metadata.phoneNumberId,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Send response
|
|
34
|
+
await client.messages.sendText({
|
|
35
|
+
to: `+${message.from}`,
|
|
36
|
+
text: { body: "Got it!" },
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Returns 200 IMMEDIATELY (handlers continue in background)
|
|
42
|
+
res.json({ success: true });
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Webhook Verification
|
|
47
|
+
|
|
48
|
+
Meta sends GET requests to verify your webhook endpoint:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
app.get("/webhook", (req, res) => {
|
|
52
|
+
const challenge = client.webhooks.verify(req.query, process.env.VERIFY_TOKEN);
|
|
53
|
+
if (challenge) {
|
|
54
|
+
return res.send(challenge);
|
|
55
|
+
}
|
|
56
|
+
return res.status(403).send("Forbidden");
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Low-Level API
|
|
61
|
+
|
|
62
|
+
For more control, extract messages manually:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const messages = client.webhooks.extractMessages(payload);
|
|
66
|
+
for (const message of messages) {
|
|
67
|
+
// Custom processing
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API Reference
|
|
72
|
+
|
|
73
|
+
- `client.webhooks.verify(query, token)` - Verify GET request, returns challenge or null
|
|
74
|
+
- `client.webhooks.extractMessages(payload)` - Extract messages from payload
|
|
75
|
+
- `client.webhooks.extractStatuses(payload)` - Extract status updates
|
|
76
|
+
- `client.webhooks.handle(payload, handlers, options?)` - Handle with type-safe callbacks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whatsapp-cloud",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Work in progress. A WhatsApp client tailored for LLMs—built to actually work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@changesets/cli": "^2.29.8",
|
|
14
|
+
"@types/node": "^25.0.3",
|
|
15
|
+
"dotenv": "^17.2.3",
|
|
14
16
|
"tsup": "^8.5.1",
|
|
17
|
+
"tsx": "^4.21.0",
|
|
15
18
|
"typescript": "^5.9.3"
|
|
16
19
|
},
|
|
17
20
|
"dependencies": {
|
|
@@ -19,6 +22,7 @@
|
|
|
19
22
|
},
|
|
20
23
|
"scripts": {
|
|
21
24
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
22
|
-
"lint": "tsc"
|
|
25
|
+
"lint": "tsc",
|
|
26
|
+
"example": "tsx src/examples/main.ts"
|
|
23
27
|
}
|
|
24
28
|
}
|
package/src/client/HttpClient.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { ClientConfig } from "../types/client";
|
|
2
2
|
|
|
3
|
+
interface APIErrorResponse {
|
|
4
|
+
error?: {
|
|
5
|
+
message?: string;
|
|
6
|
+
code?: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
/**
|
|
4
11
|
* HTTP client for making requests to the WhatsApp Cloud API
|
|
5
12
|
*/
|
|
@@ -42,12 +49,12 @@ export class HttpClient {
|
|
|
42
49
|
});
|
|
43
50
|
|
|
44
51
|
if (!response.ok) {
|
|
45
|
-
const error = await response.json().catch(() => ({
|
|
52
|
+
const error = (await response.json().catch(() => ({
|
|
46
53
|
error: {
|
|
47
54
|
message: response.statusText,
|
|
48
55
|
code: response.status,
|
|
49
56
|
},
|
|
50
|
-
}));
|
|
57
|
+
}))) as APIErrorResponse;
|
|
51
58
|
throw new Error(
|
|
52
59
|
`API Error: ${error.error?.message || response.statusText} (${
|
|
53
60
|
error.error?.code || response.status
|
|
@@ -72,12 +79,12 @@ export class HttpClient {
|
|
|
72
79
|
});
|
|
73
80
|
|
|
74
81
|
if (!response.ok) {
|
|
75
|
-
const error = await response.json().catch(() => ({
|
|
82
|
+
const error = (await response.json().catch(() => ({
|
|
76
83
|
error: {
|
|
77
84
|
message: response.statusText,
|
|
78
85
|
code: response.status,
|
|
79
86
|
},
|
|
80
|
-
}));
|
|
87
|
+
}))) as APIErrorResponse;
|
|
81
88
|
throw new Error(
|
|
82
89
|
`API Error: ${error.error?.message || response.statusText} (${
|
|
83
90
|
error.error?.code || response.status
|
|
@@ -104,12 +111,42 @@ export class HttpClient {
|
|
|
104
111
|
});
|
|
105
112
|
|
|
106
113
|
if (!response.ok) {
|
|
107
|
-
const error = await response.json().catch(() => ({
|
|
114
|
+
const error = (await response.json().catch(() => ({
|
|
115
|
+
error: {
|
|
116
|
+
message: response.statusText,
|
|
117
|
+
code: response.status,
|
|
118
|
+
},
|
|
119
|
+
}))) as APIErrorResponse;
|
|
120
|
+
throw new Error(
|
|
121
|
+
`API Error: ${error.error?.message || response.statusText} (${
|
|
122
|
+
error.error?.code || response.status
|
|
123
|
+
})`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return response.json() as Promise<T>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Make a DELETE request
|
|
132
|
+
*/
|
|
133
|
+
async delete<T>(path: string): Promise<T> {
|
|
134
|
+
const url = `${this.baseURL}/${this.apiVersion}${path}`;
|
|
135
|
+
|
|
136
|
+
const response = await fetch(url, {
|
|
137
|
+
method: "DELETE",
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const error = (await response.json().catch(() => ({
|
|
108
145
|
error: {
|
|
109
146
|
message: response.statusText,
|
|
110
147
|
code: response.status,
|
|
111
148
|
},
|
|
112
|
-
}));
|
|
149
|
+
}))) as APIErrorResponse;
|
|
113
150
|
throw new Error(
|
|
114
151
|
`API Error: ${error.error?.message || response.statusText} (${
|
|
115
152
|
error.error?.code || response.status
|
|
@@ -4,6 +4,8 @@ import { HttpClient } from "./HttpClient";
|
|
|
4
4
|
import { MessagesService } from "../services/messages/index";
|
|
5
5
|
import { AccountsService } from "../services/accounts/index";
|
|
6
6
|
import { BusinessService } from "../services/business/index";
|
|
7
|
+
import { TemplatesService } from "../services/templates/index";
|
|
8
|
+
import { WebhooksService } from "../services/webhooks/index";
|
|
7
9
|
import { ZodError } from "zod";
|
|
8
10
|
import { transformZodError } from "../utils/zod-error";
|
|
9
11
|
import type { DebugTokenResponse } from "../types/debug";
|
|
@@ -15,6 +17,8 @@ export class WhatsAppClient {
|
|
|
15
17
|
public readonly messages: MessagesService;
|
|
16
18
|
public readonly accounts: AccountsService;
|
|
17
19
|
public readonly business: BusinessService;
|
|
20
|
+
public readonly templates: TemplatesService;
|
|
21
|
+
public readonly webhooks: WebhooksService;
|
|
18
22
|
|
|
19
23
|
private readonly httpClient: HttpClient;
|
|
20
24
|
|
|
@@ -37,6 +41,8 @@ export class WhatsAppClient {
|
|
|
37
41
|
this.messages = new MessagesService(this.httpClient);
|
|
38
42
|
this.accounts = new AccountsService(this.httpClient);
|
|
39
43
|
this.business = new BusinessService(this.httpClient);
|
|
44
|
+
this.templates = new TemplatesService(this.httpClient);
|
|
45
|
+
this.webhooks = new WebhooksService(this.httpClient);
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
/**
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { WhatsAppClient } from "../client";
|
|
3
|
+
|
|
4
|
+
const client = new WhatsAppClient({
|
|
5
|
+
accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
|
|
6
|
+
businessAccountId: process.env.WHATSAPP_BUSINESS_ACCOUNT_ID!,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
async function testTemplates() {
|
|
10
|
+
try {
|
|
11
|
+
console.log("🧪 Testing Templates API...\n");
|
|
12
|
+
|
|
13
|
+
// Test 1: Create a template
|
|
14
|
+
console.log("1️⃣ Creating template...");
|
|
15
|
+
const createResponse = await client.templates.create({
|
|
16
|
+
name: `test_template_${Date.now()}`, // Unique name to avoid conflicts
|
|
17
|
+
language: "en",
|
|
18
|
+
category: "UTILITY",
|
|
19
|
+
components: [
|
|
20
|
+
{
|
|
21
|
+
type: "BODY",
|
|
22
|
+
text: "Hello! This is a test template. Thank you for testing our WhatsApp Cloud API SDK.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "FOOTER",
|
|
26
|
+
text: "This is a test footer",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "BUTTONS",
|
|
30
|
+
buttons: [
|
|
31
|
+
{
|
|
32
|
+
type: "QUICK_REPLY",
|
|
33
|
+
text: "Get Started",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "QUICK_REPLY",
|
|
37
|
+
text: "Learn More",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log("✅ Template created successfully!");
|
|
45
|
+
console.log("Response:", JSON.stringify(createResponse, null, 2));
|
|
46
|
+
console.log("\n");
|
|
47
|
+
|
|
48
|
+
// Verify create response structure
|
|
49
|
+
if (!createResponse.id) {
|
|
50
|
+
throw new Error("Create response missing 'id' field");
|
|
51
|
+
}
|
|
52
|
+
if (!createResponse.status) {
|
|
53
|
+
throw new Error("Create response missing 'status' field");
|
|
54
|
+
}
|
|
55
|
+
if (!createResponse.category) {
|
|
56
|
+
throw new Error("Create response missing 'category' field");
|
|
57
|
+
}
|
|
58
|
+
console.log("✅ Create response structure verified\n");
|
|
59
|
+
|
|
60
|
+
// Test 2: Get template by ID
|
|
61
|
+
console.log("2️⃣ Getting template by ID...");
|
|
62
|
+
const templateId = createResponse.id;
|
|
63
|
+
const getResponse = await client.templates.get(templateId);
|
|
64
|
+
|
|
65
|
+
console.log("✅ Template retrieved successfully!");
|
|
66
|
+
console.log("Response:", JSON.stringify(getResponse, null, 2));
|
|
67
|
+
console.log("\n");
|
|
68
|
+
|
|
69
|
+
// Verify get response structure
|
|
70
|
+
if (!getResponse.id) {
|
|
71
|
+
throw new Error("Get response missing 'id' field");
|
|
72
|
+
}
|
|
73
|
+
if (!getResponse.name) {
|
|
74
|
+
throw new Error("Get response missing 'name' field");
|
|
75
|
+
}
|
|
76
|
+
if (!getResponse.language) {
|
|
77
|
+
throw new Error("Get response missing 'language' field");
|
|
78
|
+
}
|
|
79
|
+
if (!getResponse.status) {
|
|
80
|
+
throw new Error("Get response missing 'status' field");
|
|
81
|
+
}
|
|
82
|
+
if (!getResponse.category) {
|
|
83
|
+
throw new Error("Get response missing 'category' field");
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(getResponse.components)) {
|
|
86
|
+
throw new Error("Get response missing 'components' array");
|
|
87
|
+
}
|
|
88
|
+
console.log("✅ Get response structure verified\n");
|
|
89
|
+
|
|
90
|
+
// Verify components
|
|
91
|
+
const bodyComponent = getResponse.components.find((c) => c.type === "BODY");
|
|
92
|
+
if (!bodyComponent) {
|
|
93
|
+
throw new Error("BODY component not found in response");
|
|
94
|
+
}
|
|
95
|
+
if (bodyComponent.type !== "BODY") {
|
|
96
|
+
throw new Error("BODY component type mismatch");
|
|
97
|
+
}
|
|
98
|
+
console.log("✅ Components verified\n");
|
|
99
|
+
|
|
100
|
+
// Test 3: List templates
|
|
101
|
+
console.log("3️⃣ Listing templates...");
|
|
102
|
+
const listResponse = await client.templates.list();
|
|
103
|
+
console.log(`✅ Found ${listResponse.data.length} template(s)`);
|
|
104
|
+
console.log("Response:", JSON.stringify(listResponse, null, 2));
|
|
105
|
+
console.log("\n");
|
|
106
|
+
|
|
107
|
+
// Verify list response structure
|
|
108
|
+
if (!Array.isArray(listResponse.data)) {
|
|
109
|
+
throw new Error("List response missing 'data' array");
|
|
110
|
+
}
|
|
111
|
+
console.log("✅ List response structure verified\n");
|
|
112
|
+
|
|
113
|
+
// Verify our created template is in the list
|
|
114
|
+
const foundTemplate = listResponse.data.find((t) => t.id === templateId);
|
|
115
|
+
if (!foundTemplate) {
|
|
116
|
+
console.log(
|
|
117
|
+
"⚠️ Note: Created template may not appear in list immediately (async processing)"
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
console.log("✅ Created template found in list\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log("🎉 All tests passed!");
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("❌ Test failed:", error);
|
|
126
|
+
if (error instanceof Error) {
|
|
127
|
+
console.error("Error message:", error.message);
|
|
128
|
+
console.error("Error stack:", error.stack);
|
|
129
|
+
}
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
testTemplates();
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,13 @@ export * from "./schemas/index";
|
|
|
7
7
|
// Export types (primary export point)
|
|
8
8
|
export type * from "./types/index";
|
|
9
9
|
|
|
10
|
+
// Export webhook handler types (convenience exports)
|
|
11
|
+
export type {
|
|
12
|
+
MessageContext,
|
|
13
|
+
MessageHandlers,
|
|
14
|
+
HandleOptions,
|
|
15
|
+
} from "./services/webhooks/index";
|
|
16
|
+
|
|
10
17
|
// Export errors for error handling
|
|
11
18
|
export {
|
|
12
19
|
WhatsAppError,
|