whatsapp-cloud 0.0.4 → 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.
Files changed (37) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
  3. package/cloud-api-docs/webhooks/endpoint.md +112 -0
  4. package/cloud-api-docs/webhooks/overview.md +154 -0
  5. package/package.json +6 -2
  6. package/src/client/HttpClient.ts +43 -6
  7. package/src/client/WhatsAppClient.ts +3 -0
  8. package/src/examples/main.ts +9 -0
  9. package/src/examples/template.ts +134 -0
  10. package/src/schemas/client.ts +2 -2
  11. package/src/schemas/index.ts +1 -0
  12. package/src/schemas/templates/component.ts +145 -0
  13. package/src/schemas/templates/index.ts +4 -0
  14. package/src/schemas/templates/request.ts +78 -0
  15. package/src/schemas/templates/response.ts +64 -0
  16. package/src/services/accounts/AccountsClient.ts +6 -14
  17. package/src/services/accounts/AccountsService.ts +19 -21
  18. package/src/services/accounts/methods/list-phone-numbers.ts +1 -2
  19. package/src/services/business/BusinessClient.ts +1 -9
  20. package/src/services/business/BusinessService.ts +19 -21
  21. package/src/services/business/methods/list-accounts.ts +1 -2
  22. package/src/services/messages/MessagesClient.ts +2 -6
  23. package/src/services/messages/MessagesService.ts +42 -22
  24. package/src/services/templates/TemplatesClient.ts +35 -0
  25. package/src/services/templates/TemplatesService.ts +117 -0
  26. package/src/services/templates/index.ts +3 -0
  27. package/src/services/templates/methods/create.ts +27 -0
  28. package/src/services/templates/methods/delete.ts +38 -0
  29. package/src/services/templates/methods/get.ts +23 -0
  30. package/src/services/templates/methods/list.ts +36 -0
  31. package/src/services/templates/methods/update.ts +35 -0
  32. package/src/types/index.ts +1 -0
  33. package/src/types/templates/component.ts +33 -0
  34. package/src/types/templates/index.ts +4 -0
  35. package/src/types/templates/request.ts +28 -0
  36. package/src/types/templates/response.ts +34 -0
  37. 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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-cloud",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
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
  }
@@ -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,7 @@ 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";
7
8
  import { ZodError } from "zod";
8
9
  import { transformZodError } from "../utils/zod-error";
9
10
  import type { DebugTokenResponse } from "../types/debug";
