vesant-sdk 1.7.0-dev.e1da577 → 1.7.0-dev.e92a808

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/{client-DF7hlMEz.d.ts → client-0NOPDnT0.d.ts} +16 -26
  2. package/dist/{client-DrjgZoH_.d.mts → client-CvWNRwwg.d.mts} +16 -26
  3. package/dist/{client-B0qhE2kr.d.mts → client-Dps40EtF.d.mts} +2 -2
  4. package/dist/{client-DtH2RLuy.d.ts → client-SMxqod4j.d.ts} +2 -2
  5. package/dist/{client-BolQlL5e.d.mts → client-q9fN8Hhf.d.mts} +103 -1
  6. package/dist/{client-BolQlL5e.d.ts → client-q9fN8Hhf.d.ts} +103 -1
  7. package/dist/compliance/index.d.mts +3 -3
  8. package/dist/compliance/index.d.ts +3 -3
  9. package/dist/compliance/index.js +133 -19
  10. package/dist/compliance/index.js.map +1 -1
  11. package/dist/compliance/index.mjs +133 -19
  12. package/dist/compliance/index.mjs.map +1 -1
  13. package/dist/decisions/index.d.mts +1 -1
  14. package/dist/decisions/index.d.ts +1 -1
  15. package/dist/decisions/index.js +68 -7
  16. package/dist/decisions/index.js.map +1 -1
  17. package/dist/decisions/index.mjs +68 -7
  18. package/dist/decisions/index.mjs.map +1 -1
  19. package/dist/geolocation/index.d.mts +59 -4
  20. package/dist/geolocation/index.d.ts +59 -4
  21. package/dist/geolocation/index.js +136 -19
  22. package/dist/geolocation/index.js.map +1 -1
  23. package/dist/geolocation/index.mjs +134 -20
  24. package/dist/geolocation/index.mjs.map +1 -1
  25. package/dist/index-C3zUKydT.d.mts +264 -0
  26. package/dist/index-DSDgS09k.d.ts +264 -0
  27. package/dist/index.d.mts +25 -8
  28. package/dist/index.d.ts +25 -8
  29. package/dist/index.js +304 -48
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +297 -49
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/kyc/core.d.mts +1 -1
  34. package/dist/kyc/core.d.ts +1 -1
  35. package/dist/kyc/core.js +74 -13
  36. package/dist/kyc/core.js.map +1 -1
  37. package/dist/kyc/core.mjs +74 -13
  38. package/dist/kyc/core.mjs.map +1 -1
  39. package/dist/kyc/index.d.mts +4 -4
  40. package/dist/kyc/index.d.ts +4 -4
  41. package/dist/kyc/index.js +74 -13
  42. package/dist/kyc/index.js.map +1 -1
  43. package/dist/kyc/index.mjs +74 -13
  44. package/dist/kyc/index.mjs.map +1 -1
  45. package/dist/react.d.mts +7 -3
  46. package/dist/react.d.ts +7 -3
  47. package/dist/react.js +68 -4
  48. package/dist/react.js.map +1 -1
  49. package/dist/react.mjs +68 -4
  50. package/dist/react.mjs.map +1 -1
  51. package/dist/risk-profile/index.d.mts +1 -1
  52. package/dist/risk-profile/index.d.ts +1 -1
  53. package/dist/risk-profile/index.js +68 -7
  54. package/dist/risk-profile/index.js.map +1 -1
  55. package/dist/risk-profile/index.mjs +68 -7
  56. package/dist/risk-profile/index.mjs.map +1 -1
  57. package/dist/scores/index.d.mts +1 -1
  58. package/dist/scores/index.d.ts +1 -1
  59. package/dist/scores/index.js +68 -7
  60. package/dist/scores/index.js.map +1 -1
  61. package/dist/scores/index.mjs +68 -7
  62. package/dist/scores/index.mjs.map +1 -1
  63. package/dist/tax/index.d.mts +2 -2
  64. package/dist/tax/index.d.ts +2 -2
  65. package/dist/tax/index.js +73 -9
  66. package/dist/tax/index.js.map +1 -1
  67. package/dist/tax/index.mjs +73 -9
  68. package/dist/tax/index.mjs.map +1 -1
  69. package/dist/webhooks/index.d.mts +2 -189
  70. package/dist/webhooks/index.d.ts +2 -189
  71. package/dist/webhooks/index.js +85 -21
  72. package/dist/webhooks/index.js.map +1 -1
  73. package/dist/webhooks/index.mjs +85 -22
  74. package/dist/webhooks/index.mjs.map +1 -1
  75. package/package.json +4 -1
