@wassist/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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (Unreleased)
4
+
5
+ Initial release.
6
+
7
+ - Typed client for the public Wassist REST API: `agents`, `conversations` (+ `messages`), `phoneNumbers`, `whatsappAccounts`, `whatsappLinkSessions`, `whatsappTemplates`.
8
+ - Auto-paginating list endpoints with `for await` support and `.firstPage()`.
9
+ - Automatic retries on `429` and `5xx` with exponential backoff and `Retry-After` honoring.
10
+ - Per-call idempotency keys for `POST` mutations (`Idempotency-Key` header).
11
+ - Typed error hierarchy with `statusCode`, `code`, `requestId`, and raw response body.
12
+ - Stripe-style webhook signature verification via `wassist.webhooks.constructEvent()` (sync) and `constructEventAsync()` (Web Crypto, for edge runtimes).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wassist
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,282 @@
1
+ # Wassist SDK
2
+
3
+ The TypeScript client for the [Wassist API](https://docs.wassist.app/api-reference). Build WhatsApp agents, send messages, manage templates, and verify webhooks from any modern JavaScript runtime.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@wassist/sdk.svg)](https://www.npmjs.com/package/@wassist/sdk)
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @wassist/sdk
11
+ ```
12
+
13
+ Requires Node.js >= 18, or any runtime with a global `fetch` (Deno, Bun, Cloudflare Workers, Vercel Edge, modern browsers).
14
+
15
+ ## Quickstart
16
+
17
+ ```ts
18
+ import { Wassist } from '@wassist/sdk';
19
+
20
+ const wassist = new Wassist({ apiKey: process.env.WASSIST_API_KEY! });
21
+
22
+ // Create an agent
23
+ const agent = await wassist.agents.create({ name: 'Sales Assistant' });
24
+
25
+ // Configure it
26
+ await wassist.agents.update(agent.id, {
27
+ systemPrompt: 'You help customers pick the right product.',
28
+ firstMessage: 'Hi! How can I help today?',
29
+ });
30
+
31
+ // Start a conversation
32
+ const conversation = await wassist.conversations.create({
33
+ toNumber: '+447700900100',
34
+ agentId: agent.id,
35
+ });
36
+
37
+ // Send a message
38
+ await wassist.conversations.messages.send(conversation.id, {
39
+ type: 'text',
40
+ text: { body: 'Hello!' },
41
+ });
42
+
43
+ // Walk every conversation lazily
44
+ for await (const c of wassist.conversations.list()) {
45
+ console.log(c.id, c.lastMessage?.body);
46
+ }
47
+ ```
48
+
49
+ Get your API key at [wassist.app/settings](https://wassist.app/settings).
50
+
51
+ ## Configuration
52
+
53
+ ```ts
54
+ const wassist = new Wassist({
55
+ apiKey: process.env.WASSIST_API_KEY!, // required
56
+ baseUrl: 'https://backend.wassist.app', // default
57
+ timeout: 60_000, // ms; default 60s
58
+ maxRetries: 2, // default 2 (so 3 attempts total)
59
+ fetch: customFetch, // optional override
60
+ });
61
+ ```
62
+
63
+ ## Resources
64
+
65
+ | Namespace | Methods |
66
+ |-----------|---------|
67
+ | `wassist.agents` | `list`, `get`, `create`, `createBYOA`, `update`, `delete` |
68
+ | `wassist.conversations` | `list`, `get`, `create`, `read`, `typing`, `prompt`, `subscribe`, `unsubscribe` |
69
+ | `wassist.conversations.messages` | `list`, `send` |
70
+ | `wassist.phoneNumbers` | `list`, `getBusinessProfile`, `subscribe`, `connectAgent`, `unsubscribe` |
71
+ | `wassist.whatsappAccounts` | `list`, `get`, `addNumber`, `availableNumbers` |
72
+ | `wassist.whatsappLinkSessions` | `list`, `get`, `create`, `expire` |
73
+ | `wassist.whatsappTemplates` | `list`, `get`, `create`, `update`, `delete`, `publish`, `unpublish` |
74
+ | `wassist.webhooks` | `constructEvent`, `constructEventAsync` |
75
+
76
+ Every method is fully typed against the [public API reference](https://docs.wassist.app/api-reference) — your editor knows every field on every input and response.
77
+
78
+ ## Pagination
79
+
80
+ List methods return a lazy `AutoPaginatedList<T>`. Use `for await` to walk all pages, or `.firstPage()` for a single page.
81
+
82
+ ```ts
83
+ // Iterate every conversation (pages are fetched on demand)
84
+ for await (const conv of wassist.conversations.list({ limit: 100 })) {
85
+ // ...
86
+ }
87
+
88
+ // One page at a time
89
+ const { data, hasMore, nextOffset } = await wassist.conversations
90
+ .list({ limit: 20 })
91
+ .firstPage();
92
+
93
+ // Eagerly collect everything (small lists only)
94
+ const allAgents = await wassist.agents.list().all();
95
+ ```
96
+
97
+ ## Errors
98
+
99
+ Every API failure throws a typed subclass of `WassistError`. Branch on `instanceof` to handle each case.
100
+
101
+ ```ts
102
+ import {
103
+ WassistError,
104
+ WassistAuthenticationError,
105
+ WassistInvalidRequestError,
106
+ WassistNotFoundError,
107
+ WassistRateLimitError,
108
+ WassistTimeoutError,
109
+ } from '@wassist/sdk';
110
+
111
+ try {
112
+ await wassist.agents.get('not-a-real-id');
113
+ } catch (err) {
114
+ if (err instanceof WassistAuthenticationError) {
115
+ // 401 — bad/missing API key
116
+ } else if (err instanceof WassistNotFoundError) {
117
+ // 404
118
+ } else if (err instanceof WassistRateLimitError) {
119
+ console.log('retry after', err.retryAfter, 'seconds');
120
+ } else if (err instanceof WassistInvalidRequestError) {
121
+ // 400 / 422
122
+ } else if (err instanceof WassistTimeoutError) {
123
+ // request didn't complete within `timeout` ms
124
+ } else if (err instanceof WassistError) {
125
+ console.log(err.statusCode, err.code, err.requestId, err.raw);
126
+ }
127
+ }
128
+ ```
129
+
130
+ Every error carries `statusCode`, `code`, and the `requestId` from the `X-Request-Id` response header — quote it when filing support tickets.
131
+
132
+ ## Retries and idempotency
133
+
134
+ The SDK automatically retries `429`, `408`, `425`, `5xx`, and network failures with exponential backoff + jitter. `Retry-After` headers on `429` are honored.
135
+
136
+ For mutations that need to be safe against retries, pass an `idempotencyKey`:
137
+
138
+ ```ts
139
+ import { randomUUID } from 'node:crypto';
140
+
141
+ const conv = await wassist.conversations.create(
142
+ { toNumber: '+447700900100', agentId },
143
+ { idempotencyKey: randomUUID() }
144
+ );
145
+ ```
146
+
147
+ The key is forwarded as the `Idempotency-Key` header; the server deduplicates within a 24-hour window.
148
+
149
+ You can also tune retries and timeouts per call:
150
+
151
+ ```ts
152
+ await wassist.agents.list({}, { timeout: 5_000, maxRetries: 0 });
153
+ ```
154
+
155
+ And cancel any request:
156
+
157
+ ```ts
158
+ const controller = new AbortController();
159
+ setTimeout(() => controller.abort(), 1000);
160
+ await wassist.agents.list({}, { signal: controller.signal });
161
+ ```
162
+
163
+ ## Webhook signature verification
164
+
165
+ Wassist signs every webhook delivery with HMAC-SHA256 using the Stripe-style `t=<unix>,v1=<hex>` header format. The SDK gives you a one-liner to verify it.
166
+
167
+ ### Node.js / Express
168
+
169
+ ```ts
170
+ import express from 'express';
171
+ import { Wassist, WassistSignatureVerificationError, type WassistEvent } from '@wassist/sdk';
172
+
173
+ const app = express();
174
+
175
+ app.post(
176
+ '/webhooks/wassist',
177
+ express.raw({ type: 'application/json' }),
178
+ (req, res) => {
179
+ let event: WassistEvent;
180
+ try {
181
+ event = Wassist.webhooks.constructEvent(
182
+ req.body, // raw Buffer — DON'T parse first
183
+ req.header('x-wassist-signature') ?? '',
184
+ process.env.WASSIST_WEBHOOK_SECRET!
185
+ );
186
+ } catch (err) {
187
+ if (err instanceof WassistSignatureVerificationError) {
188
+ return res.status(400).send(err.message);
189
+ }
190
+ throw err;
191
+ }
192
+
193
+ switch (event.event) {
194
+ case 'message.received':
195
+ // event.message, event.from, event.conversationId, ...
196
+ break;
197
+ case 'subscription.activated':
198
+ case 'subscription.revoked':
199
+ // event.webhookId, event.conversationId
200
+ break;
201
+ case 'test.ping':
202
+ // dashboard "Send test event" button
203
+ break;
204
+ }
205
+ res.status(200).send('ok');
206
+ }
207
+ );
208
+ ```
209
+
210
+ ### Cloudflare Workers / Deno / Edge runtimes
211
+
212
+ Use the async variant — it runs entirely on Web Crypto:
213
+
214
+ ```ts
215
+ export default {
216
+ async fetch(request: Request, env: { WASSIST_WEBHOOK_SECRET: string }) {
217
+ const body = await request.text();
218
+ try {
219
+ const event = await Wassist.webhooks.constructEventAsync(
220
+ body,
221
+ request.headers.get('x-wassist-signature'),
222
+ env.WASSIST_WEBHOOK_SECRET
223
+ );
224
+ // ... handle event
225
+ return new Response('ok');
226
+ } catch {
227
+ return new Response('bad signature', { status: 400 });
228
+ }
229
+ },
230
+ };
231
+ ```
232
+
233
+ ### Replay protection
234
+
235
+ By default the SDK rejects events whose timestamp is more than **300 seconds** off from now. Tune or disable:
236
+
237
+ ```ts
238
+ Wassist.webhooks.constructEvent(body, header, secret, { tolerance: 600 });
239
+ Wassist.webhooks.constructEvent(body, header, secret, { tolerance: 0 }); // off (not recommended)
240
+ ```
241
+
242
+ ## Custom fetch / runtimes
243
+
244
+ The SDK uses `globalThis.fetch` by default. Inject your own — for tests, retries, tracing, or older Node:
245
+
246
+ ```ts
247
+ const wassist = new Wassist({
248
+ apiKey: '...',
249
+ fetch: async (url, init) => {
250
+ console.log('->', init?.method ?? 'GET', url);
251
+ return fetch(url, init);
252
+ },
253
+ });
254
+ ```
255
+
256
+ ## TypeScript
257
+
258
+ The package is published with full `.d.ts` types. Every response, input, and event is typed against the public Wassist API:
259
+
260
+ ```ts
261
+ import type {
262
+ Agent,
263
+ Conversation,
264
+ Message,
265
+ PhoneNumber,
266
+ WhatsAppAccount,
267
+ WhatsAppTemplate,
268
+ WhatsAppLinkSession,
269
+ WassistEvent,
270
+ MessageReceivedEvent,
271
+ } from '@wassist/sdk';
272
+ ```
273
+
274
+ ## Support
275
+
276
+ - Documentation: [docs.wassist.app/api-reference](https://docs.wassist.app/api-reference)
277
+ - Email: [contact@wassist.app](mailto:contact@wassist.app)
278
+ - Issues: [github.com/wassist/sdk/issues](https://github.com/wassist/sdk/issues)
279
+
280
+ ## License
281
+
282
+ MIT