@signingstudio/sdk 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,696 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ AuthenticationError: () => AuthenticationError,
25
+ Documents: () => Documents,
26
+ NotFoundError: () => NotFoundError,
27
+ RateLimitError: () => RateLimitError,
28
+ SigningStudio: () => SigningStudio,
29
+ SigningStudioError: () => SigningStudioError,
30
+ Templates: () => Templates,
31
+ ValidationError: () => ValidationError,
32
+ verifyWebhook: () => verifyWebhook
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/http.ts
37
+ var import_promises = require("timers/promises");
38
+ var import_undici = require("undici");
39
+
40
+ // src/errors/index.ts
41
+ var SigningStudioError = class extends Error {
42
+ constructor(message, options) {
43
+ super(message);
44
+ this.name = "SigningStudioError";
45
+ if (options?.cause !== void 0) {
46
+ this.cause = options.cause;
47
+ }
48
+ }
49
+ };
50
+ var ApiError = class extends SigningStudioError {
51
+ statusCode;
52
+ requestId;
53
+ body;
54
+ constructor(message, init) {
55
+ super(message);
56
+ this.name = "ApiError";
57
+ this.statusCode = init.statusCode;
58
+ this.requestId = init.requestId ?? null;
59
+ this.body = init.body ?? null;
60
+ }
61
+ };
62
+ var AuthenticationError = class extends ApiError {
63
+ constructor(message, init) {
64
+ super(message, init);
65
+ this.name = "AuthenticationError";
66
+ }
67
+ };
68
+ var NotFoundError = class extends ApiError {
69
+ constructor(message, init) {
70
+ super(message, init);
71
+ this.name = "NotFoundError";
72
+ }
73
+ };
74
+ var ValidationError = class extends ApiError {
75
+ errors;
76
+ constructor(message, init) {
77
+ super(message, init);
78
+ this.name = "ValidationError";
79
+ this.errors = init.errors;
80
+ }
81
+ };
82
+ var RateLimitError = class extends ApiError {
83
+ retryAfter;
84
+ window;
85
+ constructor(message, init) {
86
+ super(message, init);
87
+ this.name = "RateLimitError";
88
+ this.retryAfter = init.retryAfter;
89
+ this.window = init.window;
90
+ }
91
+ };
92
+
93
+ // src/http.ts
94
+ var HttpClient = class {
95
+ opts;
96
+ constructor(opts) {
97
+ this.opts = {
98
+ ...opts,
99
+ fetchImpl: opts.fetchImpl ?? import_undici.request
100
+ };
101
+ }
102
+ /**
103
+ * JSON-returning request. Callers get the parsed data + envelope meta +
104
+ * rate-limit + request-id, or a typed error.
105
+ */
106
+ async requestJson(req) {
107
+ const raw = await this.execute(req, "json");
108
+ return raw;
109
+ }
110
+ /**
111
+ * Binary-returning request (PDF download). Skips JSON parsing; returns
112
+ * the raw body as a Uint8Array.
113
+ */
114
+ async requestBinary(req) {
115
+ const raw = await this.execute({ ...req, binary: true }, "binary");
116
+ return raw;
117
+ }
118
+ async execute(req, kind) {
119
+ const url = this.buildUrl(req.path, req.query);
120
+ const headers = {
121
+ authorization: `Bearer ${this.opts.apiKey}`,
122
+ accept: kind === "binary" ? "application/pdf, application/octet-stream, */*" : "application/json",
123
+ "user-agent": this.opts.userAgent
124
+ };
125
+ let body;
126
+ if (req.form !== void 0) {
127
+ body = req.form;
128
+ } else if (req.json !== void 0) {
129
+ body = JSON.stringify(req.json);
130
+ headers["content-type"] = "application/json";
131
+ }
132
+ let attempt = 0;
133
+ while (true) {
134
+ let response;
135
+ try {
136
+ response = await this.opts.fetchImpl(url, {
137
+ method: req.method,
138
+ headers,
139
+ // Only include `body` when we actually have one — undici's
140
+ // RequestOptions.body doesn't accept undefined under
141
+ // exactOptionalPropertyTypes, so conditional-spread it.
142
+ ...body !== void 0 ? { body } : {},
143
+ bodyTimeout: this.opts.requestTimeoutMs,
144
+ headersTimeout: this.opts.requestTimeoutMs
145
+ });
146
+ } catch (err) {
147
+ if (attempt >= this.opts.maxRetries) {
148
+ throw new SigningStudioError(
149
+ `Network error contacting Signing Studio: ${err.message}`,
150
+ { cause: err }
151
+ );
152
+ }
153
+ await (0, import_promises.setTimeout)(this.backoffMs(attempt));
154
+ attempt += 1;
155
+ continue;
156
+ }
157
+ const status = response.statusCode;
158
+ if (status >= 200 && status < 300) {
159
+ if (kind === "binary") {
160
+ return await this.readBinary(response);
161
+ }
162
+ return await this.parseSuccess(response, status);
163
+ }
164
+ if (status === 429 && attempt < this.opts.maxRetries) {
165
+ const retryAfter = this.extractRetryAfter(response);
166
+ if (retryAfter !== null && retryAfter <= 60) {
167
+ await (0, import_promises.setTimeout)(retryAfter * 1e3);
168
+ attempt += 1;
169
+ continue;
170
+ }
171
+ }
172
+ if (status >= 500 && attempt < this.opts.maxRetries) {
173
+ await (0, import_promises.setTimeout)(this.backoffMs(attempt));
174
+ attempt += 1;
175
+ continue;
176
+ }
177
+ throw await this.buildApiError(response, status);
178
+ }
179
+ }
180
+ buildUrl(path, query) {
181
+ const base = `${this.opts.baseUrl.replace(/\/$/, "")}/api/v1/`;
182
+ const url = new URL(path.replace(/^\/+/, ""), base);
183
+ if (query) {
184
+ for (const [k, v] of Object.entries(query)) {
185
+ if (v === void 0 || v === null) continue;
186
+ url.searchParams.set(k, String(v));
187
+ }
188
+ }
189
+ return url.toString();
190
+ }
191
+ async parseSuccess(response, status) {
192
+ const bodyStr = await this.readText(response);
193
+ const requestId = this.headerString(response, "x-request-id");
194
+ if (bodyStr === "") {
195
+ return {
196
+ data: null,
197
+ meta: {},
198
+ requestId,
199
+ rateLimit: this.readRateLimit(response)
200
+ };
201
+ }
202
+ let parsed;
203
+ try {
204
+ parsed = JSON.parse(bodyStr);
205
+ } catch (err) {
206
+ throw new SigningStudioError(
207
+ `Malformed JSON in server response (HTTP ${status})`,
208
+ { cause: err }
209
+ );
210
+ }
211
+ const envelope = typeof parsed === "object" && parsed !== null ? parsed : {};
212
+ return {
213
+ data: envelope.data ?? null,
214
+ meta: typeof envelope.meta === "object" && envelope.meta !== null ? envelope.meta : {},
215
+ requestId,
216
+ rateLimit: this.readRateLimit(response)
217
+ };
218
+ }
219
+ readRateLimit(response) {
220
+ return {
221
+ remainingMinute: this.parseIntHeader(response, "x-ratelimit-remaining-minute"),
222
+ remainingDay: this.parseIntHeader(response, "x-ratelimit-remaining-day"),
223
+ limitMinute: this.parseIntHeader(response, "x-ratelimit-limit-minute"),
224
+ limitDay: this.parseIntHeader(response, "x-ratelimit-limit-day")
225
+ };
226
+ }
227
+ async buildApiError(response, status) {
228
+ let body = null;
229
+ let message = `Signing Studio API returned HTTP ${status}`;
230
+ let requestId = this.headerString(response, "x-request-id");
231
+ const raw = await this.readText(response);
232
+ if (raw !== "") {
233
+ try {
234
+ const parsed = JSON.parse(raw);
235
+ if (typeof parsed === "object" && parsed !== null) {
236
+ body = parsed;
237
+ if (typeof body.message === "string") message = body.message;
238
+ if (!requestId && typeof body.requestId === "string") requestId = body.requestId;
239
+ }
240
+ } catch {
241
+ }
242
+ }
243
+ const init = { statusCode: status, requestId, body };
244
+ switch (status) {
245
+ case 401:
246
+ return new AuthenticationError(message, init);
247
+ case 404:
248
+ return new NotFoundError(message, init);
249
+ case 422: {
250
+ const errors = body && typeof body.errors === "object" && body.errors !== null ? body.errors : {};
251
+ return new ValidationError(message, { ...init, errors });
252
+ }
253
+ case 429:
254
+ return new RateLimitError(message, {
255
+ ...init,
256
+ retryAfter: this.extractRetryAfter(response),
257
+ window: this.headerString(response, "x-ratelimit-window")
258
+ });
259
+ default:
260
+ return new ApiError(message, init);
261
+ }
262
+ }
263
+ extractRetryAfter(response) {
264
+ const raw = this.headerString(response, "retry-after");
265
+ if (raw === null) return null;
266
+ const n = Number(raw);
267
+ return Number.isFinite(n) ? Math.trunc(n) : null;
268
+ }
269
+ headerString(response, name) {
270
+ const value = response.headers[name];
271
+ if (value === void 0) return null;
272
+ if (Array.isArray(value)) return value[0] ?? null;
273
+ return String(value);
274
+ }
275
+ parseIntHeader(response, name) {
276
+ const s = this.headerString(response, name);
277
+ if (s === null) return null;
278
+ const n = Number(s);
279
+ return Number.isFinite(n) ? Math.trunc(n) : null;
280
+ }
281
+ async readText(response) {
282
+ try {
283
+ return await response.body.text();
284
+ } catch {
285
+ return "";
286
+ }
287
+ }
288
+ async readBinary(response) {
289
+ const chunks = [];
290
+ for await (const chunk of response.body) {
291
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
292
+ }
293
+ return new Uint8Array(Buffer.concat(chunks));
294
+ }
295
+ /**
296
+ * Exponential backoff (500 ms · 1 s · 2 s · 4 s) plus a small jitter to
297
+ * avoid herd effects when many callers retry at once.
298
+ */
299
+ backoffMs(attempt) {
300
+ const base = 500 * 2 ** attempt;
301
+ const jitter = Math.floor(Math.random() * (base / 4));
302
+ return base + jitter;
303
+ }
304
+ };
305
+
306
+ // src/resources/documents.ts
307
+ var Documents = class {
308
+ constructor(http) {
309
+ this.http = http;
310
+ }
311
+ http;
312
+ /**
313
+ * List documents. All parameters are optional.
314
+ *
315
+ * The `view` toggle controls which lifecycle bucket to read from:
316
+ * - `active` (default) — non-archived, non-deleted
317
+ * - `archived` — archived only
318
+ * - `deleted` — soft-deleted only
319
+ * - `all` — active + archived, still excludes trash
320
+ */
321
+ async list(params = {}) {
322
+ const response = await this.http.requestJson({
323
+ method: "GET",
324
+ path: "documents",
325
+ query: {
326
+ status: params.status,
327
+ view: params.view,
328
+ limit: params.limit,
329
+ offset: params.offset
330
+ }
331
+ });
332
+ return {
333
+ data: response.data ?? [],
334
+ meta: response.meta ?? {
335
+ total: 0,
336
+ limit: 50,
337
+ offset: 0,
338
+ view: params.view ?? "active"
339
+ }
340
+ };
341
+ }
342
+ /**
343
+ * Send a new document built from a template.
344
+ *
345
+ * Server-side rules to be aware of (surfaced as {@link import("../errors/index.js").ValidationError}):
346
+ * - `template_id` is required.
347
+ * - At least one recipient must be supplied.
348
+ * - `prefill_values[]` needs either `field_id` or `field_name` + `value`.
349
+ * - Signature / initials fields can't be prefilled.
350
+ * - Any template field that is both required AND readonly must be prefilled.
351
+ *
352
+ * Successful sends count against the account's monthly document quota.
353
+ */
354
+ async send(payload) {
355
+ const response = await this.http.requestJson({
356
+ method: "POST",
357
+ path: "documents",
358
+ json: payload
359
+ });
360
+ return response.data;
361
+ }
362
+ async get(id) {
363
+ const response = await this.http.requestJson({
364
+ method: "GET",
365
+ path: `documents/${encodeURIComponent(id)}`
366
+ });
367
+ return response.data;
368
+ }
369
+ /**
370
+ * Delete a document. `hard: true` also purges the file from storage.
371
+ * Only valid on cancelled / completed / declined / expired documents;
372
+ * cancel first if the document is still in flight.
373
+ */
374
+ async delete(id, options = {}) {
375
+ const response = await this.http.requestJson({
376
+ method: "DELETE",
377
+ path: `documents/${encodeURIComponent(id)}`,
378
+ ...options.hard && { query: { hard: "true" } }
379
+ });
380
+ return response.data;
381
+ }
382
+ async restore(id) {
383
+ return (await this.http.requestJson({
384
+ method: "POST",
385
+ path: `documents/${encodeURIComponent(id)}/restore`
386
+ })).data;
387
+ }
388
+ async archive(id) {
389
+ return (await this.http.requestJson({
390
+ method: "POST",
391
+ path: `documents/${encodeURIComponent(id)}/archive`
392
+ })).data;
393
+ }
394
+ async unarchive(id) {
395
+ return (await this.http.requestJson({
396
+ method: "POST",
397
+ path: `documents/${encodeURIComponent(id)}/unarchive`
398
+ })).data;
399
+ }
400
+ async cancel(id) {
401
+ return (await this.http.requestJson({
402
+ method: "POST",
403
+ path: `documents/${encodeURIComponent(id)}/cancel`
404
+ })).data;
405
+ }
406
+ /**
407
+ * Read the current signing progress — cheaper than {@link get} when
408
+ * polling. Returns overall counts plus per-recipient state, no field
409
+ * or template data.
410
+ */
411
+ async progress(id) {
412
+ return (await this.http.requestJson({
413
+ method: "GET",
414
+ path: `documents/${encodeURIComponent(id)}/progress`
415
+ })).data;
416
+ }
417
+ /**
418
+ * Get a presigned download URL for the document PDF. For completed
419
+ * documents this is the flattened final PDF plus the audit-trail page;
420
+ * while in flight it's the in-progress snapshot (or source PDF if no
421
+ * signatures yet).
422
+ */
423
+ async downloadUrl(id) {
424
+ return (await this.http.requestJson({
425
+ method: "GET",
426
+ path: `documents/${encodeURIComponent(id)}/download`
427
+ })).data;
428
+ }
429
+ /**
430
+ * Fetch the document PDF as raw bytes in one call (follows the presigned
431
+ * URL redirect server-side). Handy for saving straight to disk.
432
+ */
433
+ async downloadPdf(id) {
434
+ return await this.http.requestBinary({
435
+ method: "GET",
436
+ path: `documents/${encodeURIComponent(id)}/download`,
437
+ query: { redirect: "1" }
438
+ });
439
+ }
440
+ async activity(id) {
441
+ const response = await this.http.requestJson({
442
+ method: "GET",
443
+ path: `documents/${encodeURIComponent(id)}/activity`
444
+ });
445
+ return response.data ?? [];
446
+ }
447
+ /**
448
+ * Re-send the signing invite (or first invite, if it hasn't gone out
449
+ * yet) to a specific recipient. Response includes a freshly-minted
450
+ * signing URL — this is the only endpoint where a URL is returned in
451
+ * plaintext, so capture it if you need to relay it out-of-band.
452
+ */
453
+ async remind(documentId, recipientId) {
454
+ return (await this.http.requestJson({
455
+ method: "POST",
456
+ path: `documents/${encodeURIComponent(documentId)}/recipients/${encodeURIComponent(recipientId)}/remind`
457
+ })).data;
458
+ }
459
+ };
460
+
461
+ // src/resources/templates.ts
462
+ var import_promises2 = require("fs/promises");
463
+ var import_undici2 = require("undici");
464
+ var Templates = class {
465
+ constructor(http) {
466
+ this.http = http;
467
+ }
468
+ http;
469
+ async list() {
470
+ const response = await this.http.requestJson({
471
+ method: "GET",
472
+ path: "templates"
473
+ });
474
+ return response.data ?? [];
475
+ }
476
+ /**
477
+ * Create a new template from a PDF plus metadata.
478
+ *
479
+ * The `pdf` argument can be a filesystem path, a Uint8Array/Buffer of
480
+ * raw bytes, a Blob, or an object with an explicit filename. See
481
+ * {@link PdfInput}.
482
+ */
483
+ async create(pdf, metadata, options = {}) {
484
+ const form = await this.buildPdfForm(pdf, options.filename, metadata);
485
+ const response = await this.http.requestJson({
486
+ method: "POST",
487
+ path: "templates",
488
+ form
489
+ });
490
+ return response.data;
491
+ }
492
+ async get(id) {
493
+ return (await this.http.requestJson({
494
+ method: "GET",
495
+ path: `templates/${encodeURIComponent(id)}`
496
+ })).data;
497
+ }
498
+ async update(id, metadata) {
499
+ return (await this.http.requestJson({
500
+ method: "PATCH",
501
+ path: `templates/${encodeURIComponent(id)}`,
502
+ json: metadata
503
+ })).data;
504
+ }
505
+ /**
506
+ * Soft-delete. There is no hard-delete on this route; contact support
507
+ * if you need permanent removal.
508
+ */
509
+ async delete(id) {
510
+ return (await this.http.requestJson({
511
+ method: "DELETE",
512
+ path: `templates/${encodeURIComponent(id)}`
513
+ })).data;
514
+ }
515
+ async pdfUrl(id) {
516
+ return (await this.http.requestJson({
517
+ method: "GET",
518
+ path: `templates/${encodeURIComponent(id)}/pdf`
519
+ })).data;
520
+ }
521
+ async downloadPdf(id) {
522
+ return await this.http.requestBinary({
523
+ method: "GET",
524
+ path: `templates/${encodeURIComponent(id)}/pdf`,
525
+ query: { redirect: "1" }
526
+ });
527
+ }
528
+ /**
529
+ * Replace the template's PDF. Prior versions are kept in history —
530
+ * download via {@link historyPdfUrl}.
531
+ */
532
+ async replacePdf(id, pdf, options = {}) {
533
+ const form = await this.buildPdfForm(pdf, options.filename);
534
+ return (await this.http.requestJson({
535
+ method: "PUT",
536
+ path: `templates/${encodeURIComponent(id)}/pdf`,
537
+ form
538
+ })).data;
539
+ }
540
+ /**
541
+ * Replace ALL fields on the template. The server deletes existing rows
542
+ * and inserts the supplied set — the SDK does not support partial
543
+ * updates because the server doesn't.
544
+ */
545
+ async setFields(id, fields) {
546
+ const response = await this.http.requestJson({
547
+ method: "PUT",
548
+ path: `templates/${encodeURIComponent(id)}/fields`,
549
+ json: { fields }
550
+ });
551
+ return response.data ?? [];
552
+ }
553
+ async duplicate(id) {
554
+ return (await this.http.requestJson({
555
+ method: "POST",
556
+ path: `templates/${encodeURIComponent(id)}/duplicate`
557
+ })).data;
558
+ }
559
+ async archive(id) {
560
+ return (await this.http.requestJson({
561
+ method: "POST",
562
+ path: `templates/${encodeURIComponent(id)}/archive`
563
+ })).data;
564
+ }
565
+ async history(id) {
566
+ const response = await this.http.requestJson({
567
+ method: "GET",
568
+ path: `templates/${encodeURIComponent(id)}/history`
569
+ });
570
+ return response.data ?? [];
571
+ }
572
+ async historyPdfUrl(templateId, historyId) {
573
+ return (await this.http.requestJson({
574
+ method: "GET",
575
+ path: `templates/${encodeURIComponent(templateId)}/history/${historyId}/pdf`
576
+ })).data;
577
+ }
578
+ async downloadHistoryPdf(templateId, historyId) {
579
+ return await this.http.requestBinary({
580
+ method: "GET",
581
+ path: `templates/${encodeURIComponent(templateId)}/history/${historyId}/pdf`,
582
+ query: { redirect: "1" }
583
+ });
584
+ }
585
+ /**
586
+ * Build the multipart form: PDF blob under field name `file` (required
587
+ * by the server) + JSON-encoded scalar/array metadata fields.
588
+ *
589
+ * Nested arrays/objects (like `signers`, `delivery_methods`) are
590
+ * serialized with JSON.stringify so they survive the form-data trip —
591
+ * the server route unwraps them.
592
+ */
593
+ async buildPdfForm(pdf, filenameOverride, metadata) {
594
+ const form = new import_undici2.FormData();
595
+ if (metadata) {
596
+ for (const [key, value] of Object.entries(metadata)) {
597
+ if (value === void 0 || value === null) continue;
598
+ const encoded = typeof value === "string" ? value : JSON.stringify(value);
599
+ form.append(key, encoded);
600
+ }
601
+ }
602
+ const { blob, filename } = await this.resolvePdf(pdf, filenameOverride);
603
+ form.append("file", blob, filename);
604
+ return form;
605
+ }
606
+ async resolvePdf(pdf, override) {
607
+ if (typeof pdf === "string") {
608
+ const data = await (0, import_promises2.readFile)(pdf);
609
+ return {
610
+ blob: new Blob([data], { type: "application/pdf" }),
611
+ filename: override ?? basename(pdf)
612
+ };
613
+ }
614
+ if (pdf instanceof Uint8Array) {
615
+ return {
616
+ blob: new Blob([pdf], { type: "application/pdf" }),
617
+ filename: override ?? "template.pdf"
618
+ };
619
+ }
620
+ if (pdf instanceof Blob) {
621
+ return { blob: pdf, filename: override ?? "template.pdf" };
622
+ }
623
+ if ("path" in pdf) {
624
+ const data = await (0, import_promises2.readFile)(pdf.path);
625
+ return {
626
+ blob: new Blob([data], { type: "application/pdf" }),
627
+ filename: override ?? basename(pdf.path)
628
+ };
629
+ }
630
+ return {
631
+ blob: new Blob([pdf.data], { type: pdf.contentType ?? "application/pdf" }),
632
+ filename: override ?? pdf.filename ?? "template.pdf"
633
+ };
634
+ }
635
+ };
636
+ function basename(p) {
637
+ const trimmed = p.replace(/[\\/]+$/, "");
638
+ const idx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
639
+ return idx === -1 ? trimmed : trimmed.slice(idx + 1);
640
+ }
641
+
642
+ // src/client.ts
643
+ var SigningStudio = class {
644
+ documents;
645
+ templates;
646
+ /** Escape hatch — the raw HTTP layer for callers that need to consume
647
+ * headers (rate-limit remaining, request id) alongside the parsed data. */
648
+ http;
649
+ constructor(options) {
650
+ if (!options.apiKey) {
651
+ throw new TypeError("SigningStudio: `apiKey` is required");
652
+ }
653
+ this.http = new HttpClient({
654
+ apiKey: options.apiKey,
655
+ baseUrl: options.baseUrl ?? "https://api.signingstudio.com",
656
+ maxRetries: options.maxRetries ?? 3,
657
+ requestTimeoutMs: options.requestTimeoutMs ?? 12e4,
658
+ userAgent: options.userAgent ?? "signingstudio-node/1.0",
659
+ fetchImpl: options.fetchImpl ?? void 0
660
+ });
661
+ this.documents = new Documents(this.http);
662
+ this.templates = new Templates(this.http);
663
+ }
664
+ };
665
+
666
+ // src/webhooks.ts
667
+ var import_node_crypto = require("crypto");
668
+ var import_node_buffer = require("buffer");
669
+ function verifyWebhook(rawBody, header, secret) {
670
+ if (!secret || typeof secret !== "string") {
671
+ throw new TypeError("verifyWebhook: secret must be a non-empty string");
672
+ }
673
+ if (header == null || header === "") return false;
674
+ const received = header.startsWith("sha256=") ? header.slice("sha256=".length) : header;
675
+ if (received.length !== 64 || !/^[0-9a-f]+$/i.test(received)) return false;
676
+ const bodyBuf = typeof rawBody === "string" ? import_node_buffer.Buffer.from(rawBody, "utf8") : import_node_buffer.Buffer.from(rawBody);
677
+ const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(bodyBuf).digest("hex");
678
+ const receivedBuf = import_node_buffer.Buffer.from(received, "hex");
679
+ const expectedBuf = import_node_buffer.Buffer.from(expected, "hex");
680
+ if (receivedBuf.length !== expectedBuf.length) return false;
681
+ return (0, import_node_crypto.timingSafeEqual)(expectedBuf, receivedBuf);
682
+ }
683
+ // Annotate the CommonJS export names for ESM import in node:
684
+ 0 && (module.exports = {
685
+ ApiError,
686
+ AuthenticationError,
687
+ Documents,
688
+ NotFoundError,
689
+ RateLimitError,
690
+ SigningStudio,
691
+ SigningStudioError,
692
+ Templates,
693
+ ValidationError,
694
+ verifyWebhook
695
+ });
696
+ //# sourceMappingURL=index.cjs.map