package/dist/index.mjs CHANGED
@@ -1,3 +1,59 @@
1
+ // src/core/auth.ts
2
+ var StaticApiKeyProvider = class {
3
+ constructor(apiKey) {
4
+ this.apiKey = apiKey;
5
+ }
6
+ async getCredential() {
7
+ return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
8
+ }
9
+ canRefresh() {
10
+ return false;
11
+ }
12
+ };
13
+ var RefreshingTokenProvider = class {
14
+ constructor(opts) {
15
+ this.opts = opts;
16
+ this.cached = null;
17
+ this.inFlight = null;
18
+ this.skewMs = opts.skewMs ?? 3e4;
19
+ this.scheme = opts.scheme ?? "Bearer";
20
+ this.now = opts.now ?? (() => Date.now());
21
+ }
22
+ canRefresh() {
23
+ return true;
24
+ }
25
+ async getCredential(ctx) {
26
+ const forceRefresh = ctx?.forceRefresh ?? false;
27
+ if (!forceRefresh && this.cached && !this.isExpiring(this.cached)) {
28
+ return { scheme: this.scheme, token: this.cached.token };
29
+ }
30
+ const fresh = await this.refresh();
31
+ return { scheme: this.scheme, token: fresh.token };
32
+ }
33
+ isExpiring(token) {
34
+ if (token.expiresAt === void 0) {
35
+ return false;
36
+ }
37
+ return this.now() >= token.expiresAt - this.skewMs;
38
+ }
39
+ refresh() {
40
+ if (this.inFlight) {
41
+ return this.inFlight;
42
+ }
43
+ this.inFlight = (async () => {
44
+ try {
45
+ const fresh = await this.opts.fetchToken();
46
+ this.cached = fresh;
47
+ this.opts.onRotate?.({ expiresAt: fresh.expiresAt });
48
+ return fresh;
49
+ } finally {
50
+ this.inFlight = null;
51
+ }
52
+ })();
53
+ return this.inFlight;
54
+ }
55
+ };
56
+
1
57
  // src/core/errors.ts