@@ -15,6 +16,7 @@ export class WhatsAppClient {
15
16
  public readonly messages: MessagesService;
16
17
  public readonly accounts: AccountsService;
17
18
  public readonly business: BusinessService;
19
+ public readonly templates: TemplatesService;
18
20
 
19
21
  private readonly httpClient: HttpClient;
20
22
 
@@ -37,6 +39,7 @@ export class WhatsAppClient {
37
39
  this.messages = new MessagesService(this.httpClient);
38
40
  this.accounts = new AccountsService(this.httpClient);
39
41
  this.business = new BusinessService(this.httpClient);
42
+ this.templates = new TemplatesService(this.httpClient);
40
43
  }
41
44
 
42
45
  /**
@@ -0,0 +1,9 @@
1
+ import "dotenv/config";
2
+ import { WhatsAppClient } from "../client";
3
+
4
+ const client = new WhatsAppClient({
5
+ accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
6
+ });
7
+
8
+ const debugInfo = await client.debugToken();
9
+ console.log(debugInfo);
@@ -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();
@@ -44,7 +44,7 @@ export const clientConfigSchema = z.object({
44
44
  .refine((val) => val === undefined || val.trim().length > 0, {
45
45
  message: "businessId cannot be empty or whitespace only",
46
46
  }),
47
- apiVersion: z.string().default("v18.0"),
48
- baseURL: z.string().url().default("https://graph.facebook.com"),
47
+ apiVersion: z.string().default("v18.0").optional(),
48
+ baseURL: z.string().url().default("https://graph.facebook.com").optional(),
49
49
  timeout: z.number().positive().optional(),
50
50
  });
@@ -2,4 +2,5 @@ export * from "./client";
2
2
  export * from "./messages/index";
3
3
  export * from "./accounts/index";
4
4
  export * from "./business/index";
5
+ export * from "./templates/index";
5
6
  export * from "./debug";
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Button schemas for template components
5
+ * Simplified version without variables for now
6
+ */
7
+
8
+ /**
9
+ * Quick reply button schema
10
+ */
11
+ export const quickReplyButtonSchema = z.object({
12
+ type: z.literal("QUICK_REPLY"),
13
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
14
+ });
15
+
16
+ /**
17
+ * URL button schema
18
+ * Note: example field will be added later when we support variables
19
+ */
20
+ export const urlButtonSchema = z.object({
21
+ type: z.literal("URL"),
22
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
23
+ url: z.string().url().max(2000, "URL must be 2000 characters or less"),
24
+ // example: z.array(z.string()).optional(), // For later: when URL contains variables
25
+ });
26
+
27
+ /**
28
+ * Phone number button schema
29
+ */
30
+ export const phoneNumberButtonSchema = z.object({
31
+ type: z.literal("PHONE_NUMBER"),
32
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
33
+ phone_number: z
34
+ .string()
35
+ .min(1)
36
+ .max(20, "Phone number must be 20 characters or less"),
37
+ });
38
+
39
+ /**
40
+ * Copy code button schema
41
+ * Note: example field will be added later
42
+ */
43
+ export const copyCodeButtonSchema = z.object({
44
+ type: z.literal("COPY_CODE"),
45
+ // example: z.string().max(15).optional(), // For later: example value to copy
46
+ });
47
+
48
+ /**
49
+ * Flow button schema (for authentication templates)
50
+ * Note: Will be expanded later when we support flow templates
51
+ */
52
+ export const flowButtonSchema = z.object({
53
+ type: z.literal("FLOW"),
54
+ text: z.string().min(1).max(25, "Button text must be 25 characters or less"),
55
+ flow_action: z.string().optional(),
56
+ flow_id: z.string().optional(),
57
+ navigate_screen: z.string().optional(),
58
+ });
59
+
60
+ /**
61
+ * Union of all button types
62
+ */
63
+ export const buttonSchema = z.discriminatedUnion("type", [
64
+ quickReplyButtonSchema,
65
+ urlButtonSchema,
66
+ phoneNumberButtonSchema,
67
+ copyCodeButtonSchema,
68
+ flowButtonSchema,
69
+ ]);
70
+
71
+ /**
72
+ * Header component schema
73
+ * Simplified - no variables/examples for now
74
+ *
75
+ * Note:
76
+ * - TEXT format requires text field
77
+ * - IMAGE/VIDEO/DOCUMENT formats require example.header_handle (for later)
78
+ * - LOCATION format requires neither text nor example
79
+ */
80
+ export const headerComponentSchema = z
81
+ .object({
82
+ type: z.literal("HEADER"),
83
+ format: z.enum(["TEXT", "IMAGE", "VIDEO", "DOCUMENT", "LOCATION"]),
84
+ text: z
85
+ .string()
86
+ .max(60, "Header text must be 60 characters or less")
87
+ .optional(),
88
+ // example: z.object({...}).optional(), // For later: when using variables or media
89
+ })
90
+ .refine(
91
+ (data) => {
92
+ // TEXT format requires text
93
+ if (data.format === "TEXT" && !data.text) {
94
+ return false;
95
+ }
96
+ // LOCATION format doesn't need text
97
+ if (data.format === "LOCATION") {
98
+ return true;
99
+ }
100
+ // IMAGE/VIDEO/DOCUMENT will need example.header_handle (for later)
101
+ return true;
102
+ },
103
+ {
104
+ message: "TEXT format header requires text field",
105
+ }
106
+ );
107
+
108
+ /**
109
+ * Body component schema
110
+ * Required component - no variables for now
111
+ */
112
+ export const bodyComponentSchema = z.object({
113
+ type: z.literal("BODY"),
114
+ text: z
115
+ .string()
116
+ .min(1)
117
+ .max(1024, "Body text must be 1024 characters or less"),
118
+ // example: z.object({...}).optional(), // For later: when using variables
119
+ });
120
+
121
+ /**
122
+ * Footer component schema
123
+ */
124
+ export const footerComponentSchema = z.object({
125
+ type: z.literal("FOOTER"),
126
+ text: z.string().min(1).max(60, "Footer text must be 60 characters or less"),
127
+ });
128
+
129
+ /**
130
+ * Buttons component schema
131
+ */
132
+ export const buttonsComponentSchema = z.object({
133
+ type: z.literal("BUTTONS"),
134
+ buttons: z.array(buttonSchema).min(1).max(10, "Maximum 10 buttons allowed"),
135
+ });
136
+
137
+ /**
138
+ * Union of all component types
139
+ */
140
+ export const componentSchema = z.discriminatedUnion("type", [
141
+ headerComponentSchema,
142
+ bodyComponentSchema,
143
+ footerComponentSchema,
144
+ buttonsComponentSchema,
145
+ ]);
@@ -0,0 +1,4 @@
1
+ export * from "./component";
2
+ export * from "./request";
3
+ export * from "./response";
4
+
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+ import { componentSchema } from "./component";
3
+
4
+ /**
5
+ * Schema for creating a template
6
+ * Simplified - no variables/examples for now
7
+ */
8
+ export const createTemplateRequestSchema = z.object({
9
+ name: z.string().min(1).max(512, "Template name must be 512 characters or less"),
10
+ language: z.string().min(2).max(5, "Language code must be 2-5 characters (e.g., 'en' or 'en_US')"),
11
+ category: z.enum(["AUTHENTICATION", "MARKETING", "UTILITY"]),
12
+ components: z
13
+ .array(componentSchema)
14
+ .min(1, "At least one component is required")
15
+ .refine(
16
+ (components) => {
17
+ // Body component is required
18
+ return components.some((c) => c.type === "BODY");
19
+ },
20
+ { message: "BODY component is required" }
21
+ )
22
+ .refine(
23
+ (components) => {
24
+ // Only one header allowed
25
+ const headers = components.filter((c) => c.type === "HEADER");
26
+ return headers.length <= 1;
27
+ },
28
+ { message: "Only one HEADER component is allowed" }
29
+ )
30
+ .refine(
31
+ (components) => {
32
+ // Only one footer allowed
33
+ const footers = components.filter((c) => c.type === "FOOTER");
34
+ return footers.length <= 1;
35
+ },
36
+ { message: "Only one FOOTER component is allowed" }
37
+ )
38
+ .refine(
39
+ (components) => {
40
+ // Only one buttons component allowed
41
+ const buttons = components.filter((c) => c.type === "BUTTONS");
42
+ return buttons.length <= 1;
43
+ },
44
+ { message: "Only one BUTTONS component is allowed" }
45
+ ),
46
+ });
47
+
48
+ /**
49
+ * Schema for updating a template
50
+ * All fields optional - only update what's provided
51
+ */
52
+ export const updateTemplateRequestSchema = z.object({
53
+ category: z.enum(["AUTHENTICATION", "MARKETING", "UTILITY"]).optional(),
54
+ components: z.array(componentSchema).optional(),
55
+ language: z.string().min(2).max(5).optional(),
56
+ name: z.string().min(1).max(512).optional(),
57
+ });
58
+
59
+ /**
60
+ * Schema for listing templates
61
+ */
62
+ export const listTemplatesRequestSchema = z.object({
63
+ name: z.string().optional(), // Filter by template name
64
+ });
65
+
66
+ /**
67
+ * Schema for deleting a template
68
+ * Either name or hsm_id must be provided
69
+ */
70
+ export const deleteTemplateRequestSchema = z
71
+ .object({
72
+ name: z.string().optional(),
73
+ hsm_id: z.string().optional(),
74
+ })
75
+ .refine((data) => data.name || data.hsm_id, {
76
+ message: "Either name or hsm_id must be provided",
77
+ });
78
+