@whatsapi.sh/sdk 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 whatsapi.sh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # @whatsapi.sh/sdk
2
+
3
+ The official TypeScript client for [whatsapi.sh](https://whatsapi.sh) — send WhatsApp
4
+ messages, media and one-time codes through numbers you pair yourself, and receive
5
+ signed events back.
6
+
7
+ Every method, type and doc comment in this package is generated from the API's own
8
+ operation definitions, so what TypeScript tells you here is what the server actually
9
+ does.
10
+
11
+ ```bash
12
+ npm install @whatsapi.sh/sdk
13
+ ```
14
+
15
+ ## Send a message
16
+
17
+ ```ts
18
+ import { Whatsapi } from "@whatsapi.sh/sdk";
19
+
20
+ const wa = new Whatsapi({ apiKey: process.env.WHATSAPI_KEY! });
21
+
22
+ const message = await wa.sendMessage({
23
+ to: "+5511988887777",
24
+ text: "Your table is ready.",
25
+ });
26
+
27
+ console.log(message.id, message.quotaRemaining);
28
+ ```
29
+
30
+ The key comes from [the dashboard](https://app.whatsapi.sh/keys). With a single
31
+ connected number you never name a sender; with several, pass `from`.
32
+
33
+ ## Pair a number
34
+
35
+ A number is a slot on your account plus a real WhatsApp session linked to it. Create
36
+ the slot, then pair it by QR (scan from **WhatsApp → Linked devices**) or by an
37
+ 8-character code typed into the phone:
38
+
39
+ ```ts
40
+ const number = await wa.createNumber({ name: "Support" });
41
+
42
+ const pairing = await wa.pairNumber({ numberId: number.id, mode: "qr" });
43
+ if (pairing.qrCode) showToUser(pairing.qrCode); // base64 PNG, good for ~90s
44
+ ```
45
+
46
+ `pairNumber` is also the poll: call it again while `state` is `"pairing"` and no code
47
+ has been minted yet. A fresh QR can only be generated every few minutes, so reuse the
48
+ one you have (`isCached`, `generatedAt`, `remainingCooldown` all say where you stand).
49
+
50
+ ## One-time codes
51
+
52
+ `sendOtp` generates the code, sends it, and hands it back — verify it yourself, or let
53
+ us do it and keep nothing:
54
+
55
+ ```ts
56
+ const otp = await wa.sendOtp({ to: "+5511988887777" });
57
+ // …later, with what the user typed:
58
+ await wa.checkOtp({ to: "+5511988887777", code: "123456" });
59
+ ```
60
+
61
+ A wrong or expired code throws `OTP_INVALID` rather than answering "not verified" — a
62
+ check you forget to read can never look like a pass.
63
+
64
+ ## Receive events
65
+
66
+ One endpoint per account receives all five events, signed. Verify before you trust:
67
+
68
+ ```ts
69
+ import { verifyWebhook, WebhookSignatureError } from "@whatsapi.sh/sdk";
70
+
71
+ app.post("/webhooks/whatsapp", express.raw({ type: "application/json" }), async (req, res) => {
72
+ try {
73
+ const event = await verifyWebhook({
74
+ payload: req.body, // the RAW body, not the parsed one
75
+ signature: req.header("x-whatsapi-signature"),
76
+ secret: process.env.WHATSAPI_WEBHOOK_SECRET!,
77
+ });
78
+
79
+ if (event.type === "message.created") {
80
+ await reply(event.data.number.id, event.data.message.text ?? "");
81
+ }
82
+ res.sendStatus(200);
83
+ } catch (error) {
84
+ res.sendStatus(error instanceof WebhookSignatureError ? 400 : 500);
85
+ }
86
+ });
87
+ ```
88
+
89
+ `event.type` narrows `event.data`, so the compiler knows a `message.status` event has
90
+ `messageIds` and a `number.logged_out` event does not. Deliveries retry, so the same
91
+ `event.id` can arrive twice — record it and drop repeats.
92
+
93
+ ## Errors
94
+
95
+ Every failure is a `WhatsapiError` carrying the API's own `code`, plus `status`,
96
+ `retryAfter` and the `requestId` to quote at support.
97
+
98
+ ```ts
99
+ import { WhatsapiError } from "@whatsapi.sh/sdk";
100
+
101
+ try {
102
+ await wa.sendMessage({ to, text });
103
+ } catch (error) {
104
+ if (!(error instanceof WhatsapiError)) throw error;
105
+
106
+ if (error.code === "PAYMENT_REQUIRED") return askThemToUpgrade();
107
+ if (error.code === "NUMBER_NOT_CONNECTED") return askThemToPairAgain();
108
+ if (error.code === "GATEWAY_TIMEOUT") return watchFor(error.messageId); // see below
109
+ throw error;
110
+ }
111
+ ```
112
+
113
+ Two rules worth knowing before you write a retry loop of your own:
114
+
115
+ - **`GATEWAY_TIMEOUT` is not a failure.** WhatsApp did not confirm in time, but the
116
+ send was accepted and most likely delivered. Sending again sends twice. Watch for
117
+ `error.messageId` on the `message.status` webhook instead.
118
+ - **`PAYMENT_REQUIRED` never clears by waiting**, unlike `RATE_LIMIT_EXCEEDED` and
119
+ `QUOTA_EXCEEDED`, which carry a `retryAfter`.
120
+
121
+ This client retries **reads** (a failed `GET`, twice by default, honoring
122
+ `Retry-After`) and never retries a write. There is no idempotency key on the API yet,
123
+ so an automatic retry of a send would be a second real message to a real person — that
124
+ call is yours to make, deliberately.
125
+
126
+ ## Agents
127
+
128
+ If you are wiring up an LLM rather than an app, you probably want the MCP server
129
+ instead of this package: the same operations, as tools, at
130
+ `https://api.whatsapi.sh/mcp` with the same `wa_` key. See
131
+ [the MCP reference](https://docs.whatsapi.sh/mcp).
132
+
133
+ ## Before you ship
134
+
135
+ whatsapi.sh runs on the **unofficial** WhatsApp protocol — the same one WhatsApp Web
136
+ uses. It is not the WhatsApp Business API, there is no Meta approval, and WhatsApp can
137
+ restrict or ban a number that behaves like a broadcaster. Cold outreach, identical
138
+ messages at speed and large recipient lists are what gets numbers banned. Pace your
139
+ sends, message people who expect you, and do not put a number you cannot afford to
140
+ lose on it. We tell you this before you pair, not after.
141
+
142
+ ## Requirements
143
+
144
+ Node 20+, or any runtime with `fetch` and Web Crypto (Bun, Deno, Cloudflare Workers,
145
+ the browser — though a `wa_` key does not belong in a browser). ESM only; on Node
146
+ 22.12+ `require("@whatsapi.sh/sdk")` works too.
147
+
148
+ - Docs: [docs.whatsapi.sh](https://docs.whatsapi.sh)
149
+ - REST reference: [docs.whatsapi.sh/reference](https://docs.whatsapi.sh/reference)
@@ -0,0 +1,35 @@
1
+ import { GeneratedOperations } from "./generated/operations.ts";
2
+ export interface WhatsapiOptions {
3
+ /** A `wa_` key from the dashboard. */
4
+ apiKey: string;
5
+ /** Defaults to the hosted API; point it at a self-hosted deployment if you run one. */
6
+ baseUrl?: string;
7
+ /** Per-attempt timeout in ms. Default 30000. */
8
+ timeout?: number;
9
+ /** Extra attempts for READ calls only. Default 2, 0 disables. */
10
+ maxRetries?: number;
11
+ /** Swap the fetch implementation (tests, proxies, a custom agent). */
12
+ fetch?: typeof globalThis.fetch;
13
+ }
14
+ /** What a generated method hands the transport. */
15
+ export interface RequestSpec {
16
+ method: "GET" | "POST" | "PUT" | "DELETE";
17
+ /** Template with `:name` placeholders naming the params that fill them. */
18
+ path: string;
19
+ pathParams?: readonly string[];
20
+ /** Values the ROUTE fixes rather than the caller. */
21
+ fixed?: Readonly<Record<string, unknown>>;
22
+ }
23
+ export declare class Whatsapi extends GeneratedOperations {
24
+ private readonly apiKey;
25
+ private readonly baseUrl;
26
+ private readonly timeout;
27
+ private readonly maxRetries;
28
+ private readonly fetchImpl;
29
+ constructor(options: WhatsapiOptions | string);
30
+ protected request<T>(spec: RequestSpec, params?: object): Promise<T>;
31
+ /** How long to wait before repeating a read, or undefined to give up now. */
32
+ private retryDelay;
33
+ private attempt;
34
+ }
35
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAUA,OAAO,EAAoB,mBAAmB,EAAe,MAAM,2BAA2B,CAAC;AAE/F,MAAM,WAAW,eAAe;IAC5B,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACnC;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IACxB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC1C,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,qDAAqD;IACrD,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC7C;AA6BD,qBAAa,QAAS,SAAQ,mBAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;gBAExC,OAAO,EAAE,eAAe,GAAG,MAAM;cAqB7B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAgB9E,6EAA6E;IAC7E,OAAO,CAAC,UAAU;YAkBJ,OAAO;CA8ExB"}
package/dist/client.js ADDED
@@ -0,0 +1,169 @@
1
+ // The transport under every generated method: one request, one `{ data }`
2
+ // unwrap, one error taxonomy.
3
+ //
4
+ // The retry policy is the opinionated part, and it is deliberately narrow:
5
+ // READS retry, WRITES never do. There is no idempotency key on /v1, so a
6
+ // retried send is a second WhatsApp message to a real person and a second unit
7
+ // off the quota — worse than the failure it was papering over. Writes come back
8
+ // as a WhatsapiError carrying `code` and `retryAfter`, and the caller decides.
9
+ import { WhatsapiError } from "./errors.js";
10
+ import { DEFAULT_BASE_URL, GeneratedOperations, SDK_VERSION } from "./generated/operations.js";
11
+ /** A Retry-After we would honor beyond this is not a retry, it is a hang — the
12
+ * caller gets the error (with `retryAfter` on it) and schedules it themselves. */
13
+ const MAX_RETRY_DELAY_MS = 10_000;
14
+ /** How the API answers when it never got to answer at all. */
15
+ function statusCode(status) {
16
+ if (status === 503)
17
+ return "SERVICE_UNAVAILABLE";
18
+ if (status === 404)
19
+ return "NOT_FOUND";
20
+ if (status === 401)
21
+ return "UNAUTHORIZED";
22
+ return "INTERNAL_ERROR";
23
+ }
24
+ /** Seconds from `Retry-After` (delta form) or the error's own details. */
25
+ function retryAfterOf(header, details) {
26
+ const fromDetails = typeof details === "object" && details !== null
27
+ ? details.retryAfterSecs
28
+ : undefined;
29
+ if (typeof fromDetails === "number" && Number.isFinite(fromDetails))
30
+ return fromDetails;
31
+ const seconds = header === null ? NaN : Number(header);
32
+ return Number.isFinite(seconds) ? seconds : undefined;
33
+ }
34
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
35
+ export class Whatsapi extends GeneratedOperations {
36
+ apiKey;
37
+ baseUrl;
38
+ timeout;
39
+ maxRetries;
40
+ fetchImpl;
41
+ constructor(options) {
42
+ super();
43
+ const opts = typeof options === "string" ? { apiKey: options } : options;
44
+ if (!opts.apiKey)
45
+ throw new TypeError("whatsapi: an API key is required — create one at https://app.whatsapi.sh/keys");
46
+ this.apiKey = opts.apiKey;
47
+ // Trailing slashes would double up against the leading slash of a path.
48
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
49
+ this.timeout = opts.timeout ?? 30_000;
50
+ this.maxRetries = opts.maxRetries ?? 2;
51
+ const impl = opts.fetch ?? globalThis.fetch;
52
+ if (typeof impl !== "function")
53
+ throw new TypeError("whatsapi: no global fetch — use Node 20+, or pass `fetch` in the options.");
54
+ // Bound: an unbound global fetch throws "Illegal invocation" in browsers.
55
+ this.fetchImpl = opts.fetch ?? impl.bind(globalThis);
56
+ }
57
+ async request(spec, params = {}) {
58
+ // Reads are the only calls we may repeat: a re-sent message is a second
59
+ // message to a real person, and no header on /v1 makes it idempotent.
60
+ const retries = spec.method === "GET" ? this.maxRetries : 0;
61
+ for (let attempt = 0;; attempt++) {
62
+ try {
63
+ return await this.attempt(spec, params);
64
+ }
65
+ catch (error) {
66
+ if (!(error instanceof WhatsapiError) || attempt >= retries)
67
+ throw error;
68
+ const delay = this.retryDelay(error, attempt);
69
+ if (delay === undefined)
70
+ throw error;
71
+ await sleep(delay);
72
+ }
73
+ }
74
+ }
75
+ /** How long to wait before repeating a read, or undefined to give up now. */
76
+ retryDelay(error, attempt) {
77
+ const worthRepeating = error.code === "NETWORK_ERROR" ||
78
+ error.code === "TIMEOUT" ||
79
+ error.code === "RATE_LIMIT_EXCEEDED" ||
80
+ // A quota clears when the month rolls over and a pairing cooldown
81
+ // runs for minutes: both are answers, not hiccups.
82
+ (error.status >= 500 && error.code !== "GATEWAY_TIMEOUT");
83
+ if (!worthRepeating)
84
+ return undefined;
85
+ if (error.retryAfter !== undefined) {
86
+ const ms = error.retryAfter * 1000;
87
+ return ms > MAX_RETRY_DELAY_MS ? undefined : ms;
88
+ }
89
+ // Exponential with jitter, so a fleet of clients does not resynchronize
90
+ // onto the same second after an outage.
91
+ return Math.min(250 * 2 ** attempt, 4_000) * (0.5 + Math.random());
92
+ }
93
+ async attempt(spec, params) {
94
+ const rest = { ...params };
95
+ let path = spec.path;
96
+ for (const name of spec.pathParams ?? []) {
97
+ const value = rest[name];
98
+ if (value === undefined || value === null || value === "")
99
+ throw new TypeError(`whatsapi: \`${name}\` is required`);
100
+ delete rest[name];
101
+ path = path.replace(`:${name}`, encodeURIComponent(String(value)));
102
+ }
103
+ const url = new URL(this.baseUrl + path);
104
+ const hasBody = spec.method === "POST" || spec.method === "PUT";
105
+ if (!hasBody)
106
+ for (const [key, value] of Object.entries(rest))
107
+ if (value !== undefined && value !== null)
108
+ url.searchParams.set(key, String(value));
109
+ const headers = {
110
+ authorization: `Bearer ${this.apiKey}`,
111
+ accept: "application/json",
112
+ "x-whatsapi-client": `whatsapi-js/${SDK_VERSION}`,
113
+ };
114
+ if (hasBody)
115
+ headers["content-type"] = "application/json";
116
+ let response;
117
+ try {
118
+ response = await this.fetchImpl(url.toString(), {
119
+ method: spec.method,
120
+ headers,
121
+ ...(hasBody
122
+ ? { body: JSON.stringify({ ...rest, ...(spec.fixed ?? {}) }) }
123
+ : undefined),
124
+ signal: AbortSignal.timeout(this.timeout),
125
+ });
126
+ }
127
+ catch (cause) {
128
+ const timedOut = cause instanceof Error && cause.name === "TimeoutError";
129
+ throw new WhatsapiError({
130
+ code: (timedOut ? "TIMEOUT" : "NETWORK_ERROR"),
131
+ status: 0,
132
+ message: timedOut
133
+ ? `whatsapi: no answer within ${this.timeout}ms — the call may still have gone through.`
134
+ : `whatsapi: could not reach ${url.host}.`,
135
+ cause,
136
+ });
137
+ }
138
+ const requestId = response.headers.get("x-request-id") ?? undefined;
139
+ const text = await response.text();
140
+ let payload;
141
+ try {
142
+ payload = text ? JSON.parse(text) : undefined;
143
+ }
144
+ catch {
145
+ payload = undefined;
146
+ }
147
+ if (response.ok)
148
+ return payload.data;
149
+ const envelope = typeof payload === "object" && payload !== null
150
+ ? payload.error
151
+ : undefined;
152
+ const details = envelope?.details;
153
+ throw new WhatsapiError({
154
+ code: envelope?.code ?? statusCode(response.status),
155
+ status: response.status,
156
+ // A proxy or a cold start can answer HTML; say what actually came back
157
+ // rather than pretending the body was ours.
158
+ message: typeof envelope?.message === "string"
159
+ ? envelope.message
160
+ : `whatsapi: ${spec.method} ${path} failed with ${response.status}.`,
161
+ messageKey: typeof envelope?.messageKey === "string" ? envelope.messageKey : undefined,
162
+ params: envelope?.params,
163
+ details: envelope ? details : text.slice(0, 500) || undefined,
164
+ retryAfter: retryAfterOf(response.headers.get("retry-after"), details),
165
+ requestId,
166
+ });
167
+ }
168
+ }
169
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { ErrorCode } from "./generated/contract.ts";
2
+ /** Failures that never reached the API, so they carry no server code. */
3
+ export type ClientErrorCode = "NETWORK_ERROR" | "TIMEOUT";
4
+ export interface WhatsapiErrorInit {
5
+ code: ErrorCode | ClientErrorCode;
6
+ message: string;
7
+ /** HTTP status, or 0 when no answer arrived. */
8
+ status: number;
9
+ messageKey?: string;
10
+ params?: Record<string, string | number>;
11
+ details?: unknown;
12
+ /** Seconds until the call is worth making again, when the API said. */
13
+ retryAfter?: number;
14
+ /** `X-Request-ID` — quote it in a support request and we can find the call. */
15
+ requestId?: string;
16
+ cause?: unknown;
17
+ }
18
+ export declare class WhatsapiError extends Error {
19
+ readonly code: ErrorCode | ClientErrorCode;
20
+ readonly status: number;
21
+ readonly messageKey?: string;
22
+ readonly params?: Record<string, string | number>;
23
+ readonly details?: unknown;
24
+ readonly retryAfter?: number;
25
+ readonly requestId?: string;
26
+ constructor(init: WhatsapiErrorInit);
27
+ /** The id a timed-out send went out with.
28
+ *
29
+ * `GATEWAY_TIMEOUT` means WhatsApp did not confirm in time — NOT that the
30
+ * message failed. It was accepted upstream and most likely delivered, so
31
+ * sending it again sends it twice. Watch for this id on the
32
+ * `message.status` webhook instead. */
33
+ get messageId(): string | undefined;
34
+ }
35
+ /** A delivery whose signature did not check out — treat the body as hostile and
36
+ * answer 400. Never a WhatsapiError: nothing here came from a call we made. */
37
+ export declare class WebhookSignatureError extends Error {
38
+ constructor(message: string);
39
+ }
40
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,yEAAyE;AACzE,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,SAAS,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,SAAS,GAAG,eAAe,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,aAAc,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,IAAI,EAAE,iBAAiB;IAYnC;;;;;4CAKwC;IACxC,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAMlC;CACJ;AAED;gFACgF;AAChF,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B"}
package/dist/errors.js ADDED
@@ -0,0 +1,42 @@
1
+ export class WhatsapiError extends Error {
2
+ code;
3
+ status;
4
+ messageKey;
5
+ params;
6
+ details;
7
+ retryAfter;
8
+ requestId;
9
+ constructor(init) {
10
+ super(init.message, init.cause !== undefined ? { cause: init.cause } : undefined);
11
+ this.name = "WhatsapiError";
12
+ this.code = init.code;
13
+ this.status = init.status;
14
+ this.messageKey = init.messageKey;
15
+ this.params = init.params;
16
+ this.details = init.details;
17
+ this.retryAfter = init.retryAfter;
18
+ this.requestId = init.requestId;
19
+ }
20
+ /** The id a timed-out send went out with.
21
+ *
22
+ * `GATEWAY_TIMEOUT` means WhatsApp did not confirm in time — NOT that the
23
+ * message failed. It was accepted upstream and most likely delivered, so
24
+ * sending it again sends it twice. Watch for this id on the
25
+ * `message.status` webhook instead. */
26
+ get messageId() {
27
+ const details = this.details;
28
+ if (this.code !== "GATEWAY_TIMEOUT" || typeof details !== "object" || details === null)
29
+ return undefined;
30
+ const id = details.messageId;
31
+ return typeof id === "string" ? id : undefined;
32
+ }
33
+ }
34
+ /** A delivery whose signature did not check out — treat the body as hostile and
35
+ * answer 400. Never a WhatsapiError: nothing here came from a call we made. */
36
+ export class WebhookSignatureError extends Error {
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = "WebhookSignatureError";
40
+ }
41
+ }
42
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AA2BA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC3B,IAAI,CAA8B;IAClC,MAAM,CAAS;IACf,UAAU,CAAU;IACpB,MAAM,CAAmC;IACzC,OAAO,CAAW;IAClB,UAAU,CAAU;IACpB,SAAS,CAAU;IAE5B,YAAY,IAAuB;QAC/B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACpC,CAAC;IAED;;;;;4CAKwC;IACxC,IAAI,SAAS;QACT,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;YAClF,OAAO,SAAS,CAAC;QACrB,MAAM,EAAE,GAAI,OAAmC,CAAC,SAAS,CAAC;QAC1D,OAAO,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACnD,CAAC;CACJ;AAED;gFACgF;AAChF,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACxC,CAAC;CACJ"}