2
58
  var VesantError = class _VesantError extends Error {
3
59
  constructor(message, code, statusCode, details) {
@@ -322,6 +378,12 @@ var BaseClient = class {
322
378
  if (!config.tenantId?.trim()) {
323
379
  throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
324
380
  }
381
+ if (typeof fetch === "undefined") {
382
+ throw new VesantError(
383
+ "The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
384
+ "RUNTIME_UNSUPPORTED"
385
+ );
386
+ }
325
387
  this.interceptors = config.interceptors || [];
326
388
  this.logger = config.logger || createConsoleLogger();
327
389
  let environment = config.environment;
@@ -340,11 +402,12 @@ var BaseClient = class {
340
402
  environment,
341
403
  headers: config.headers || {},
342
404
  timeout: config.timeout || 1e4,
343
- retries: config.retries || 3,
405
+ retries: config.retries ?? 3,
344
406
  debug: config.debug || false,
345
407
  interceptors: this.interceptors,
346
408
  logger: this.logger
347
409
  };
410
+ this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
348
411
  if (config.circuitBreaker) {
349
412
  this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
350
413
  }
@@ -352,6 +415,19 @@ var BaseClient = class {
352
415
  this.rateLimitTracker = new RateLimitTracker();
353
416
  }
354
417
  }
418
+ /**
419
+ * Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
420
+ * get the caller-supplied key or a freshly minted UUID; non-mutating methods get
421
+ * none. requestWithRetry resolves this once and injects it so every retry reuses
422
+ * one key (letting the server dedup replays); a direct request() resolves per call.
423
+ */
424
+ resolveIdempotencyKey(method, requestOptions) {
425
+ const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
426
+ if (!isMutating) {
427
+ return void 0;
428
+ }
429
+ return requestOptions?.idempotencyKey ?? generateUUID();
430
+ }
355
431
  /**
356
432
  * Make an HTTP request with timeout and error handling
357
433
  */
@@ -377,15 +453,20 @@ var BaseClient = class {
377
453
  ...this.config.headers,
378
454
  ...options.headers || {}
379
455
  };
380
- if (this.config.apiKey) {
381
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
456
+ const credential = await this.authProvider?.getCredential({
457
+ tenantId: this.config.tenantId,
458
+ requestId
459
+ });
460
+ if (credential) {
461
+ headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
382
462
  }
383
463
  if (this.config.environment === "sandbox") {
384
464
  headers["X-Sandbox"] = "true";
385
465
  }
386
466
  const method = (options.method || "GET").toUpperCase();
387
- if (["POST", "PUT", "PATCH"].includes(method)) {
388
- headers["Idempotency-Key"] = requestOptions?.idempotencyKey || generateUUID();
467
+ const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
468
+ if (idempotencyKey) {
469
+ headers["Idempotency-Key"] = idempotencyKey;
389
470
  }
390
471
  const controller = new AbortController();
391
472
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
@@ -406,10 +487,28 @@ var BaseClient = class {
406
487
  if (this.config.debug) {
407
488
  this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
408
489
  }
409
- const response = await fetch(url, {
490
+ let response = await fetch(url, {
410
491
  ...finalOptions,
411
492
  signal: controller.signal
412
493
  });
494
+ if (response.status === 401 && this.authProvider?.canRefresh?.()) {
495
+ const refreshed = await this.authProvider.getCredential({
496
+ tenantId: this.config.tenantId,
497
+ requestId,
498
+ forceRefresh: true
499
+ });
500
+ if (refreshed) {
501
+ this.config.onCredentialRotated?.({ reason: "unauthorized" });
502
+ finalOptions = {
503
+ ...finalOptions,
504
+ headers: {
505
+ ...finalOptions.headers,
506
+ Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
507
+ }
508
+ };
509
+ response = await fetch(url, { ...finalOptions, signal: controller.signal });
510
+ }
511
+ }
413
512
  clearTimeout(timeoutId);
414
513
  if (this.rateLimitTracker) {
415
514
  this.rateLimitTracker.updateFromHeaders(response.headers);
@@ -498,9 +597,11 @@ var BaseClient = class {
498
597
  */
499
598
  async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
500
599
  let lastError;
600
+ const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
601
+ const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
501
602
  for (let attempt = 0; attempt <= retries; attempt++) {
502
603
  try {
503
- return await this.request(endpoint, options, serviceURL, requestOptions);
604
+ return await this.request(endpoint, options, serviceURL, attemptOptions);
504
605
  } catch (error) {
505
606
  lastError = error instanceof Error ? error : new Error("Unknown error");
506
607
  if (requestOptions?.signal?.aborted) {
@@ -597,6 +698,9 @@ var BaseClient = class {
597
698
  if (config.interceptors) {
598
699
  this.interceptors = config.interceptors;
599
700
  }
701
+ if (config.authProvider !== void 0 || config.apiKey !== void 0) {
702
+ this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
703
+ }
600
704
  }
601
705
  /**
602
706
  * Get current configuration (readonly)
@@ -624,8 +728,69 @@ var BaseClient = class {
624
728
  }
625
729
  };
626
730
 
731
+ // src/core/pagination.ts
732
+ async function* paginate(fetchPage) {
733
+ let page = 1;
734
+ let totalPages;
735
+ do {
736
+ const response = await fetchPage(page);
737
+ const items = response.data ?? [];
738
+ for (const item of items) {
739
+ yield item;
740
+ }
741
+ totalPages = response.total_pages ?? page;
742
+ if (items.length === 0) {
743
+ break;
744
+ }
745
+ page += 1;
746
+ } while (page <= totalPages);
747
+ }
748
+ async function collectAll(fetchPage) {
749
+ const items = [];
750
+ for await (const item of paginate(fetchPage)) {
751
+ items.push(item);
752
+ }
753
+ return items;
754
+ }
755
+
756
+ // src/core/dedup.ts
757
+ var InMemoryDedupStore = class {
758
+ constructor(opts = {}) {
759
+ this.expiryById = /* @__PURE__ */ new Map();
760
+ this.maxEntries = opts.maxEntries ?? 1e3;
761
+ this.now = opts.now ?? (() => Date.now());
762
+ }
763
+ async seen(id) {
764
+ return this.expiryById.has(id);
765
+ }
766
+ async mark(id, ttlMs) {
767
+ const now = this.now();
768
+ this.expiryById.set(id, now + ttlMs);
769
+ if (this.expiryById.size > this.maxEntries) {
770
+ for (const [key, expiresAt] of this.expiryById) {
771
+ if (now > expiresAt) {
772
+ this.expiryById.delete(key);
773
+ }
774
+ }
775
+ }
776
+ }
777
+ };
778
+
627
779
  // src/core/webhook-utils.ts
628
- async function verifyWebhookSignature(payload, signature, secret) {
780
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
781
+ const signed = parseSignedTimestamp(signature);
782
+ if (signed) {
783
+ const tolerance = opts.tolerance ?? 3e5;
784
+ const now = opts.now?.() ?? Date.now();
785
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
786
+ return false;
787
+ }
788
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
789
+ return constantTimeEqual(signed.v1, expected);
790
+ }
791
+ if (opts.allowLegacy === false) {
792
+ return false;
793
+ }
629
794
  const hexDigest = await computeHmacSha256(payload, secret);
630
795
  const expectedPrefixed = `sha256=${hexDigest}`;
631
796
  if (signature.startsWith("sha256=")) {
@@ -633,6 +798,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
633
798
  }
634
799
  return constantTimeEqual(signature, hexDigest);
635
800
  }
801
+ function parseSignedTimestamp(signature) {
802
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
803
+ return null;
804
+ }
805
+ let t;
806
+ let v1;
807
+ for (const part of signature.split(",")) {
808
+ const eq = part.indexOf("=");
809
+ if (eq === -1) continue;
810
+ const key = part.slice(0, eq).trim();
811
+ const value = part.slice(eq + 1).trim();
812
+ if (key === "t") t = Number(value);
813
+ else if (key === "v1") v1 = value;
814
+ }
815
+ if (t === void 0 || Number.isNaN(t) || !v1) {
816
+ return null;
817
+ }
818
+ return { t, v1 };
819
+ }
636
820
  async function computeHmacSha256(message, secret) {
637
821
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
638
822
  const encoder = new TextEncoder();
@@ -667,6 +851,61 @@ function constantTimeEqual(a, b) {
667
851
  return result === 0;
668
852
  }
669
853
 
854
+ // src/geolocation/integrity.ts
855
+ var isNil = (v) => v === null || v === void 0;
856
+ function assessGpsIntegrity(input, opts = {}) {
857
+ const flags = [];
858
+ const now = opts.now ?? (() => Date.now());
859
+ const maxAgeMs = opts.maxAgeMs ?? 6e4;
860
+ const minAccuracy = opts.minAccuracyMeters ?? 1;
861
+ const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
862
+ if (isNil(accuracy)) {
863
+ flags.push("accuracy_missing");
864
+ } else if (accuracy === 0) {
865
+ flags.push("accuracy_zero");
866
+ } else if (accuracy < minAccuracy) {
867
+ flags.push("accuracy_implausibly_small");
868
+ }
869
+ const hasMotion = !isNil(heading) || !isNil(speed);
870
+ if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
871
+ flags.push("motion_without_accuracy");
872
+ }
873
+ if (!isNil(altitude) && isNil(altitudeAccuracy)) {
874
+ flags.push("altitude_without_accuracy");
875
+ }
876
+ if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
877
+ flags.push("stale_timestamp");
878
+ }
879
+ if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
880
+ flags.push(`permission_${opts.permissionState}`);
881
+ }
882
+ return { suspicious: flags.length > 0, flags };
883
+ }
884
+ function gpsIntegrityInputFromPosition(position) {
885
+ const c = position.coords;
886
+ return {
887
+ latitude: c.latitude,
888
+ longitude: c.longitude,
889
+ accuracy: c.accuracy,
890
+ altitude: c.altitude,
891
+ altitudeAccuracy: c.altitudeAccuracy,
892
+ heading: c.heading,
893
+ speed: c.speed,
894
+ timestamp: position.timestamp
895
+ };
896
+ }
897
+ async function probeGeolocationPermission() {
898
+ try {
899
+ if (typeof navigator === "undefined" || !navigator.permissions?.query) {
900
+ return "unavailable";
901
+ }
902
+ const status = await navigator.permissions.query({ name: "geolocation" });
903
+ return status.state;
904
+ } catch {
905
+ return "unavailable";
906
+ }
907
+ }
908
+
670
909
  // src/geolocation/ciphertext.ts
671
910
  var CIPHER_TEXT_EXPIRY_MINUTES = 5;
672
911
  async function computeHMAC(key, message) {
@@ -719,6 +958,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
719
958
  if (typeof navigator === "undefined" || !navigator.geolocation) {
720
959
  return null;
721
960
  }
961
+ const permissionState = await probeGeolocationPermission();
722
962
  return new Promise((resolve) => {
723
963
  navigator.geolocation.getCurrentPosition(
724
964
  (position) => {
@@ -735,6 +975,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
735
975
  resolve(null);
736
976
  return;
737
977
  }
978
+ const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
738
979
  resolve({
739
980
  latitude,
740
981
  longitude,
@@ -743,7 +984,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
743
984
  altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
744
985
  heading: position.coords.heading ?? void 0,
745
986
  speed: position.coords.speed ?? void 0,
746
- timestamp: position.timestamp
987
+ timestamp: position.timestamp,
988
+ integrity_flags: flags.length ? flags : void 0
747
989
  });
748
990
  },
749
991
  () => {
@@ -810,6 +1052,9 @@ async function generateCipherText(options, config) {
810
1052
  );
811
1053
  if (location) {
812
1054
  locationData = location;
1055
+ if (location.integrity_flags?.length) {
1056
+ warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
1057
+ }
813
1058
  } else if (gpsRequiredByConfig) {
814
1059
  throw new VesantError(
815
1060
  `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
@@ -929,8 +1174,8 @@ var GeolocationClient = class extends BaseClient {
929
1174
  * ```
930
1175
  */
931
1176
  async verifyIP(request, requestOptions) {
932
- if (!request.ip_address?.trim()) {
933
- throw new ValidationError("ip_address is required and must be a non-empty string", ["ip_address"]);
1177
+ if (request.ip_address !== void 0 && !request.ip_address.trim()) {
1178
+ throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
934
1179
  }
935
1180
  return this.requestWithRetry("/api/v1/geo/verify", {
936
1181
  method: "POST",
@@ -1280,20 +1525,12 @@ var GeolocationClient = class extends BaseClient {
1280
1525
  *
1281
1526
  * @example
1282
1527
  * ```typescript
1283
- * // Using GPS (preferred)
1528
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1284
1529
  * const result = await client.captureLocation(token, {
1285
1530
  * latitude: 37.7749,
1286
1531
  * longitude: -122.4194,
1287
1532
  * accuracy: 10
1288
1533
  * });
1289
- *
1290
- * // Using WiFi positioning (fallback)
1291
- * const result = await client.captureLocation(token, {
1292
- * wifi_networks: [
1293
- * { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
1294
- * { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
1295
- * ]
1296
- * });
1297
1534
  * ```
1298
1535
  */
1299
1536
  async captureLocation(token, capture, requestOptions) {
@@ -2736,12 +2973,12 @@ var KycClient = class extends BaseClient {
2736
2973
  * console.log(`KYC ID: ${result.kyc_id}`);
2737
2974
  * ```
2738
2975
  */
2739
- async requestKycSubmitLink(request) {
2976
+ async requestKycSubmitLink(request, requestOptions) {
2740
2977
  return this.requestWithRetry("/api/v1/kyc/request", {
2741
2978
  method: "POST",
2742
2979
  body: JSON.stringify(request),
2743
2980
  headers: this.getUserHeaders()
2744
- });
2981
+ }, void 0, void 0, requestOptions);
2745
2982
  }
2746
2983
  /**
2747
2984
  * Create a Event-Based Face Verification session.
@@ -2774,12 +3011,12 @@ var KycClient = class extends BaseClient {
2774
3011
  *
2775
3012
  * @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
2776
3013
  */
2777
- async submitEventBasedFaceVerificationSession(request) {
3014
+ async submitEventBasedFaceVerificationSession(request, requestOptions) {
2778
3015
  return this.request("/api/v1/kyc/face/submit", {
2779
3016
  method: "POST",
2780
3017
  body: JSON.stringify(request),
2781
3018
  headers: this.getUserHeaders()
2782
- });
3019
+ }, void 0, requestOptions);
2783
3020
  }
2784
3021
  /**
2785
3022
  * Look up the current state of a Event-Based Face Verification session by its
@@ -2895,12 +3132,12 @@ var KycClient = class extends BaseClient {
2895
3132
  * console.log(`Verification submitted: ${result.reference}`);
2896
3133
  * ```
2897
3134
  */
2898
- async submitVerification(request) {
3135
+ async submitVerification(request, requestOptions) {
2899
3136
  return this.requestWithRetry("/api/v1/kyc/submit", {
2900
3137
  method: "POST",
2901
3138
  body: JSON.stringify(request),
2902
3139
  headers: this.getUserHeaders()
2903
- });
3140
+ }, void 0, void 0, requestOptions);
2904
3141
  }
2905
3142
  /**
2906
3143
  * Get a KYC request by ID
@@ -3340,10 +3577,13 @@ var TaxClient = class extends BaseClient {
3340
3577
  { method: "POST" }
3341
3578
  );
3342
3579
  }
3343
- async checkTINStatus(customerID) {
3580
+ async checkTINStatus(customerID, requestOptions) {
3344
3581
  return this.requestWithRetry(
3345
3582
  `/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
3346
- { method: "POST" }
3583
+ { method: "POST" },
3584
+ void 0,
3585
+ void 0,
3586
+ requestOptions
3347
3587
  );
3348
3588
  }
3349
3589
  async reRequestTaxForm(customerID, input) {
@@ -3424,14 +3664,24 @@ var TaxClient = class extends BaseClient {
3424
3664
  };
3425
3665
 
3426
3666
  // src/webhooks/handler.ts
3667
+ function resolveEventId(event) {
3668
+ if (event.id) {
3669
+ return event.id;
3670
+ }
3671
+ if (event.event_type === "notification.created") {
3672
+ return event.notification?.id;
3673
+ }
3674
+ return void 0;
3675
+ }
3427
3676
  var WebhookHandler = class {
3428
3677
  constructor(config) {
3429
3678
  this.handlers = /* @__PURE__ */ new Map();
3430
3679
  this.anyHandlers = [];
3431
- this.seenEventIds = /* @__PURE__ */ new Map();
3432
3680
  this.secret = config.secret;
3433
3681
  this.tolerance = config.tolerance ?? 3e5;
3434
3682
  this.replayProtection = config.replayProtection ?? true;
3683
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3684
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3435
3685
  }
3436
3686
  /**
3437
3687
  * Register a handler for a specific event type.
@@ -3453,7 +3703,10 @@ var WebhookHandler = class {
3453
3703
  * Verify signature and parse the webhook body.
3454
3704
  */
3455
3705
  async verifyAndParse(body, signature) {
3456
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
3706
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
3707
+ tolerance: this.tolerance,
3708
+ allowLegacy: this.allowLegacySignature
3709
+ });
3457
3710
  if (!isValid) {
3458
3711
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3459
3712
  }
@@ -3469,18 +3722,21 @@ var WebhookHandler = class {
3469
3722
  /**
3470
3723
  * Parse an event without signature verification (for testing).
3471
3724
  */
3472
- parseEvent(body) {
3725
+ async parseEvent(body) {
3473
3726
  return this.parseAndValidate(body);
3474
3727
  }
3475
- parseAndValidate(body) {
3728
+ async parseAndValidate(body) {
3476
3729
  const event = JSON.parse(body);
3477
- if (!event.type || !event.id || !event.timestamp) {
3478
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
3730
+ const eventId = resolveEventId(event);
3731
+ if (!event.event_type || !eventId || !event.timestamp) {
3732
+ throw new ValidationError(
3733
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
3734
+ ["event_type", "id", "timestamp"]
3735
+ );
3479
3736
  }
3480
3737
  if (this.tolerance > 0) {
3481
3738
  const eventTime = new Date(event.timestamp).getTime();
3482
- const now = Date.now();
3483
- if (Math.abs(now - eventTime) > this.tolerance) {
3739
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3484
3740
  throw new ValidationError(
3485
3741
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3486
3742
  ["timestamp"]
@@ -3488,26 +3744,18 @@ var WebhookHandler = class {
3488
3744
  }
3489
3745
  }
3490
3746
  if (this.replayProtection) {
3491
- if (this.seenEventIds.has(event.id)) {
3747
+ if (await this.dedupStore.seen(eventId)) {
3492
3748
  throw new ValidationError(
3493
- `Duplicate webhook event: ${event.id} has already been processed`,
3749
+ `Duplicate webhook event: ${eventId} has already been processed`,
3494
3750
  ["id"]
3495
3751
  );
3496
3752
  }
3497
- const now = Date.now();
3498
- this.seenEventIds.set(event.id, now);
3499
- if (this.seenEventIds.size > 1e3) {
3500
- for (const [id, seenAt] of this.seenEventIds) {
3501
- if (now - seenAt > this.tolerance) {
3502
- this.seenEventIds.delete(id);
3503
- }
3504
- }
3505
- }
3753
+ await this.dedupStore.mark(eventId, this.tolerance);
3506
3754
  }
3507
3755
  return event;
3508
3756
  }
3509
3757
  async dispatch(event) {
3510
- const typeHandlers = this.handlers.get(event.type) || [];
3758
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3511
3759
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3512
3760
  for (const handler of allHandlers) {
3513
3761
  await handler(event);
@@ -3588,6 +3836,6 @@ function buildHandler(options) {
3588
3836
  return handler;
3589
3837
  }
3590
3838
 
3591
- export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES, GeolocationClient, KYC_DECLINED_DESCRIPTIONS, KycClient, NetworkError, RateLimitError, RateLimitTracker, RiskProfileClient, SDK_VERSION, ServiceUnavailableError, TaxClient, TimeoutError, ValidationError, VesantError, WebhookHandler, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, isCipherTextExpired, noopLogger, sdkReasons, verifyWebhookSignature };
3839
+ export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES, GeolocationClient, InMemoryDedupStore, KYC_DECLINED_DESCRIPTIONS, KycClient, NetworkError, RateLimitError, RateLimitTracker, RefreshingTokenProvider, RiskProfileClient, SDK_VERSION, ServiceUnavailableError, StaticApiKeyProvider, TaxClient, TimeoutError, ValidationError, VesantError, WebhookHandler, assessGpsIntegrity, collectAll, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, gpsIntegrityInputFromPosition, isCipherTextExpired, noopLogger, paginate, probeGeolocationPermission, sdkReasons, verifyWebhookSignature };
3592
3840
  //# sourceMappingURL=index.mjs.map
3593
3841
  //# sourceMappingURL=index.mjs.map