@dojocoding/whatsapp-sdk 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +402 -0
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/adapters/express/index.cjs +114 -0
- package/dist/adapters/express/index.d.cts +42 -0
- package/dist/adapters/express/index.d.ts +42 -0
- package/dist/adapters/express/index.js +108 -0
- package/dist/adapters/hono/index.cjs +52 -0
- package/dist/adapters/hono/index.d.cts +38 -0
- package/dist/adapters/hono/index.d.ts +38 -0
- package/dist/adapters/hono/index.js +50 -0
- package/dist/adapters/web/index.cjs +46 -0
- package/dist/adapters/web/index.d.cts +40 -0
- package/dist/adapters/web/index.d.ts +40 -0
- package/dist/adapters/web/index.js +44 -0
- package/dist/index-CDfzGvQJ.d.cts +42 -0
- package/dist/index-CDfzGvQJ.d.ts +42 -0
- package/dist/index.cjs +2242 -0
- package/dist/index.d.cts +1262 -0
- package/dist/index.d.ts +1262 -0
- package/dist/index.js +2183 -0
- package/dist/receiver-C_yfwg6g.d.ts +167 -0
- package/dist/receiver-DWJm571Z.d.cts +167 -0
- package/dist/storage/postgres.cjs +66 -0
- package/dist/storage/postgres.d.cts +38 -0
- package/dist/storage/postgres.d.ts +38 -0
- package/dist/storage/postgres.js +63 -0
- package/dist/storage/redis.cjs +32 -0
- package/dist/storage/redis.d.cts +38 -0
- package/dist/storage/redis.d.ts +38 -0
- package/dist/storage/redis.js +30 -0
- package/package.json +181 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1262 @@
|
|
|
1
|
+
import { S as Storage } from './index-CDfzGvQJ.cjs';
|
|
2
|
+
export { I as InMemoryStorage } from './index-CDfzGvQJ.cjs';
|
|
3
|
+
import { W as WebhookReceiver, a as WhatsAppEvent } from './receiver-DWJm571Z.cjs';
|
|
4
|
+
export { A as AccountAlertEvent, b as AccountReviewEvent, B as BaseEvent, D as DeliveryStatus, E as ErrorHandler, c as EventKindMap, H as HandlePayloadResult, d as Handler, I as IncomingMessageKind, M as MessageEvent, P as PhoneNumberQualityUpdateEvent, S as StatusEvent, T as TemplateCategoryUpdateEvent, e as TemplateQualityUpdateEvent, f as TemplateStatusEvent, U as UnknownEvent, V as VerifyRequestInput, g as VerifyRequestResult, h as WebhookReceiverOptions } from './receiver-DWJm571Z.cjs';
|
|
5
|
+
import { Tracer, Attributes } from '@opentelemetry/api';
|
|
6
|
+
|
|
7
|
+
type TemplateCategory = "MARKETING" | "UTILITY" | "AUTHENTICATION" | (string & {});
|
|
8
|
+
type TemplateStatus = "APPROVED" | "PENDING" | "REJECTED" | "PAUSED" | "DISABLED" | "FLAGGED" | (string & {});
|
|
9
|
+
type TemplateComponentDefinitionType = "HEADER" | "BODY" | "FOOTER" | "BUTTONS" | (string & {});
|
|
10
|
+
interface TemplateButtonDefinition {
|
|
11
|
+
type: "QUICK_REPLY" | "URL" | "PHONE_NUMBER" | "COPY_CODE" | "OTP" | (string & {});
|
|
12
|
+
text?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
phone_number?: string;
|
|
15
|
+
example?: ReadonlyArray<string>;
|
|
16
|
+
}
|
|
17
|
+
interface TemplateComponentDefinition {
|
|
18
|
+
type: TemplateComponentDefinitionType;
|
|
19
|
+
/** Header format ("TEXT" | "IMAGE" | "VIDEO" | "DOCUMENT" | "LOCATION"). */
|
|
20
|
+
format?: string;
|
|
21
|
+
/** The body / header text containing `{{1}}`, `{{2}}`, … placeholders. */
|
|
22
|
+
text?: string;
|
|
23
|
+
/** Example values Meta shows in the editor. */
|
|
24
|
+
example?: {
|
|
25
|
+
body_text?: ReadonlyArray<ReadonlyArray<string>>;
|
|
26
|
+
header_text?: ReadonlyArray<string>;
|
|
27
|
+
header_handle?: ReadonlyArray<string>;
|
|
28
|
+
};
|
|
29
|
+
/** Buttons component lists buttons here (capitalised in the API). */
|
|
30
|
+
buttons?: ReadonlyArray<TemplateButtonDefinition>;
|
|
31
|
+
}
|
|
32
|
+
interface TemplateDefinition {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
language: string;
|
|
36
|
+
category: TemplateCategory;
|
|
37
|
+
status: TemplateStatus;
|
|
38
|
+
components: ReadonlyArray<TemplateComponentDefinition>;
|
|
39
|
+
quality_score?: {
|
|
40
|
+
score: string;
|
|
41
|
+
date?: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
interface ListTemplatesQuery {
|
|
45
|
+
name?: string;
|
|
46
|
+
language?: string;
|
|
47
|
+
status?: TemplateStatus;
|
|
48
|
+
category?: TemplateCategory;
|
|
49
|
+
limit?: number;
|
|
50
|
+
/** Cursor pagination forward. */
|
|
51
|
+
after?: string;
|
|
52
|
+
/** Cursor pagination backward. */
|
|
53
|
+
before?: string;
|
|
54
|
+
}
|
|
55
|
+
interface ListTemplatesPaging {
|
|
56
|
+
cursors?: {
|
|
57
|
+
after?: string;
|
|
58
|
+
before?: string;
|
|
59
|
+
};
|
|
60
|
+
next?: string;
|
|
61
|
+
previous?: string;
|
|
62
|
+
}
|
|
63
|
+
interface ListTemplatesResponse {
|
|
64
|
+
data: ReadonlyArray<TemplateDefinition>;
|
|
65
|
+
paging?: ListTemplatesPaging;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type RecipientType = "individual";
|
|
69
|
+
interface BaseMessage {
|
|
70
|
+
messaging_product: "whatsapp";
|
|
71
|
+
recipient_type: RecipientType;
|
|
72
|
+
to: string;
|
|
73
|
+
context?: {
|
|
74
|
+
message_id: string;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
interface TextBody {
|
|
78
|
+
body: string;
|
|
79
|
+
preview_url?: boolean;
|
|
80
|
+
}
|
|
81
|
+
interface TextMessage extends BaseMessage {
|
|
82
|
+
type: "text";
|
|
83
|
+
text: TextBody;
|
|
84
|
+
}
|
|
85
|
+
interface MediaSource {
|
|
86
|
+
/** Pre-uploaded media id from `POST /{phone-number-id}/media`. */
|
|
87
|
+
id?: string;
|
|
88
|
+
/** Public URL Meta will fetch on send. */
|
|
89
|
+
link?: string;
|
|
90
|
+
/** Image / video / document caption. Not used for audio / sticker. */
|
|
91
|
+
caption?: string;
|
|
92
|
+
/** Document filename hint. Not used for image / video / audio / sticker. */
|
|
93
|
+
filename?: string;
|
|
94
|
+
}
|
|
95
|
+
type MediaKind = "image" | "video" | "audio" | "document" | "sticker";
|
|
96
|
+
interface ImageMessage extends BaseMessage {
|
|
97
|
+
type: "image";
|
|
98
|
+
image: Omit<MediaSource, "filename">;
|
|
99
|
+
}
|
|
100
|
+
interface VideoMessage extends BaseMessage {
|
|
101
|
+
type: "video";
|
|
102
|
+
video: Omit<MediaSource, "filename">;
|
|
103
|
+
}
|
|
104
|
+
interface AudioMessage extends BaseMessage {
|
|
105
|
+
type: "audio";
|
|
106
|
+
/**
|
|
107
|
+
* Audio body. Setting `voice: true` makes the WhatsApp client render
|
|
108
|
+
* the message as a push-to-talk voice note (with transcription
|
|
109
|
+
* support, auto-download, and a "played" status when the recipient
|
|
110
|
+
* listens) instead of a music file. Source:
|
|
111
|
+
* https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/audio-messages
|
|
112
|
+
*/
|
|
113
|
+
audio: Pick<MediaSource, "id" | "link"> & {
|
|
114
|
+
voice?: boolean;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
interface DocumentMessage extends BaseMessage {
|
|
118
|
+
type: "document";
|
|
119
|
+
document: MediaSource;
|
|
120
|
+
}
|
|
121
|
+
interface StickerMessage extends BaseMessage {
|
|
122
|
+
type: "sticker";
|
|
123
|
+
sticker: Pick<MediaSource, "id" | "link">;
|
|
124
|
+
}
|
|
125
|
+
interface LocationBody {
|
|
126
|
+
latitude: number;
|
|
127
|
+
longitude: number;
|
|
128
|
+
name?: string;
|
|
129
|
+
address?: string;
|
|
130
|
+
}
|
|
131
|
+
interface LocationMessage extends BaseMessage {
|
|
132
|
+
type: "location";
|
|
133
|
+
location: LocationBody;
|
|
134
|
+
}
|
|
135
|
+
interface ContactName {
|
|
136
|
+
formatted_name: string;
|
|
137
|
+
first_name?: string;
|
|
138
|
+
last_name?: string;
|
|
139
|
+
middle_name?: string;
|
|
140
|
+
prefix?: string;
|
|
141
|
+
suffix?: string;
|
|
142
|
+
}
|
|
143
|
+
interface ContactPhone {
|
|
144
|
+
phone: string;
|
|
145
|
+
type?: "HOME" | "WORK" | "CELL" | "MAIN" | "IPHONE";
|
|
146
|
+
wa_id?: string;
|
|
147
|
+
}
|
|
148
|
+
interface ContactEmail {
|
|
149
|
+
email: string;
|
|
150
|
+
type?: "HOME" | "WORK";
|
|
151
|
+
}
|
|
152
|
+
interface Contact {
|
|
153
|
+
name: ContactName;
|
|
154
|
+
phones?: ReadonlyArray<ContactPhone>;
|
|
155
|
+
emails?: ReadonlyArray<ContactEmail>;
|
|
156
|
+
org?: {
|
|
157
|
+
company?: string;
|
|
158
|
+
department?: string;
|
|
159
|
+
title?: string;
|
|
160
|
+
};
|
|
161
|
+
birthday?: string;
|
|
162
|
+
}
|
|
163
|
+
interface ContactsMessage extends BaseMessage {
|
|
164
|
+
type: "contacts";
|
|
165
|
+
contacts: ReadonlyArray<Contact>;
|
|
166
|
+
}
|
|
167
|
+
interface InteractiveHeaderText {
|
|
168
|
+
type: "text";
|
|
169
|
+
text: string;
|
|
170
|
+
}
|
|
171
|
+
interface InteractiveHeaderImage {
|
|
172
|
+
type: "image";
|
|
173
|
+
image: {
|
|
174
|
+
id?: string;
|
|
175
|
+
link?: string;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
interface InteractiveHeaderVideo {
|
|
179
|
+
type: "video";
|
|
180
|
+
video: {
|
|
181
|
+
id?: string;
|
|
182
|
+
link?: string;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
interface InteractiveHeaderDocument {
|
|
186
|
+
type: "document";
|
|
187
|
+
document: {
|
|
188
|
+
id?: string;
|
|
189
|
+
link?: string;
|
|
190
|
+
filename?: string;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
type InteractiveHeader = InteractiveHeaderText | InteractiveHeaderImage | InteractiveHeaderVideo | InteractiveHeaderDocument;
|
|
194
|
+
interface InteractiveButtonReply {
|
|
195
|
+
type: "reply";
|
|
196
|
+
reply: {
|
|
197
|
+
id: string;
|
|
198
|
+
title: string;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
interface InteractiveButtonAction {
|
|
202
|
+
buttons: ReadonlyArray<InteractiveButtonReply>;
|
|
203
|
+
}
|
|
204
|
+
interface InteractiveListRow {
|
|
205
|
+
id: string;
|
|
206
|
+
title: string;
|
|
207
|
+
description?: string;
|
|
208
|
+
}
|
|
209
|
+
interface InteractiveListSection {
|
|
210
|
+
title: string;
|
|
211
|
+
rows: ReadonlyArray<InteractiveListRow>;
|
|
212
|
+
}
|
|
213
|
+
interface InteractiveListAction {
|
|
214
|
+
button: string;
|
|
215
|
+
sections: ReadonlyArray<InteractiveListSection>;
|
|
216
|
+
}
|
|
217
|
+
interface InteractiveCtaUrlAction {
|
|
218
|
+
name: "cta_url";
|
|
219
|
+
parameters: {
|
|
220
|
+
display_text: string;
|
|
221
|
+
url: string;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
interface InteractiveButtonBody {
|
|
225
|
+
type: "button";
|
|
226
|
+
header?: InteractiveHeader;
|
|
227
|
+
body: {
|
|
228
|
+
text: string;
|
|
229
|
+
};
|
|
230
|
+
footer?: {
|
|
231
|
+
text: string;
|
|
232
|
+
};
|
|
233
|
+
action: InteractiveButtonAction;
|
|
234
|
+
}
|
|
235
|
+
interface InteractiveListBody {
|
|
236
|
+
type: "list";
|
|
237
|
+
header?: InteractiveHeaderText;
|
|
238
|
+
body: {
|
|
239
|
+
text: string;
|
|
240
|
+
};
|
|
241
|
+
footer?: {
|
|
242
|
+
text: string;
|
|
243
|
+
};
|
|
244
|
+
action: InteractiveListAction;
|
|
245
|
+
}
|
|
246
|
+
interface InteractiveCtaUrlBody {
|
|
247
|
+
type: "cta_url";
|
|
248
|
+
header?: InteractiveHeader;
|
|
249
|
+
body: {
|
|
250
|
+
text: string;
|
|
251
|
+
};
|
|
252
|
+
footer?: {
|
|
253
|
+
text: string;
|
|
254
|
+
};
|
|
255
|
+
action: InteractiveCtaUrlAction;
|
|
256
|
+
}
|
|
257
|
+
type InteractiveBody = InteractiveButtonBody | InteractiveListBody | InteractiveCtaUrlBody;
|
|
258
|
+
interface InteractiveMessage extends BaseMessage {
|
|
259
|
+
type: "interactive";
|
|
260
|
+
interactive: InteractiveBody;
|
|
261
|
+
}
|
|
262
|
+
interface TemplateLanguage {
|
|
263
|
+
code: string;
|
|
264
|
+
policy?: "deterministic";
|
|
265
|
+
}
|
|
266
|
+
interface TemplateParameterText {
|
|
267
|
+
type: "text";
|
|
268
|
+
text: string;
|
|
269
|
+
}
|
|
270
|
+
interface TemplateParameterCurrency {
|
|
271
|
+
type: "currency";
|
|
272
|
+
currency: {
|
|
273
|
+
fallback_value: string;
|
|
274
|
+
code: string;
|
|
275
|
+
amount_1000: number;
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
interface TemplateParameterDateTime {
|
|
279
|
+
type: "date_time";
|
|
280
|
+
date_time: {
|
|
281
|
+
fallback_value: string;
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
interface TemplateParameterImage {
|
|
285
|
+
type: "image";
|
|
286
|
+
image: {
|
|
287
|
+
id?: string;
|
|
288
|
+
link?: string;
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
interface TemplateParameterVideo {
|
|
292
|
+
type: "video";
|
|
293
|
+
video: {
|
|
294
|
+
id?: string;
|
|
295
|
+
link?: string;
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
interface TemplateParameterDocument {
|
|
299
|
+
type: "document";
|
|
300
|
+
document: {
|
|
301
|
+
id?: string;
|
|
302
|
+
link?: string;
|
|
303
|
+
filename?: string;
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Limited-time-offer parameter (paired with a `type: "limited_time_offer"`
|
|
308
|
+
* component). `expiration_time_ms` is Unix epoch milliseconds. Source:
|
|
309
|
+
* https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/marketing-templates/limited-time-offer-templates/
|
|
310
|
+
*/
|
|
311
|
+
interface TemplateParameterLimitedTimeOffer {
|
|
312
|
+
type: "limited_time_offer";
|
|
313
|
+
limited_time_offer: {
|
|
314
|
+
expiration_time_ms: number;
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Coupon-code parameter (paired with a `sub_type: "copy_code"` button).
|
|
319
|
+
* Source: same Meta doc as `TemplateParameterLimitedTimeOffer`.
|
|
320
|
+
*/
|
|
321
|
+
interface TemplateParameterCouponCode {
|
|
322
|
+
type: "coupon_code";
|
|
323
|
+
coupon_code: string;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Payload parameter for `sub_type: "quick_reply"` buttons (used inside
|
|
327
|
+
* carousel cards). Source:
|
|
328
|
+
* https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/marketing-templates/media-card-carousel-templates/
|
|
329
|
+
*/
|
|
330
|
+
interface TemplateParameterPayload {
|
|
331
|
+
type: "payload";
|
|
332
|
+
payload: string;
|
|
333
|
+
}
|
|
334
|
+
type TemplateParameter = TemplateParameterText | TemplateParameterCurrency | TemplateParameterDateTime | TemplateParameterImage | TemplateParameterVideo | TemplateParameterDocument | TemplateParameterLimitedTimeOffer | TemplateParameterCouponCode | TemplateParameterPayload;
|
|
335
|
+
/**
|
|
336
|
+
* A single carousel card. The `card_index` is 0-based and assigned by
|
|
337
|
+
* the builder, never by the caller. Per Meta's docs the card's
|
|
338
|
+
* `components` array contains a required `header` plus optional
|
|
339
|
+
* `body` and `button` sub-components.
|
|
340
|
+
*/
|
|
341
|
+
interface CarouselCardComponent {
|
|
342
|
+
card_index: number;
|
|
343
|
+
components: ReadonlyArray<TemplateComponent>;
|
|
344
|
+
}
|
|
345
|
+
interface TemplateComponent {
|
|
346
|
+
type: "header" | "body" | "button" | "footer" | "carousel" | "limited_time_offer";
|
|
347
|
+
sub_type?: "quick_reply" | "url" | "copy_code";
|
|
348
|
+
/**
|
|
349
|
+
* Meta's docs use a string for auth-template buttons (`"0"`) and a
|
|
350
|
+
* number for carousel-card buttons (`0`). Both work at the API; we
|
|
351
|
+
* mirror what Meta publishes so reviewers can diff without mental
|
|
352
|
+
* conversion.
|
|
353
|
+
*/
|
|
354
|
+
index?: string | number;
|
|
355
|
+
parameters?: ReadonlyArray<TemplateParameter>;
|
|
356
|
+
/** Only set when `type === "carousel"`. */
|
|
357
|
+
cards?: ReadonlyArray<CarouselCardComponent>;
|
|
358
|
+
}
|
|
359
|
+
interface TemplateBody {
|
|
360
|
+
name: string;
|
|
361
|
+
language: TemplateLanguage;
|
|
362
|
+
components?: ReadonlyArray<TemplateComponent>;
|
|
363
|
+
}
|
|
364
|
+
interface TemplateMessage extends BaseMessage {
|
|
365
|
+
type: "template";
|
|
366
|
+
template: TemplateBody;
|
|
367
|
+
}
|
|
368
|
+
interface ReactionBody {
|
|
369
|
+
message_id: string;
|
|
370
|
+
emoji: string;
|
|
371
|
+
}
|
|
372
|
+
interface ReactionMessage extends BaseMessage {
|
|
373
|
+
type: "reaction";
|
|
374
|
+
reaction: ReactionBody;
|
|
375
|
+
}
|
|
376
|
+
type WhatsAppMessage = TextMessage | ImageMessage | VideoMessage | AudioMessage | DocumentMessage | StickerMessage | LocationMessage | ContactsMessage | InteractiveMessage | TemplateMessage | ReactionMessage;
|
|
377
|
+
interface MessageSendResponseContact {
|
|
378
|
+
input: string;
|
|
379
|
+
wa_id: string;
|
|
380
|
+
}
|
|
381
|
+
interface MessageSendResponseMessage {
|
|
382
|
+
id: string;
|
|
383
|
+
message_status?: string;
|
|
384
|
+
}
|
|
385
|
+
interface MessageSendResponse {
|
|
386
|
+
messaging_product: "whatsapp";
|
|
387
|
+
contacts: ReadonlyArray<MessageSendResponseContact>;
|
|
388
|
+
messages: ReadonlyArray<MessageSendResponseMessage>;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
interface BuildTextInput {
|
|
392
|
+
to: string;
|
|
393
|
+
body: string;
|
|
394
|
+
previewUrl?: boolean;
|
|
395
|
+
replyTo?: string;
|
|
396
|
+
}
|
|
397
|
+
declare function buildText(input: BuildTextInput): TextMessage;
|
|
398
|
+
interface BuildMediaInput {
|
|
399
|
+
to: string;
|
|
400
|
+
id?: string;
|
|
401
|
+
link?: string;
|
|
402
|
+
caption?: string;
|
|
403
|
+
filename?: string;
|
|
404
|
+
replyTo?: string;
|
|
405
|
+
}
|
|
406
|
+
declare function buildImage(input: BuildMediaInput): ImageMessage;
|
|
407
|
+
declare function buildVideo(input: BuildMediaInput): VideoMessage;
|
|
408
|
+
declare function buildAudio(input: BuildMediaInput): AudioMessage;
|
|
409
|
+
declare function buildDocument(input: BuildMediaInput): DocumentMessage;
|
|
410
|
+
declare function buildSticker(input: BuildMediaInput): StickerMessage;
|
|
411
|
+
interface BuildLocationInput {
|
|
412
|
+
to: string;
|
|
413
|
+
latitude: number;
|
|
414
|
+
longitude: number;
|
|
415
|
+
name?: string;
|
|
416
|
+
address?: string;
|
|
417
|
+
replyTo?: string;
|
|
418
|
+
}
|
|
419
|
+
declare function buildLocation(input: BuildLocationInput): LocationMessage;
|
|
420
|
+
interface BuildContactsInput {
|
|
421
|
+
to: string;
|
|
422
|
+
contacts: Contact | ReadonlyArray<Contact>;
|
|
423
|
+
replyTo?: string;
|
|
424
|
+
}
|
|
425
|
+
declare function buildContacts(input: BuildContactsInput): ContactsMessage;
|
|
426
|
+
interface BuildInteractiveButtonInput {
|
|
427
|
+
to: string;
|
|
428
|
+
header?: InteractiveHeader;
|
|
429
|
+
body: string;
|
|
430
|
+
footer?: string;
|
|
431
|
+
buttons: ReadonlyArray<{
|
|
432
|
+
id: string;
|
|
433
|
+
title: string;
|
|
434
|
+
}>;
|
|
435
|
+
replyTo?: string;
|
|
436
|
+
}
|
|
437
|
+
declare function buildInteractiveButton(input: BuildInteractiveButtonInput): InteractiveMessage;
|
|
438
|
+
interface BuildInteractiveListInput {
|
|
439
|
+
to: string;
|
|
440
|
+
header?: {
|
|
441
|
+
type: "text";
|
|
442
|
+
text: string;
|
|
443
|
+
};
|
|
444
|
+
body: string;
|
|
445
|
+
footer?: string;
|
|
446
|
+
button: string;
|
|
447
|
+
sections: ReadonlyArray<InteractiveListSection>;
|
|
448
|
+
replyTo?: string;
|
|
449
|
+
}
|
|
450
|
+
declare function buildInteractiveList(input: BuildInteractiveListInput): InteractiveMessage;
|
|
451
|
+
interface BuildInteractiveCtaUrlInput {
|
|
452
|
+
to: string;
|
|
453
|
+
header?: InteractiveHeader;
|
|
454
|
+
body: string;
|
|
455
|
+
footer?: string;
|
|
456
|
+
cta: {
|
|
457
|
+
displayText: string;
|
|
458
|
+
url: string;
|
|
459
|
+
};
|
|
460
|
+
replyTo?: string;
|
|
461
|
+
}
|
|
462
|
+
declare function buildInteractiveCtaUrl(input: BuildInteractiveCtaUrlInput): InteractiveMessage;
|
|
463
|
+
type BuildInteractiveInput = ({
|
|
464
|
+
kind: "button";
|
|
465
|
+
} & BuildInteractiveButtonInput) | ({
|
|
466
|
+
kind: "list";
|
|
467
|
+
} & BuildInteractiveListInput) | ({
|
|
468
|
+
kind: "cta_url";
|
|
469
|
+
} & BuildInteractiveCtaUrlInput);
|
|
470
|
+
declare function buildInteractive(input: BuildInteractiveInput): InteractiveMessage;
|
|
471
|
+
interface BuildTemplateInput {
|
|
472
|
+
to: string;
|
|
473
|
+
name: string;
|
|
474
|
+
language: string;
|
|
475
|
+
components?: ReadonlyArray<TemplateComponent>;
|
|
476
|
+
replyTo?: string;
|
|
477
|
+
/**
|
|
478
|
+
* Optional approved-template definition to cross-validate against
|
|
479
|
+
* before building. When supplied, parameter counts and component
|
|
480
|
+
* shapes are checked locally and `TemplateError` is thrown on
|
|
481
|
+
* mismatch, avoiding the wasted HTTP round-trip to Meta.
|
|
482
|
+
*/
|
|
483
|
+
validateAgainst?: TemplateDefinition;
|
|
484
|
+
}
|
|
485
|
+
declare function buildTemplate(input: BuildTemplateInput): TemplateMessage;
|
|
486
|
+
interface BuildReactionInput {
|
|
487
|
+
to: string;
|
|
488
|
+
messageId: string;
|
|
489
|
+
/** Empty string clears a previously set reaction. */
|
|
490
|
+
emoji: string;
|
|
491
|
+
replyTo?: string;
|
|
492
|
+
}
|
|
493
|
+
declare function buildReaction(input: BuildReactionInput): ReactionMessage;
|
|
494
|
+
interface BuildAuthTemplateInput {
|
|
495
|
+
to: string;
|
|
496
|
+
/** Template name (case-sensitive, must exist as APPROVED in your WABA). */
|
|
497
|
+
name: string;
|
|
498
|
+
/** BCP-47 language code — must match the approved template's language. */
|
|
499
|
+
language: string;
|
|
500
|
+
/**
|
|
501
|
+
* The one-time password / verification code. Meta caps this at 15
|
|
502
|
+
* characters. Appears in BOTH the body and URL-button parameters of
|
|
503
|
+
* the documented wire payload — the builder duplicates it for you.
|
|
504
|
+
*/
|
|
505
|
+
otp: string;
|
|
506
|
+
/**
|
|
507
|
+
* Index of the URL button on the approved template. Defaults to `"0"`
|
|
508
|
+
* (matches Meta's documented example). Override only if your approved
|
|
509
|
+
* template has the OTP button at a non-zero position.
|
|
510
|
+
*/
|
|
511
|
+
otpButtonIndex?: string | number;
|
|
512
|
+
replyTo?: string;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Build an authentication-template (OTP) send payload. The wire shape
|
|
516
|
+
* matches Meta's documented copy-code button authentication template
|
|
517
|
+
* payload at
|
|
518
|
+
* https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/authentication-templates/copy-code-button-authentication-templates/.
|
|
519
|
+
*
|
|
520
|
+
* The same wire shape applies to one-tap autofill and zero-tap
|
|
521
|
+
* authentication templates — the subtype distinction lives at template
|
|
522
|
+
* creation time (Meta Business Manager / the templates API), not at
|
|
523
|
+
* send time.
|
|
524
|
+
*
|
|
525
|
+
* The OTP code appears in BOTH the body component's `text` parameter
|
|
526
|
+
* AND the URL button component's `text` parameter. This is Meta's
|
|
527
|
+
* documented requirement, not an SDK quirk; the builder duplicates the
|
|
528
|
+
* code so consumers don't have to remember to pass it twice.
|
|
529
|
+
*/
|
|
530
|
+
declare function buildAuthTemplate(input: BuildAuthTemplateInput): TemplateMessage;
|
|
531
|
+
interface BuildVoiceInput {
|
|
532
|
+
to: string;
|
|
533
|
+
/** Pre-uploaded media id from `POST /{phone-number-id}/media`. Exactly one of `id` / `link` must be supplied. */
|
|
534
|
+
id?: string;
|
|
535
|
+
/** Public URL Meta will fetch on send. Exactly one of `id` / `link` must be supplied. */
|
|
536
|
+
link?: string;
|
|
537
|
+
replyTo?: string;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Build a voice-note send payload — an audio message with the
|
|
541
|
+
* `voice: true` flag set. Source:
|
|
542
|
+
* https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/audio-messages
|
|
543
|
+
*
|
|
544
|
+
* Setting `voice: true` triggers transcription support, auto-download,
|
|
545
|
+
* and a "played" delivery status when the recipient listens. Without
|
|
546
|
+
* the flag the same media renders as a regular audio file with the
|
|
547
|
+
* music-player UI.
|
|
548
|
+
*
|
|
549
|
+
* The media payload (`.ogg` with OPUS codec is recommended) is supplied
|
|
550
|
+
* the same way as for `buildAudio` — either a pre-uploaded media id or
|
|
551
|
+
* a public link.
|
|
552
|
+
*/
|
|
553
|
+
declare function buildVoice(input: BuildVoiceInput): AudioMessage;
|
|
554
|
+
interface CarouselCardMediaHeader {
|
|
555
|
+
type: "image" | "video";
|
|
556
|
+
/** Pre-uploaded media id. Exactly one of `mediaId` / `link` must be supplied. */
|
|
557
|
+
mediaId?: string;
|
|
558
|
+
/** Public link. Exactly one of `mediaId` / `link` must be supplied. */
|
|
559
|
+
link?: string;
|
|
560
|
+
}
|
|
561
|
+
type CarouselCardButton = {
|
|
562
|
+
subType: "quick_reply";
|
|
563
|
+
payload: string;
|
|
564
|
+
} | {
|
|
565
|
+
subType: "url";
|
|
566
|
+
text: string;
|
|
567
|
+
};
|
|
568
|
+
interface CarouselCard {
|
|
569
|
+
/** Image or video header for this card (required by Meta). */
|
|
570
|
+
header: CarouselCardMediaHeader;
|
|
571
|
+
/** Body-text variable substitutions for this card's approved-template body. */
|
|
572
|
+
bodyParameters?: ReadonlyArray<string>;
|
|
573
|
+
/**
|
|
574
|
+
* Up to two buttons per card (Meta cap). Order matters — the index
|
|
575
|
+
* in the wire payload is the position in this array.
|
|
576
|
+
*/
|
|
577
|
+
buttons?: ReadonlyArray<CarouselCardButton>;
|
|
578
|
+
}
|
|
579
|
+
interface BuildCarouselTemplateInput {
|
|
580
|
+
to: string;
|
|
581
|
+
name: string;
|
|
582
|
+
language: string;
|
|
583
|
+
/** Top-level body-text variable substitutions for the carousel's leading body component. */
|
|
584
|
+
bodyParameters?: ReadonlyArray<string>;
|
|
585
|
+
cards: ReadonlyArray<CarouselCard>;
|
|
586
|
+
replyTo?: string;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Build a media-card carousel template send payload. The wire shape
|
|
590
|
+
* matches Meta's documented payload at
|
|
591
|
+
* https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/marketing-templates/media-card-carousel-templates/.
|
|
592
|
+
*
|
|
593
|
+
* Cards are bounded to 1–10 per Meta's documented maximum. Each card's
|
|
594
|
+
* `card_index` is computed from its position in the input array so
|
|
595
|
+
* consumers cannot misorder it.
|
|
596
|
+
*/
|
|
597
|
+
declare function buildCarouselTemplate(input: BuildCarouselTemplateInput): TemplateMessage;
|
|
598
|
+
|
|
599
|
+
declare const GRAPH_API_VERSION: "v25.0";
|
|
600
|
+
declare const META_GRAPH_BASE_URL: "https://graph.facebook.com";
|
|
601
|
+
declare const WEBHOOK_ACK_DEADLINE_MS: 30000;
|
|
602
|
+
declare const WINDOW_TTL_MS: number;
|
|
603
|
+
declare const WEBHOOK_DEDUPE_TTL_MS: number;
|
|
604
|
+
type GraphApiVersion = typeof GRAPH_API_VERSION | `v${number}.${number}`;
|
|
605
|
+
|
|
606
|
+
interface WindowTrackerOptions {
|
|
607
|
+
/** The phone number id this tracker scopes its keys to. */
|
|
608
|
+
phoneNumberId: string;
|
|
609
|
+
/** Async key/value store. Use `InMemoryStorage` for dev, BYO Redis for prod. */
|
|
610
|
+
storage: Storage;
|
|
611
|
+
/** Window TTL in milliseconds. Defaults to {@link WINDOW_TTL_MS} (24h). */
|
|
612
|
+
ttlMs?: number;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* 24-hour customer-service-window tracker. Records the most recent inbound
|
|
616
|
+
* timestamp per `customerWaId`, scoped to a single `phoneNumberId`, and
|
|
617
|
+
* exposes `isWindowOpen` for pre-flight checks on outbound free-form
|
|
618
|
+
* sends.
|
|
619
|
+
*
|
|
620
|
+
* Wire it from your inbound handler:
|
|
621
|
+
* receiver.on("message", (e) => tracker.notifyInbound(e.from));
|
|
622
|
+
*
|
|
623
|
+
* And from your outbound client:
|
|
624
|
+
* const client = new WhatsAppClient({ ..., windowTracker: tracker });
|
|
625
|
+
*/
|
|
626
|
+
declare class WindowTracker {
|
|
627
|
+
#private;
|
|
628
|
+
readonly phoneNumberId: string;
|
|
629
|
+
constructor(options: WindowTrackerOptions);
|
|
630
|
+
get ttlMs(): number;
|
|
631
|
+
notifyInbound(customerWaId: string, _atMs?: number): Promise<void>;
|
|
632
|
+
isWindowOpen(customerWaId: string): Promise<boolean>;
|
|
633
|
+
/** @internal — exposed so consumers can clear a window after a hard error. */
|
|
634
|
+
clear(customerWaId: string): Promise<void>;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
interface RetryPolicy {
|
|
638
|
+
/** Total attempts including the first call. */
|
|
639
|
+
maxAttempts: number;
|
|
640
|
+
/** Base delay used as the seed for exponential backoff. */
|
|
641
|
+
baseDelayMs: number;
|
|
642
|
+
/** Hard cap on any single delay. */
|
|
643
|
+
maxDelayMs: number;
|
|
644
|
+
/** Jitter strategy. Only "full" is supported in v1. */
|
|
645
|
+
jitter: "full";
|
|
646
|
+
/** Lower bound on every delay (avoids 0-ms hammering when jitter rolls 0). */
|
|
647
|
+
floorMs: number;
|
|
648
|
+
}
|
|
649
|
+
declare const DEFAULT_RETRY_POLICY: RetryPolicy;
|
|
650
|
+
/**
|
|
651
|
+
* Marker thrown by callers (or the transport layer) to signal a transient
|
|
652
|
+
* HTTP failure that should be retried. Carries optional Retry-After hint
|
|
653
|
+
* derived from the response headers.
|
|
654
|
+
*/
|
|
655
|
+
declare class TransientHttpError extends Error {
|
|
656
|
+
readonly retryAfterMs: number | undefined;
|
|
657
|
+
constructor(message: string, retryAfterMs?: number);
|
|
658
|
+
}
|
|
659
|
+
interface RetryHooks {
|
|
660
|
+
/** Optional sleep injection — in tests we pass a `vi.advanceTimersByTime`-friendly stub. */
|
|
661
|
+
sleep?: (ms: number) => Promise<void>;
|
|
662
|
+
/** Optional RNG injection for deterministic jitter testing. Returns a value in [0, 1). */
|
|
663
|
+
random?: () => number;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
667
|
+
interface RequestOptions {
|
|
668
|
+
/** Override the default retry policy for this call. */
|
|
669
|
+
retryPolicy?: RetryPolicy;
|
|
670
|
+
/** Test hooks; never set in production. */
|
|
671
|
+
retryHooks?: RetryHooks;
|
|
672
|
+
/** Per-call AbortSignal; cancellation is treated as a retryable error. */
|
|
673
|
+
signal?: AbortSignal;
|
|
674
|
+
/**
|
|
675
|
+
* Override the resolved Graph API version for this call (rare — only
|
|
676
|
+
* useful for cross-version migrations).
|
|
677
|
+
*/
|
|
678
|
+
graphApiVersion?: string;
|
|
679
|
+
/**
|
|
680
|
+
* Optional caller-provided idempotency key. When omitted, the SDK
|
|
681
|
+
* generates a fresh UUID v4 per logical call and reuses it across
|
|
682
|
+
* retry attempts of that call. Sent as `X-Dojo-Idempotency-Key`.
|
|
683
|
+
*
|
|
684
|
+
* Meta does NOT honour this header — a retry of `POST /messages`
|
|
685
|
+
* creates a new send. The key is for client-side correlation only
|
|
686
|
+
* (internal logs, parity replays in the mock, future replay-buffering).
|
|
687
|
+
* See `docs/compliance.md` § 3.4.
|
|
688
|
+
*/
|
|
689
|
+
idempotencyKey?: string;
|
|
690
|
+
/** Override fetch implementation — internal hook used by tests. */
|
|
691
|
+
fetchImpl?: typeof fetch;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
interface TokenInfo {
|
|
695
|
+
/** Whether Meta considers the token currently usable. */
|
|
696
|
+
valid: boolean;
|
|
697
|
+
/**
|
|
698
|
+
* Token expiration, expressed as Unix epoch milliseconds. `null` when
|
|
699
|
+
* Meta returns 0 or omits the field (some long-lived tokens never expire).
|
|
700
|
+
*/
|
|
701
|
+
expiresAt: number | null;
|
|
702
|
+
appId: string | null;
|
|
703
|
+
userId: string | null;
|
|
704
|
+
scopes: ReadonlyArray<string>;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Resolves the bearer token used for Graph API requests. Invoked
|
|
709
|
+
* exactly once per outer `request()` call; the resolved value is used
|
|
710
|
+
* for all retry attempts within that request. Throw, return an empty
|
|
711
|
+
* string, or return a non-string value to surface as `AuthenticationError`
|
|
712
|
+
* before the HTTP request is made.
|
|
713
|
+
*
|
|
714
|
+
* Use this shape when tokens rotate (System User expiry, manual
|
|
715
|
+
* rotation in Business Manager, refresh after a 401). The SDK does
|
|
716
|
+
* NOT cache the resolved value across requests; cache inside your
|
|
717
|
+
* callback if needed.
|
|
718
|
+
*/
|
|
719
|
+
type TokenProvider = () => string | Promise<string>;
|
|
720
|
+
interface WhatsAppClientOptions {
|
|
721
|
+
/** Phone number ID for the WhatsApp Business phone (Graph API: /{phone-number-id}/messages). */
|
|
722
|
+
phoneNumberId: string;
|
|
723
|
+
/** WhatsApp Business Account ID (Graph API: /{waba-id}/message_templates). */
|
|
724
|
+
wabaId: string;
|
|
725
|
+
/**
|
|
726
|
+
* Long-lived BISU or System User token, or a `TokenProvider` callback
|
|
727
|
+
* that resolves one per request. Callback shape supports rotation
|
|
728
|
+
* without swapping the client instance.
|
|
729
|
+
*/
|
|
730
|
+
token: string | TokenProvider;
|
|
731
|
+
/** App secret used to verify HMAC-SHA256 signatures on inbound webhooks. */
|
|
732
|
+
appSecret: string;
|
|
733
|
+
/** Optional override for the pinned Graph API version (default: GRAPH_API_VERSION). */
|
|
734
|
+
graphApiVersion?: GraphApiVersion;
|
|
735
|
+
/**
|
|
736
|
+
* Optional 24h-window tracker. When provided, free-form sends
|
|
737
|
+
* (sendText, sendMedia*, sendLocation, sendContacts, sendInteractive)
|
|
738
|
+
* pre-flight-check the tracker and throw `WindowClosedError` before
|
|
739
|
+
* issuing any HTTP request. `sendTemplate` and `sendReaction` are
|
|
740
|
+
* window-exempt and never consult the tracker.
|
|
741
|
+
*/
|
|
742
|
+
windowTracker?: WindowTracker;
|
|
743
|
+
}
|
|
744
|
+
declare class WhatsAppClient {
|
|
745
|
+
#private;
|
|
746
|
+
readonly phoneNumberId: string;
|
|
747
|
+
readonly wabaId: string;
|
|
748
|
+
readonly graphApiVersion: GraphApiVersion;
|
|
749
|
+
constructor(options: WhatsAppClientOptions);
|
|
750
|
+
/**
|
|
751
|
+
* Whether the 24h customer-service window is currently open for `to`.
|
|
752
|
+
* Returns `true` when no window tracker is configured (preserving the
|
|
753
|
+
* pre-Phase-4 "ungated" behaviour); otherwise delegates to the tracker.
|
|
754
|
+
*/
|
|
755
|
+
isWindowOpen(to: string): Promise<boolean>;
|
|
756
|
+
/**
|
|
757
|
+
* @internal — exposed for capability slices that need the bearer
|
|
758
|
+
* token. Resolves the configured `TokenProvider` exactly once per
|
|
759
|
+
* call; surfaces provider failures as `AuthenticationError` before
|
|
760
|
+
* the HTTP request is made.
|
|
761
|
+
*/
|
|
762
|
+
_resolveBearerToken(): Promise<string>;
|
|
763
|
+
/** @internal — exposed for the webhook receiver capability (Phase 3). */
|
|
764
|
+
_getAppSecret(): string;
|
|
765
|
+
/**
|
|
766
|
+
* Issue an authenticated Graph API request.
|
|
767
|
+
*
|
|
768
|
+
* @internal — public-API surface for sends, templates, etc. lands in
|
|
769
|
+
* later phases (Phase 2 message-builders, Phase 5 template-management).
|
|
770
|
+
*/
|
|
771
|
+
request<T>(method: HttpMethod, path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
772
|
+
/**
|
|
773
|
+
* Verify the bearer token via Meta's `/debug_token` endpoint.
|
|
774
|
+
*
|
|
775
|
+
* Resolves with the parsed token info; throws `WhatsAppError` if the
|
|
776
|
+
* token is invalid or the call fails.
|
|
777
|
+
*/
|
|
778
|
+
healthCheck(options?: RequestOptions): Promise<TokenInfo>;
|
|
779
|
+
sendText(input: BuildTextInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
780
|
+
sendImage(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
781
|
+
sendVideo(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
782
|
+
sendAudio(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
783
|
+
sendDocument(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
784
|
+
sendSticker(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
785
|
+
sendLocation(input: BuildLocationInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
786
|
+
sendContacts(input: BuildContactsInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
787
|
+
sendInteractive(input: BuildInteractiveInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
788
|
+
/**
|
|
789
|
+
* Window-exempt: templates are the escape hatch when the window is closed.
|
|
790
|
+
* Async so any synchronous error from `buildTemplate` (e.g.,
|
|
791
|
+
* `validateAgainst` mismatch) surfaces as a rejected promise rather
|
|
792
|
+
* than a synchronous throw.
|
|
793
|
+
*/
|
|
794
|
+
sendTemplate(input: BuildTemplateInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
795
|
+
/** Window-exempt: authentication templates are the canonical out-of-window send. */
|
|
796
|
+
sendAuthTemplate(input: BuildAuthTemplateInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
797
|
+
/**
|
|
798
|
+
* Send a voice note (audio with `voice: true`). Window-gated like
|
|
799
|
+
* any other free-form media send. Voice notes trigger transcription
|
|
800
|
+
* support, auto-download, and a "played" status when the recipient
|
|
801
|
+
* listens.
|
|
802
|
+
*/
|
|
803
|
+
sendVoice(input: BuildVoiceInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
804
|
+
/** Window-exempt: carousel sends are template sends. */
|
|
805
|
+
sendCarouselTemplate(input: BuildCarouselTemplateInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
806
|
+
listTemplates(query?: ListTemplatesQuery, options?: RequestOptions): Promise<ListTemplatesResponse>;
|
|
807
|
+
getTemplate(templateId: string, options?: RequestOptions): Promise<TemplateDefinition>;
|
|
808
|
+
/** Window-exempt: reactions are part of an existing thread. */
|
|
809
|
+
sendReaction(input: BuildReactionInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
810
|
+
/**
|
|
811
|
+
* Send any pre-built `WhatsAppMessage` payload as a reply to a previous
|
|
812
|
+
* message identified by its wamid. Sets `context.message_id` and posts.
|
|
813
|
+
* Window-gated for non-template, non-reaction payloads.
|
|
814
|
+
*/
|
|
815
|
+
sendReply(replyTo: string, payload: WhatsAppMessage, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* POST a fully-built `WhatsAppMessage` payload to `/{phoneNumberId}/messages`.
|
|
820
|
+
* Returns the parsed response body (`{ messaging_product, contacts, messages }`).
|
|
821
|
+
*/
|
|
822
|
+
declare function sendMessage(client: WhatsAppClient, payload: WhatsAppMessage, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
823
|
+
|
|
824
|
+
type WhatsAppErrorCode = "MISSING_CREDENTIALS" | "RATE_LIMIT" | "WINDOW_CLOSED" | "WEBHOOK_SIGNATURE" | "TEMPLATE" | "MOCK_MODE" | "AUTHENTICATION" | "PERMISSION" | "CAPABILITY" | "UNKNOWN";
|
|
825
|
+
interface WhatsAppErrorOptions {
|
|
826
|
+
cause?: unknown;
|
|
827
|
+
}
|
|
828
|
+
declare class WhatsAppError extends Error {
|
|
829
|
+
readonly code: WhatsAppErrorCode;
|
|
830
|
+
constructor(code: WhatsAppErrorCode, message: string, options?: WhatsAppErrorOptions);
|
|
831
|
+
toJSON(): {
|
|
832
|
+
name: string;
|
|
833
|
+
code: WhatsAppErrorCode;
|
|
834
|
+
message: string;
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
type CredentialField = "phoneNumberId" | "wabaId" | "token" | "appSecret";
|
|
838
|
+
declare class MissingCredentialsError extends WhatsAppError {
|
|
839
|
+
readonly code: "MISSING_CREDENTIALS";
|
|
840
|
+
readonly missingFields: ReadonlyArray<CredentialField>;
|
|
841
|
+
constructor(missingFields: ReadonlyArray<CredentialField>, options?: WhatsAppErrorOptions);
|
|
842
|
+
toJSON(): {
|
|
843
|
+
name: string;
|
|
844
|
+
code: "MISSING_CREDENTIALS";
|
|
845
|
+
message: string;
|
|
846
|
+
missingFields: ReadonlyArray<CredentialField>;
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
interface RateLimitErrorMeta {
|
|
850
|
+
/** Meta error code (e.g., 131056, 130429, 131048). */
|
|
851
|
+
metaCode?: number;
|
|
852
|
+
/** Retry hint in milliseconds, derived from headers or backoff. */
|
|
853
|
+
retryAfterMs?: number;
|
|
854
|
+
}
|
|
855
|
+
declare class RateLimitError extends WhatsAppError {
|
|
856
|
+
readonly code: "RATE_LIMIT";
|
|
857
|
+
readonly metaCode: number | undefined;
|
|
858
|
+
readonly retryAfterMs: number | undefined;
|
|
859
|
+
constructor(message: string, meta?: RateLimitErrorMeta, options?: WhatsAppErrorOptions);
|
|
860
|
+
}
|
|
861
|
+
declare class WindowClosedError extends WhatsAppError {
|
|
862
|
+
readonly code: "WINDOW_CLOSED";
|
|
863
|
+
readonly customerWaId: string;
|
|
864
|
+
constructor(customerWaId: string, options?: WhatsAppErrorOptions);
|
|
865
|
+
}
|
|
866
|
+
declare class WebhookSignatureError extends WhatsAppError {
|
|
867
|
+
readonly code: "WEBHOOK_SIGNATURE";
|
|
868
|
+
constructor(message?: string, options?: WhatsAppErrorOptions);
|
|
869
|
+
}
|
|
870
|
+
declare class TemplateError extends WhatsAppError {
|
|
871
|
+
readonly code: "TEMPLATE";
|
|
872
|
+
readonly templateName: string | undefined;
|
|
873
|
+
constructor(message: string, templateName?: string, options?: WhatsAppErrorOptions);
|
|
874
|
+
}
|
|
875
|
+
declare class MockModeError extends WhatsAppError {
|
|
876
|
+
readonly code: "MOCK_MODE";
|
|
877
|
+
constructor(message: string, options?: WhatsAppErrorOptions);
|
|
878
|
+
}
|
|
879
|
+
interface AuthenticationErrorMeta {
|
|
880
|
+
/** Meta error code (typically 190). */
|
|
881
|
+
metaCode?: number;
|
|
882
|
+
/** Meta error_subcode (e.g. 463 expired, 467 invalid, 492 changed). */
|
|
883
|
+
subcode?: number;
|
|
884
|
+
}
|
|
885
|
+
declare class AuthenticationError extends WhatsAppError {
|
|
886
|
+
readonly code: "AUTHENTICATION";
|
|
887
|
+
readonly metaCode: number | undefined;
|
|
888
|
+
readonly subcode: number | undefined;
|
|
889
|
+
constructor(message: string, meta?: AuthenticationErrorMeta, options?: WhatsAppErrorOptions);
|
|
890
|
+
}
|
|
891
|
+
interface PermissionErrorMeta {
|
|
892
|
+
/** Meta error code (200, 210, 230, 294, or 299 in v1). */
|
|
893
|
+
metaCode?: number;
|
|
894
|
+
}
|
|
895
|
+
declare class PermissionError extends WhatsAppError {
|
|
896
|
+
readonly code: "PERMISSION";
|
|
897
|
+
readonly metaCode: number | undefined;
|
|
898
|
+
constructor(message: string, meta?: PermissionErrorMeta, options?: WhatsAppErrorOptions);
|
|
899
|
+
}
|
|
900
|
+
interface CapabilityErrorMeta {
|
|
901
|
+
/** Meta error code (typically 100). */
|
|
902
|
+
metaCode?: number;
|
|
903
|
+
}
|
|
904
|
+
declare class CapabilityError extends WhatsAppError {
|
|
905
|
+
readonly code: "CAPABILITY";
|
|
906
|
+
readonly metaCode: number | undefined;
|
|
907
|
+
constructor(message: string, meta?: CapabilityErrorMeta, options?: WhatsAppErrorOptions);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Shared interface satisfied by both `WhatsAppClient` and
|
|
912
|
+
* `MockWhatsAppClient`. Consumer code that only needs send capability
|
|
913
|
+
* can take this union and run uniformly against either backend.
|
|
914
|
+
*/
|
|
915
|
+
interface WhatsAppLikeClient {
|
|
916
|
+
readonly phoneNumberId: string;
|
|
917
|
+
readonly wabaId: string;
|
|
918
|
+
readonly graphApiVersion: GraphApiVersion;
|
|
919
|
+
isWindowOpen(to: string): Promise<boolean>;
|
|
920
|
+
sendText(input: BuildTextInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
921
|
+
sendImage(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
922
|
+
sendVideo(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
923
|
+
sendAudio(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
924
|
+
sendDocument(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
925
|
+
sendSticker(input: BuildMediaInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
926
|
+
sendLocation(input: BuildLocationInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
927
|
+
sendContacts(input: BuildContactsInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
928
|
+
sendInteractive(input: BuildInteractiveInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
929
|
+
sendTemplate(input: BuildTemplateInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
930
|
+
sendAuthTemplate(input: BuildAuthTemplateInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
931
|
+
sendVoice(input: BuildVoiceInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
932
|
+
sendCarouselTemplate(input: BuildCarouselTemplateInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
933
|
+
sendReaction(input: BuildReactionInput, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
934
|
+
sendReply(replyTo: string, payload: WhatsAppMessage, options?: RequestOptions): Promise<MessageSendResponse>;
|
|
935
|
+
listTemplates(query?: ListTemplatesQuery, options?: RequestOptions): Promise<ListTemplatesResponse>;
|
|
936
|
+
getTemplate(templateId: string, options?: RequestOptions): Promise<TemplateDefinition>;
|
|
937
|
+
}
|
|
938
|
+
/** A single send recorded by the mock client. */
|
|
939
|
+
interface RecordedSend {
|
|
940
|
+
wamid: string;
|
|
941
|
+
payload: WhatsAppMessage;
|
|
942
|
+
sentAt: number;
|
|
943
|
+
}
|
|
944
|
+
interface MockWhatsAppClientOptions {
|
|
945
|
+
phoneNumberId: string;
|
|
946
|
+
wabaId: string;
|
|
947
|
+
graphApiVersion?: GraphApiVersion;
|
|
948
|
+
windowTracker?: WindowTracker;
|
|
949
|
+
/** Optional clock injection (defaults to Date.now). */
|
|
950
|
+
now?: () => number;
|
|
951
|
+
/**
|
|
952
|
+
* Optional template registry. When supplied, `listTemplates(query?)`
|
|
953
|
+
* filters the seed in memory and `getTemplate(id)` resolves with the
|
|
954
|
+
* matching entry. When omitted, the mock preserves the v1 behaviour:
|
|
955
|
+
* `listTemplates` → `{ data: [] }` and `getTemplate` rejects.
|
|
956
|
+
*/
|
|
957
|
+
templates?: ReadonlyArray<TemplateDefinition>;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* In-memory implementation of `WhatsAppLikeClient`. Records every send
|
|
962
|
+
* as a `RecordedSend`, generates deterministic `wamid.mock-${counter}`
|
|
963
|
+
* ids, and never touches the network.
|
|
964
|
+
*
|
|
965
|
+
* Honours the same `windowTracker` gate as the real client.
|
|
966
|
+
*/
|
|
967
|
+
declare class MockWhatsAppClient implements WhatsAppLikeClient {
|
|
968
|
+
#private;
|
|
969
|
+
readonly phoneNumberId: string;
|
|
970
|
+
readonly wabaId: string;
|
|
971
|
+
readonly graphApiVersion: GraphApiVersion;
|
|
972
|
+
constructor(options: MockWhatsAppClientOptions);
|
|
973
|
+
get sentMessages(): ReadonlyArray<RecordedSend>;
|
|
974
|
+
reset(): void;
|
|
975
|
+
isWindowOpen(to: string): Promise<boolean>;
|
|
976
|
+
/**
|
|
977
|
+
* Synthesise a webhook event into a `WebhookReceiver`. Bypasses
|
|
978
|
+
* signature verification — the receiver dispatches handlers directly.
|
|
979
|
+
*/
|
|
980
|
+
simulateInbound(receiver: WebhookReceiver, event: WhatsAppEvent): Promise<void>;
|
|
981
|
+
sendText(input: BuildTextInput): Promise<MessageSendResponse>;
|
|
982
|
+
sendImage(input: BuildMediaInput): Promise<MessageSendResponse>;
|
|
983
|
+
sendVideo(input: BuildMediaInput): Promise<MessageSendResponse>;
|
|
984
|
+
sendAudio(input: BuildMediaInput): Promise<MessageSendResponse>;
|
|
985
|
+
sendDocument(input: BuildMediaInput): Promise<MessageSendResponse>;
|
|
986
|
+
sendSticker(input: BuildMediaInput): Promise<MessageSendResponse>;
|
|
987
|
+
sendLocation(input: BuildLocationInput): Promise<MessageSendResponse>;
|
|
988
|
+
sendContacts(input: BuildContactsInput): Promise<MessageSendResponse>;
|
|
989
|
+
sendInteractive(input: BuildInteractiveInput): Promise<MessageSendResponse>;
|
|
990
|
+
sendTemplate(input: BuildTemplateInput): Promise<MessageSendResponse>;
|
|
991
|
+
sendAuthTemplate(input: BuildAuthTemplateInput): Promise<MessageSendResponse>;
|
|
992
|
+
sendVoice(input: BuildVoiceInput): Promise<MessageSendResponse>;
|
|
993
|
+
sendCarouselTemplate(input: BuildCarouselTemplateInput): Promise<MessageSendResponse>;
|
|
994
|
+
sendReaction(input: BuildReactionInput): Promise<MessageSendResponse>;
|
|
995
|
+
sendReply(replyTo: string, payload: WhatsAppMessage): Promise<MessageSendResponse>;
|
|
996
|
+
listTemplates(query?: ListTemplatesQuery, _options?: RequestOptions): Promise<ListTemplatesResponse>;
|
|
997
|
+
getTemplate(templateId: string, _options?: RequestOptions): Promise<TemplateDefinition>;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
interface PickWhatsAppClientOptions extends WhatsAppClientOptions {
|
|
1001
|
+
/** Force the real client even when WHATSAPP_MODE=mock is set. */
|
|
1002
|
+
forceReal?: boolean;
|
|
1003
|
+
/** Force the mock client regardless of env. */
|
|
1004
|
+
forceMock?: boolean;
|
|
1005
|
+
/**
|
|
1006
|
+
* Optional template registry — only used when the factory routes to the
|
|
1007
|
+
* mock backend. Forwarded to `MockWhatsAppClient` as `options.templates`.
|
|
1008
|
+
* Ignored by the real client.
|
|
1009
|
+
*/
|
|
1010
|
+
templates?: ReadonlyArray<TemplateDefinition>;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Choose between the real `WhatsAppClient` and `MockWhatsAppClient`
|
|
1014
|
+
* based on `process.env.WHATSAPP_MODE`. Optional `forceReal` /
|
|
1015
|
+
* `forceMock` overrides take precedence over env detection.
|
|
1016
|
+
*
|
|
1017
|
+
* Returns the shared `WhatsAppLikeClient` interface so consumer code
|
|
1018
|
+
* runs uniformly against either implementation.
|
|
1019
|
+
*/
|
|
1020
|
+
declare function pickWhatsAppClient(options: PickWhatsAppClientOptions): WhatsAppLikeClient;
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Set the salt used by `hashPhoneNumberId` (and friends) for PII redaction.
|
|
1024
|
+
* Production deployments SHOULD set this once at boot to a per-environment
|
|
1025
|
+
* value so spans across runs / replicas correlate consistently within an
|
|
1026
|
+
* environment but differ across environments.
|
|
1027
|
+
*/
|
|
1028
|
+
declare function setRedactSalt(salt: string): void;
|
|
1029
|
+
/**
|
|
1030
|
+
* Stable, salted SHA-256 hash truncated to 16 lowercase-hex characters.
|
|
1031
|
+
* Suitable for tagging OTel spans without leaking the raw phone number id.
|
|
1032
|
+
*
|
|
1033
|
+
* Runtime-portable: uses `crypto.subtle.digest("SHA-256", ...)` so the
|
|
1034
|
+
* function runs unmodified on Node ≥ 20, Cloudflare Workers, Bun, Deno,
|
|
1035
|
+
* and any WinterCG runtime.
|
|
1036
|
+
*/
|
|
1037
|
+
declare function hashPhoneNumberId(value: string): Promise<string>;
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Convenience accessor for the SDK's tracer. When no provider is
|
|
1041
|
+
* registered (default), the OTel API returns a no-op tracer and the
|
|
1042
|
+
* spans we emit are silent.
|
|
1043
|
+
*/
|
|
1044
|
+
declare function getTracer(): Tracer;
|
|
1045
|
+
/**
|
|
1046
|
+
* Run `fn` inside an active OTel span. Applies `attributes` on start,
|
|
1047
|
+
* records exceptions, sets the span's status to ERROR on rejection,
|
|
1048
|
+
* and ends the span exactly once. Returns whatever `fn` resolves with.
|
|
1049
|
+
*/
|
|
1050
|
+
declare function withSpan<T>(name: string, fn: () => Promise<T>, attributes?: Attributes): Promise<T>;
|
|
1051
|
+
|
|
1052
|
+
interface TokenBucketOptions {
|
|
1053
|
+
/** Maximum number of tokens the bucket holds. */
|
|
1054
|
+
capacity: number;
|
|
1055
|
+
/** Refill rate in tokens per millisecond (e.g. `80 / 1000` for 80 MPS). */
|
|
1056
|
+
refillPerMs: number;
|
|
1057
|
+
/** Optional clock injection (defaults to `Date.now`). */
|
|
1058
|
+
now?: () => number;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Continuous-refill token bucket. `acquire(count)` resolves
|
|
1062
|
+
* immediately when enough tokens are available; otherwise waits
|
|
1063
|
+
* (via `setTimeout`) until refill produces enough. Concurrent
|
|
1064
|
+
* `acquire` calls on an empty bucket are serialised so two callers
|
|
1065
|
+
* never consume the same refilled token.
|
|
1066
|
+
*
|
|
1067
|
+
* No background timer is scheduled; refill is computed lazily on
|
|
1068
|
+
* access. Safe to construct and discard.
|
|
1069
|
+
*/
|
|
1070
|
+
declare class TokenBucket {
|
|
1071
|
+
#private;
|
|
1072
|
+
constructor(options: TokenBucketOptions);
|
|
1073
|
+
/** Current token count after applying any pending refill. Pure read. */
|
|
1074
|
+
peek(): number;
|
|
1075
|
+
/** Last access timestamp (epoch ms). Used by `BucketMap` eviction. */
|
|
1076
|
+
lastAccessAt(): number;
|
|
1077
|
+
/** Whether the bucket currently has its full capacity worth of tokens. */
|
|
1078
|
+
isFull(): boolean;
|
|
1079
|
+
/**
|
|
1080
|
+
* Acquire `count` tokens. Resolves once the tokens are reserved
|
|
1081
|
+
* for this caller. Concurrent acquires queue behind the previous
|
|
1082
|
+
* waiter so the refill is consumed in arrival order.
|
|
1083
|
+
*/
|
|
1084
|
+
acquire(count?: number): Promise<void>;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
interface BucketMapOptions extends TokenBucketOptions {
|
|
1088
|
+
/**
|
|
1089
|
+
* Buckets at full capacity AND idle for at least this many
|
|
1090
|
+
* milliseconds are evicted opportunistically on the next
|
|
1091
|
+
* `acquire`. Defaults to 60_000 (1 minute).
|
|
1092
|
+
*/
|
|
1093
|
+
evictAfterMs?: number;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Lazily-created map of `TokenBucket`s, keyed by an arbitrary
|
|
1097
|
+
* string. Buckets that have been full and idle for `evictAfterMs`
|
|
1098
|
+
* are dropped on the next `acquire` sweep so the map doesn't grow
|
|
1099
|
+
* unbounded under fanout workloads.
|
|
1100
|
+
*
|
|
1101
|
+
* No background timer is scheduled; eviction is opportunistic.
|
|
1102
|
+
*/
|
|
1103
|
+
declare class BucketMap {
|
|
1104
|
+
#private;
|
|
1105
|
+
constructor(options: BucketMapOptions);
|
|
1106
|
+
acquire(key: string, count?: number): Promise<void>;
|
|
1107
|
+
size(): number;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
interface RateLimitOptions {
|
|
1111
|
+
/**
|
|
1112
|
+
* Per-pair (sender phone_number_id × recipient) ceiling.
|
|
1113
|
+
* Defaults to `{ messages: 1, per: 6_000 }` — Meta's documented
|
|
1114
|
+
* limit for unsolicited free-form sends.
|
|
1115
|
+
*/
|
|
1116
|
+
perPair?: {
|
|
1117
|
+
messages: number;
|
|
1118
|
+
per: number;
|
|
1119
|
+
};
|
|
1120
|
+
/**
|
|
1121
|
+
* Per-WABA ceiling. Defaults to `{ mps: 80 }`, the verified-tier
|
|
1122
|
+
* starting limit. Raise as Meta grants higher tiers.
|
|
1123
|
+
*/
|
|
1124
|
+
perWaba?: {
|
|
1125
|
+
mps: number;
|
|
1126
|
+
};
|
|
1127
|
+
/** Optional clock injection for deterministic testing. */
|
|
1128
|
+
now?: () => number;
|
|
1129
|
+
/**
|
|
1130
|
+
* Idle-eviction window for per-pair buckets. Buckets at full
|
|
1131
|
+
* capacity AND idle for ≥ this many ms are dropped. Default
|
|
1132
|
+
* 60_000.
|
|
1133
|
+
*/
|
|
1134
|
+
evictAfterMs?: number;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Decorate any `WhatsAppLikeClient` with two token-bucket rate
|
|
1138
|
+
* limiters so outbound sends respect Meta's per-pair and per-WABA
|
|
1139
|
+
* ceilings before issuing the HTTP request.
|
|
1140
|
+
*
|
|
1141
|
+
* The returned client has the same shape as the input. Non-send
|
|
1142
|
+
* methods (`isWindowOpen`, `listTemplates`, `getTemplate`) pass
|
|
1143
|
+
* through unchanged.
|
|
1144
|
+
*/
|
|
1145
|
+
declare function withRateLimit(client: WhatsAppLikeClient, options?: RateLimitOptions): WhatsAppLikeClient;
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* GET /{wabaId}/message_templates — list approved templates for a WABA.
|
|
1149
|
+
* The optional `query` is encoded as URL search params.
|
|
1150
|
+
*/
|
|
1151
|
+
declare function listTemplates(client: WhatsAppClient, query?: ListTemplatesQuery, options?: RequestOptions): Promise<ListTemplatesResponse>;
|
|
1152
|
+
/** GET /{templateId} — fetch a single template definition by id. */
|
|
1153
|
+
declare function getTemplate(client: WhatsAppClient, templateId: string, options?: RequestOptions): Promise<TemplateDefinition>;
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Count the number of unique `{{N}}` placeholders in a template body / header
|
|
1157
|
+
* string. Validates that placeholders are 1-INDEXED and contiguous (no gaps).
|
|
1158
|
+
*
|
|
1159
|
+
* Throws `TemplateError` on:
|
|
1160
|
+
* - any `{{0}}` (Meta's variables are 1-indexed)
|
|
1161
|
+
* - gaps (e.g., `{{1}}` and `{{3}}` without a `{{2}}`)
|
|
1162
|
+
*
|
|
1163
|
+
* Repeated indices are counted once.
|
|
1164
|
+
*/
|
|
1165
|
+
declare function countTemplatePlaceholders(text: string | undefined): number;
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* Cross-validate a built `TemplateMessage` payload against an approved
|
|
1169
|
+
* `TemplateDefinition`. Throws `TemplateError(message, definition.name)`
|
|
1170
|
+
* on any mismatch:
|
|
1171
|
+
* 1. payload.template.name !== definition.name
|
|
1172
|
+
* 2. payload.template.language.code !== definition.language
|
|
1173
|
+
* 3. for each payload component: matching definition component must
|
|
1174
|
+
* exist (by type, and for buttons also by sub_type/index)
|
|
1175
|
+
* 4. parameter count must equal placeholder count in the matching
|
|
1176
|
+
* definition component
|
|
1177
|
+
*/
|
|
1178
|
+
declare function validateTemplateSend(payload: TemplateMessage, definition: TemplateDefinition): void;
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Tracks which webhook events have been seen and returns whether the
|
|
1182
|
+
* current sighting is new. Backed by any `Storage` impl (in-memory,
|
|
1183
|
+
* Redis, etc.).
|
|
1184
|
+
*
|
|
1185
|
+
* The key for a `message` event is the wamid; for a `status` event the
|
|
1186
|
+
* receiver layers in the status string so transitions (sent → delivered
|
|
1187
|
+
* → read → failed) are not collapsed.
|
|
1188
|
+
*/
|
|
1189
|
+
declare class WebhookDeduper {
|
|
1190
|
+
#private;
|
|
1191
|
+
constructor(storage: Storage, ttlMs?: number);
|
|
1192
|
+
markIfNew(eventKey: string): Promise<boolean>;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
interface VerifyHandshakeInput {
|
|
1196
|
+
/** `hub.mode` query param sent by Meta. */
|
|
1197
|
+
mode: string | null | undefined;
|
|
1198
|
+
/** `hub.verify_token` query param sent by Meta. */
|
|
1199
|
+
verifyToken: string | null | undefined;
|
|
1200
|
+
/** `hub.challenge` query param sent by Meta. */
|
|
1201
|
+
challenge: string | null | undefined;
|
|
1202
|
+
/** The verify-token shared secret you registered in Meta's webhook UI. */
|
|
1203
|
+
expectedToken: string;
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Verify Meta's GET-based webhook handshake. Returns the `challenge`
|
|
1207
|
+
* value when `mode === "subscribe"` AND `verifyToken === expectedToken`
|
|
1208
|
+
* (constant-time compare). Returns `null` for every other input — the
|
|
1209
|
+
* caller SHOULD respond `403 Forbidden`.
|
|
1210
|
+
*
|
|
1211
|
+
* Runtime-portable: uses an explicit constant-time byte-wise compare
|
|
1212
|
+
* rather than `node:crypto.timingSafeEqual`, so the function runs
|
|
1213
|
+
* unmodified on Cloudflare Workers, Bun, Deno, and any WinterCG runtime.
|
|
1214
|
+
*/
|
|
1215
|
+
declare function verifyHandshake({ mode, verifyToken, challenge, expectedToken, }: VerifyHandshakeInput): string | null;
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Parse a Meta webhook payload (already JSON-decoded) into a flat
|
|
1219
|
+
* `ReadonlyArray<WhatsAppEvent>`. Pure, never throws, surfaces
|
|
1220
|
+
* unrecognised top-level fields as `{ kind: "unknown" }` so consumers
|
|
1221
|
+
* can log and the SDK can extend later without breaking changes.
|
|
1222
|
+
*/
|
|
1223
|
+
declare function parseWebhookPayload(body: unknown): ReadonlyArray<WhatsAppEvent>;
|
|
1224
|
+
|
|
1225
|
+
interface VerifySignatureInput {
|
|
1226
|
+
/** Raw body BYTES Meta sent. Strings are UTF-8 encoded; do not pre-parse. */
|
|
1227
|
+
rawBody: Buffer | Uint8Array | string;
|
|
1228
|
+
/** The `X-Hub-Signature-256` header value. May or may not include the `sha256=` prefix. */
|
|
1229
|
+
signatureHeader: string | null | undefined;
|
|
1230
|
+
/** Your Meta app secret. */
|
|
1231
|
+
appSecret: string;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Timing-safe HMAC-SHA256 verification of an incoming webhook body.
|
|
1235
|
+
*
|
|
1236
|
+
* Resolves to `true` if and only if the HMAC of the raw body, keyed with
|
|
1237
|
+
* `appSecret`, matches the hex value in `signatureHeader`. Resolves to
|
|
1238
|
+
* `false` (without rejecting) on every other input — including missing
|
|
1239
|
+
* header, malformed hex, or wrong byte length.
|
|
1240
|
+
*
|
|
1241
|
+
* Uses WebCrypto (`crypto.subtle.sign`) so the function runs unmodified
|
|
1242
|
+
* on Node ≥ 20, Cloudflare Workers, Bun, Deno, and any WinterCG runtime.
|
|
1243
|
+
*/
|
|
1244
|
+
declare function verifySignature({ rawBody, signatureHeader, appSecret, }: VerifySignatureInput): Promise<boolean>;
|
|
1245
|
+
/**
|
|
1246
|
+
* Throwing variant of {@link verifySignature}. Resolves to `void` on a
|
|
1247
|
+
* valid signature; throws `WebhookSignatureError` on any failure (bad
|
|
1248
|
+
* HMAC, missing header, malformed hex, wrong byte length).
|
|
1249
|
+
*
|
|
1250
|
+
* Use this when wiring your own HTTP layer (i.e. not the SDK's
|
|
1251
|
+
* Express / web / Hono adapters) and you want to surface a typed
|
|
1252
|
+
* error rather than branch on a boolean. The SDK's bundled adapters
|
|
1253
|
+
* use the boolean variant and return `401` directly.
|
|
1254
|
+
*/
|
|
1255
|
+
declare function verifySignatureOrThrow(input: VerifySignatureInput): Promise<void>;
|
|
1256
|
+
/**
|
|
1257
|
+
* Compute the lowercase-hex `HMAC-SHA256(appSecret, rawBody)`. Exposed
|
|
1258
|
+
* primarily so test fixtures can produce the expected header value.
|
|
1259
|
+
*/
|
|
1260
|
+
declare function computeSignature(rawBody: Buffer | Uint8Array | string, appSecret: string): Promise<string>;
|
|
1261
|
+
|
|
1262
|
+
export { type AudioMessage, AuthenticationError, type AuthenticationErrorMeta, type BaseMessage, BucketMap, type BucketMapOptions, type BuildAuthTemplateInput, type BuildCarouselTemplateInput, type BuildContactsInput, type BuildInteractiveButtonInput, type BuildInteractiveCtaUrlInput, type BuildInteractiveInput, type BuildInteractiveListInput, type BuildLocationInput, type BuildMediaInput, type BuildReactionInput, type BuildTemplateInput, type BuildTextInput, type BuildVoiceInput, CapabilityError, type CapabilityErrorMeta, type CarouselCard, type CarouselCardButton, type CarouselCardComponent, type CarouselCardMediaHeader, type Contact, type ContactEmail, type ContactName, type ContactPhone, type ContactsMessage, type CredentialField, DEFAULT_RETRY_POLICY, type DocumentMessage, GRAPH_API_VERSION, type GraphApiVersion, type HttpMethod, type ImageMessage, type InteractiveBody, type InteractiveButtonAction, type InteractiveButtonBody, type InteractiveButtonReply, type InteractiveCtaUrlAction, type InteractiveCtaUrlBody, type InteractiveHeader, type InteractiveListAction, type InteractiveListBody, type InteractiveListRow, type InteractiveListSection, type InteractiveMessage, type ListTemplatesPaging, type ListTemplatesQuery, type ListTemplatesResponse, type LocationBody, type LocationMessage, META_GRAPH_BASE_URL, type MediaKind, type MediaSource, type MessageSendResponse, type MessageSendResponseContact, type MessageSendResponseMessage, MissingCredentialsError, MockModeError, MockWhatsAppClient, type MockWhatsAppClientOptions, PermissionError, type PermissionErrorMeta, type PickWhatsAppClientOptions, RateLimitError, type RateLimitErrorMeta, type RateLimitOptions, type ReactionBody, type ReactionMessage, type RecipientType, type RecordedSend, type RequestOptions, type RetryPolicy, type StickerMessage, Storage, type TemplateBody, type TemplateButtonDefinition, type TemplateCategory, type TemplateComponent, type TemplateComponentDefinition, type TemplateComponentDefinitionType, type TemplateDefinition, TemplateError, type TemplateLanguage, type TemplateMessage, type TemplateParameter, type TemplateParameterCouponCode, type TemplateParameterCurrency, type TemplateParameterDateTime, type TemplateParameterDocument, type TemplateParameterImage, type TemplateParameterLimitedTimeOffer, type TemplateParameterPayload, type TemplateParameterText, type TemplateParameterVideo, type TemplateStatus, type TextBody, type TextMessage, TokenBucket, type TokenBucketOptions, type TokenInfo, type TokenProvider, TransientHttpError, type VerifyHandshakeInput, type VerifySignatureInput, type VideoMessage, WEBHOOK_ACK_DEADLINE_MS, WEBHOOK_DEDUPE_TTL_MS, WINDOW_TTL_MS, WebhookDeduper, WebhookReceiver, WebhookSignatureError, WhatsAppClient, type WhatsAppClientOptions, WhatsAppError, type WhatsAppErrorCode, type WhatsAppErrorOptions, WhatsAppEvent, type WhatsAppLikeClient, type WhatsAppMessage, WindowClosedError, WindowTracker, type WindowTrackerOptions, buildAudio, buildAuthTemplate, buildCarouselTemplate, buildContacts, buildDocument, buildImage, buildInteractive, buildInteractiveButton, buildInteractiveCtaUrl, buildInteractiveList, buildLocation, buildReaction, buildSticker, buildTemplate, buildText, buildVideo, buildVoice, computeSignature, countTemplatePlaceholders, getTemplate, getTracer, hashPhoneNumberId, listTemplates, parseWebhookPayload, pickWhatsAppClient, sendMessage, setRedactSalt, validateTemplateSend, verifyHandshake, verifySignature, verifySignatureOrThrow, withRateLimit, withSpan };
|