vesant-sdk 1.7.0 → 1.7.1-dev.301c6df

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-DtH2RLuy.d.ts → client-B3DCAHlg.d.ts} +2 -2
  2. package/dist/{client-DrjgZoH_.d.mts → client-BwXMQA2a.d.mts} +64 -42
  3. package/dist/{client-B0qhE2kr.d.mts → client-D23KhWeh.d.mts} +2 -2
  4. package/dist/{client-DF7hlMEz.d.ts → client-fy3trF2K.d.ts} +64 -42
  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 +181 -91
  10. package/dist/compliance/index.js.map +1 -1
  11. package/dist/compliance/index.mjs +181 -91
  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 +69 -8
  16. package/dist/decisions/index.js.map +1 -1
  17. package/dist/decisions/index.mjs +69 -8
  18. package/dist/decisions/index.mjs.map +1 -1
  19. package/dist/geolocation/index.d.mts +68 -7
  20. package/dist/geolocation/index.d.ts +68 -7
  21. package/dist/geolocation/index.js +186 -91
  22. package/dist/geolocation/index.js.map +1 -1
  23. package/dist/geolocation/index.mjs +183 -92
  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 +26 -9
  28. package/dist/index.d.ts +26 -9
  29. package/dist/index.js +354 -120
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +346 -121
  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 +75 -14
  36. package/dist/kyc/core.js.map +1 -1
  37. package/dist/kyc/core.mjs +75 -14
  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 +75 -14
  42. package/dist/kyc/index.js.map +1 -1
  43. package/dist/kyc/index.mjs +75 -14
  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 +70 -33
  48. package/dist/react.js.map +1 -1
  49. package/dist/react.mjs +70 -33
  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 +69 -8
  54. package/dist/risk-profile/index.js.map +1 -1
  55. package/dist/risk-profile/index.mjs +69 -8
  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 +69 -8
  60. package/dist/scores/index.js.map +1 -1
  61. package/dist/scores/index.mjs +69 -8
  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 +74 -10
  66. package/dist/tax/index.js.map +1 -1
  67. package/dist/tax/index.mjs +74 -10
  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
@@ -1,189 +1,2 @@
1
- import { U as UUID, T as Timestamp, R as RiskLevel } from '../types-CBQRNL-l.js';
2
-
3
- /**
4
- * Webhook signature verification utilities.
5
- *
6
- * Supports both Web Crypto API (browsers, Deno, Cloudflare Workers)
7
- * and Node.js crypto module with automatic runtime detection.
8
- * Uses constant-time comparison to prevent timing attacks.
9
- */
10
- /**
11
- * Verify an HMAC-SHA256 webhook signature.
12
- *
13
- * @param payload - The raw request body string
14
- * @param signature - The signature from the webhook header
15
- * @param secret - The webhook signing secret
16
- * @returns true if the signature is valid
17
- */
18
- declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
19
-
20
- /**
21
- * Webhook event types and payload definitions.
22
- */
23
-
24
- type WebhookEventType = 'risk.score.changed' | 'risk.category.changed' | 'profile.created' | 'profile.updated' | 'geolocation.verified' | 'geolocation.blocked' | 'kyc.status.changed' | 'kyc.approved' | 'kyc.declined' | 'decision.recorded' | 'label.applied' | 'location_request.completed' | 'compliance.check.failed' | 'tax.reminder.sent';
25
- interface BaseWebhookEvent<T extends WebhookEventType = WebhookEventType, P = unknown> {
26
- id: UUID;
27
- type: T;
28
- tenant_id: string;
29
- environment: 'production' | 'sandbox';
30
- customer_id: string;
31
- payload: P;
32
- timestamp: Timestamp;
33
- }
34
- interface RiskScoreChangedPayload {
35
- previous_score: number;
36
- new_score: number;
37
- risk_level: RiskLevel;
38
- factors: string[];
39
- }
40
- interface RiskCategoryChangedPayload {
41
- previous_category: string;
42
- new_category: string;
43
- risk_level: RiskLevel;
44
- }
45
- interface ProfileCreatedPayload {
46
- customer_id: string;
47
- entity_type: string;
48
- source: string;
49
- }
50
- interface ProfileUpdatedPayload {
51
- customer_id: string;
52
- updated_fields: string[];
53
- }
54
- interface GeolocationVerifiedPayload {
55
- latitude: number;
56
- longitude: number;
57
- country: string;
58
- region?: string;
59
- is_compliant: boolean;
60
- }
61
- interface GeolocationBlockedPayload {
62
- latitude: number;
63
- longitude: number;
64
- country: string;
65
- region?: string;
66
- reasons: string[];
67
- }
68
- interface KycStatusChangedPayload {
69
- previous_status: string;
70
- new_status: string;
71
- provider?: string;
72
- }
73
- interface DecisionRecordedPayload {
74
- decision_id: string;
75
- decision_type: string;
76
- category: string;
77
- source: string;
78
- risk_score?: number;
79
- }
80
- interface LabelAppliedPayload {
81
- label_type: string;
82
- reasons?: string[];
83
- }
84
- interface LocationRequestCompletedPayload {
85
- request_id: string;
86
- status: string;
87
- latitude?: number;
88
- longitude?: number;
89
- }
90
- interface ComplianceCheckFailedPayload {
91
- check_type: string;
92
- reasons: string[];
93
- severity: string;
94
- }
95
- interface TaxReminderSentPayload {
96
- /** W-form the reminder is for (e.g. "W-9", "W-8BEN", "W-8BEN-E") */
97
- form_type: string;
98
- /** Sequential count of reminders sent in the current solicitation cycle */
99
- reminder_count: number;
100
- /** Configured maximum reminders per cycle (reminder_max_count) */
101
- max_count: number;
102
- }
103
- type WebhookEvent = BaseWebhookEvent<'risk.score.changed', RiskScoreChangedPayload> | BaseWebhookEvent<'risk.category.changed', RiskCategoryChangedPayload> | BaseWebhookEvent<'profile.created', ProfileCreatedPayload> | BaseWebhookEvent<'profile.updated', ProfileUpdatedPayload> | BaseWebhookEvent<'geolocation.verified', GeolocationVerifiedPayload> | BaseWebhookEvent<'geolocation.blocked', GeolocationBlockedPayload> | BaseWebhookEvent<'kyc.status.changed', KycStatusChangedPayload> | BaseWebhookEvent<'kyc.approved', KycStatusChangedPayload> | BaseWebhookEvent<'kyc.declined', KycStatusChangedPayload> | BaseWebhookEvent<'decision.recorded', DecisionRecordedPayload> | BaseWebhookEvent<'label.applied', LabelAppliedPayload> | BaseWebhookEvent<'location_request.completed', LocationRequestCompletedPayload> | BaseWebhookEvent<'compliance.check.failed', ComplianceCheckFailedPayload> | BaseWebhookEvent<'tax.reminder.sent', TaxReminderSentPayload>;
104
- interface WebhookHandlerConfig {
105
- /** Webhook signing secret for signature verification */
106
- secret: string;
107
- /** Header name containing the signature (default: 'x-webhook-signature') */
108
- signatureHeader?: string;
109
- /** Maximum age of webhook event in ms (default: 300000 = 5 minutes) */
110
- tolerance?: number;
111
- /** Enable replay protection to reject duplicate event IDs (default: true) */
112
- replayProtection?: boolean;
113
- }
114
- type WebhookEventHandler<T extends WebhookEventType = WebhookEventType> = (event: Extract<WebhookEvent, {
115
- type: T;
116
- }>) => void | Promise<void>;
117
- type WebhookAnyHandler = (event: WebhookEvent) => void | Promise<void>;
118
-
119
- /**
120
- * Webhook event handler with signature verification and typed dispatch.
121
- */
122
-
123
- declare class WebhookHandler {
124
- private handlers;
125
- private anyHandlers;
126
- private readonly secret;
127
- private readonly tolerance;
128
- private readonly replayProtection;
129
- private seenEventIds;
130
- constructor(config: WebhookHandlerConfig);
131
- /**
132
- * Register a handler for a specific event type.
133
- */
134
- on<T extends WebhookEventType>(eventType: T, handler: WebhookEventHandler<T>): this;
135
- /**
136
- * Register a catch-all handler for all event types.
137
- */
138
- onAny(handler: WebhookAnyHandler): this;
139
- /**
140
- * Verify signature and parse the webhook body.
141
- */
142
- verifyAndParse(body: string, signature: string): Promise<WebhookEvent>;
143
- /**
144
- * Verify signature, parse, and dispatch to registered handlers.
145
- */
146
- handle(body: string, signature: string): Promise<void>;
147
- /**
148
- * Parse an event without signature verification (for testing).
149
- */
150
- parseEvent(body: string): WebhookEvent;
151
- private parseAndValidate;
152
- private dispatch;
153
- }
154
-
155
- /**
156
- * Framework middleware helpers for webhook handling.
157
- *
158
- * Provides pre-built integrations for Express/Connect and Next.js App Router.
159
- */
160
-
161
- interface WebhookMiddlewareOptions extends WebhookHandlerConfig {
162
- /** Event handlers to register */
163
- handlers?: Partial<Record<WebhookEventType, WebhookEventHandler>>;
164
- /** Catch-all handler */
165
- onAny?: WebhookAnyHandler;
166
- }
167
- /**
168
- * Create an Express/Connect-compatible middleware for webhook handling.
169
- *
170
- * Expects the raw body to be available as `req.body` (string).
171
- * Use `express.raw({ type: 'application/json' })` or similar middleware upstream.
172
- */
173
- declare function createWebhookMiddleware(options: WebhookMiddlewareOptions): (req: {
174
- body: string | Buffer;
175
- headers: Record<string, string | string[] | undefined>;
176
- }, res: {
177
- status(code: number): {
178
- json(body: unknown): void;
179
- };
180
- json?(body: unknown): void;
181
- }, next?: (err?: unknown) => void) => Promise<void>;
182
- /**
183
- * Create a Next.js App Router-compatible webhook handler.
184
- *
185
- * Returns an async function `(request: Request) => Promise<Response>`.
186
- */
187
- declare function createNextWebhookHandler(options: WebhookMiddlewareOptions): (request: Request) => Promise<Response>;
188
-
189
- export { type BaseWebhookEvent, type ComplianceCheckFailedPayload, type DecisionRecordedPayload, type GeolocationBlockedPayload, type GeolocationVerifiedPayload, type KycStatusChangedPayload, type LabelAppliedPayload, type LocationRequestCompletedPayload, type ProfileCreatedPayload, type ProfileUpdatedPayload, type RiskCategoryChangedPayload, type RiskScoreChangedPayload, type TaxReminderSentPayload, type WebhookAnyHandler, type WebhookEvent, type WebhookEventHandler, type WebhookEventType, WebhookHandler, type WebhookHandlerConfig, type WebhookMiddlewareOptions, createNextWebhookHandler, createWebhookMiddleware, verifyWebhookSignature };
1
+ export { B as BaseWebhookEvent, C as ComplianceCheckFailedPayload, D as DecisionRecordedPayload, G as GeolocationBlockedPayload, a as GeolocationVerifiedPayload, I as InMemoryDedupStore, b as InMemoryDedupStoreOptions, K as KycStatusChangedPayload, L as LabelAppliedPayload, c as LocationRequestCompletedPayload, N as NotificationWebhookEvent, P as ProfileCreatedPayload, d as ProfileUpdatedPayload, R as RiskCategoryChangedPayload, e as RiskScoreChangedPayload, T as TaxReminderSentPayload, W as WebhookAnyHandler, f as WebhookDedupStore, g as WebhookEvent, h as WebhookEventHandler, i as WebhookEventType, j as WebhookHandler, k as WebhookHandlerConfig, l as WebhookMiddlewareOptions, m as WebhookNotification, n as createNextWebhookHandler, o as createWebhookMiddleware, v as verifyWebhookSignature } from '../index-DSDgS09k.js';
2
+ import '../types-CBQRNL-l.js';
@@ -20,7 +20,20 @@ var ValidationError = class _ValidationError extends VesantError {
20
20
  };
21
21
 
22
22
  // src/core/webhook-utils.ts
23
- async function verifyWebhookSignature(payload, signature, secret) {
23
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
24
+ const signed = parseSignedTimestamp(signature);
25
+ if (signed) {
26
+ const tolerance = opts.tolerance ?? 3e5;
27
+ const now = opts.now?.() ?? Date.now();
28
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
29
+ return false;
30
+ }
31
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
32
+ return constantTimeEqual(signed.v1, expected);
33
+ }
34
+ if (opts.allowLegacy === false) {
35
+ return false;
36
+ }
24
37
  const hexDigest = await computeHmacSha256(payload, secret);
25
38
  const expectedPrefixed = `sha256=${hexDigest}`;
26
39
  if (signature.startsWith("sha256=")) {
@@ -28,6 +41,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
28
41
  }
29
42
  return constantTimeEqual(signature, hexDigest);
30
43
  }
44
+ function parseSignedTimestamp(signature) {
45
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
46
+ return null;
47
+ }
48
+ let t;
49
+ let v1;
50
+ for (const part of signature.split(",")) {
51
+ const eq = part.indexOf("=");
52
+ if (eq === -1) continue;
53
+ const key = part.slice(0, eq).trim();
54
+ const value = part.slice(eq + 1).trim();
55
+ if (key === "t") t = Number(value);
56
+ else if (key === "v1") v1 = value;
57
+ }
58
+ if (t === void 0 || Number.isNaN(t) || !v1) {
59
+ return null;
60
+ }
61
+ return { t, v1 };
62
+ }
31
63
  async function computeHmacSha256(message, secret) {
32
64
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
33
65
  const encoder = new TextEncoder();
@@ -62,15 +94,48 @@ function constantTimeEqual(a, b) {
62
94
  return result === 0;
63
95
  }
64
96
 
97
+ // src/core/dedup.ts
98
+ var InMemoryDedupStore = class {
99
+ constructor(opts = {}) {
100
+ this.expiryById = /* @__PURE__ */ new Map();
101
+ this.maxEntries = opts.maxEntries ?? 1e3;
102
+ this.now = opts.now ?? (() => Date.now());
103
+ }
104
+ async seen(id) {
105
+ return this.expiryById.has(id);
106
+ }
107
+ async mark(id, ttlMs) {
108
+ const now = this.now();
109
+ this.expiryById.set(id, now + ttlMs);
110
+ if (this.expiryById.size > this.maxEntries) {
111
+ for (const [key, expiresAt] of this.expiryById) {
112
+ if (now > expiresAt) {
113
+ this.expiryById.delete(key);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ };
119
+
65
120
  // src/webhooks/handler.ts
121
+ function resolveEventId(event) {
122
+ if (event.id) {
123
+ return event.id;
124
+ }
125
+ if (event.event_type === "notification.created") {
126
+ return event.notification?.id;
127
+ }
128
+ return void 0;
129
+ }
66
130
  var WebhookHandler = class {
67
131
  constructor(config) {
68
132
  this.handlers = /* @__PURE__ */ new Map();
69
133
  this.anyHandlers = [];
70
- this.seenEventIds = /* @__PURE__ */ new Map();
71
134
  this.secret = config.secret;
72
135
  this.tolerance = config.tolerance ?? 3e5;
73
136
  this.replayProtection = config.replayProtection ?? true;
137
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
138
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
74
139
  }
75
140
  /**
76
141
  * Register a handler for a specific event type.
@@ -92,7 +157,10 @@ var WebhookHandler = class {
92
157
  * Verify signature and parse the webhook body.
93
158
  */
94
159
  async verifyAndParse(body, signature) {
95
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
160
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
161
+ tolerance: this.tolerance,
162
+ allowLegacy: this.allowLegacySignature
163
+ });
96
164
  if (!isValid) {
97
165
  throw new ValidationError("Invalid webhook signature", ["signature"]);
98
166
  }
@@ -108,18 +176,21 @@ var WebhookHandler = class {
108
176
  /**
109
177
  * Parse an event without signature verification (for testing).
110
178
  */
111
- parseEvent(body) {
179
+ async parseEvent(body) {
112
180
  return this.parseAndValidate(body);
113
181
  }
114
- parseAndValidate(body) {
182
+ async parseAndValidate(body) {
115
183
  const event = JSON.parse(body);
116
- if (!event.type || !event.id || !event.timestamp) {
117
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
184
+ const eventId = resolveEventId(event);
185
+ if (!event.event_type || !eventId || !event.timestamp) {
186
+ throw new ValidationError(
187
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
188
+ ["event_type", "id", "timestamp"]
189
+ );
118
190
  }
119
191
  if (this.tolerance > 0) {
120
192
  const eventTime = new Date(event.timestamp).getTime();
121
- const now = Date.now();
122
- if (Math.abs(now - eventTime) > this.tolerance) {
193
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
123
194
  throw new ValidationError(
124
195
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
125
196
  ["timestamp"]
@@ -127,26 +198,18 @@ var WebhookHandler = class {
127
198
  }
128
199
  }
129
200
  if (this.replayProtection) {
130
- if (this.seenEventIds.has(event.id)) {
201
+ if (await this.dedupStore.seen(eventId)) {
131
202
  throw new ValidationError(
132
- `Duplicate webhook event: ${event.id} has already been processed`,
203
+ `Duplicate webhook event: ${eventId} has already been processed`,
133
204
  ["id"]
134
205
  );
135
206
  }
136
- const now = Date.now();
137
- this.seenEventIds.set(event.id, now);
138
- if (this.seenEventIds.size > 1e3) {
139
- for (const [id, seenAt] of this.seenEventIds) {
140
- if (now - seenAt > this.tolerance) {
141
- this.seenEventIds.delete(id);
142
- }
143
- }
144
- }
207
+ await this.dedupStore.mark(eventId, this.tolerance);
145
208
  }
146
209
  return event;
147
210
  }
148
211
  async dispatch(event) {
149
- const typeHandlers = this.handlers.get(event.type) || [];
212
+ const typeHandlers = this.handlers.get(event.event_type) || [];
150
213
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
151
214
  for (const handler of allHandlers) {
152
215
  await handler(event);
@@ -227,6 +290,7 @@ function buildHandler(options) {
227
290
  return handler;
228
291
  }
229
292
 
293
+ exports.InMemoryDedupStore = InMemoryDedupStore;
230
294
  exports.WebhookHandler = WebhookHandler;
231
295
  exports.createNextWebhookHandler = createNextWebhookHandler;
232
296
  exports.createWebhookMiddleware = createWebhookMiddleware;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/errors.ts","../../src/core/webhook-utils.ts","../../src/webhooks/handler.ts","../../src/webhooks/middleware.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error hierarchy for Vesant SDK\n *\n * Provides structured error handling with specific error types\n * for different failure scenarios.\n */\n\nexport class VesantError extends Error {\n public requestId?: string;\n\n constructor(\n message: string,\n public code: string,\n public statusCode?: number,\n public details?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'VesantError';\n Object.setPrototypeOf(this, VesantError.prototype);\n }\n}\n\n/** @deprecated Use VesantError instead */\nexport const CGSError = VesantError;\n/** @deprecated Use VesantError instead */\nexport type CGSError = VesantError;\n\nexport class NetworkError extends VesantError {\n constructor(message: string, public originalError?: unknown) {\n super(message, 'NETWORK_ERROR');\n this.name = 'NetworkError';\n Object.setPrototypeOf(this, NetworkError.prototype);\n }\n}\n\nexport class ValidationError extends VesantError {\n constructor(message: string, fields?: string[]) {\n super(message, 'VALIDATION_ERROR', 400, { fields });\n this.name = 'ValidationError';\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\nexport class ServiceUnavailableError extends VesantError {\n constructor(message: string = 'Service unavailable') {\n super(message, 'SERVICE_UNAVAILABLE', 503);\n this.name = 'ServiceUnavailableError';\n Object.setPrototypeOf(this, ServiceUnavailableError.prototype);\n }\n}\n\nexport class ComplianceBlockedError extends VesantError {\n constructor(reasons: string[]) {\n super('Access blocked due to compliance rules', 'COMPLIANCE_BLOCKED', 403, { reasons });\n this.name = 'ComplianceBlockedError';\n Object.setPrototypeOf(this, ComplianceBlockedError.prototype);\n }\n}\n\nexport class AuthenticationError extends VesantError {\n constructor(message: string = 'Authentication failed') {\n super(message, 'AUTHENTICATION_ERROR', 401);\n this.name = 'AuthenticationError';\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n}\n\nexport class RateLimitError extends VesantError {\n constructor(public retryAfter?: number) {\n super('Rate limit exceeded', 'RATE_LIMIT_EXCEEDED', 429, { retryAfter });\n this.name = 'RateLimitError';\n Object.setPrototypeOf(this, RateLimitError.prototype);\n }\n}\n\nexport class TimeoutError extends VesantError {\n constructor(public timeout: number) {\n super(`Request timeout after ${timeout}ms`, 'TIMEOUT', 408, { timeout });\n this.name = 'TimeoutError';\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n\nexport class ComplianceError extends VesantError {\n constructor(\n message: string,\n public originalError?: unknown,\n code: string = 'COMPLIANCE_ERROR'\n ) {\n super(message, code);\n this.name = 'ComplianceError';\n Object.setPrototypeOf(this, ComplianceError.prototype);\n }\n}\n\nexport class CircuitBreakerOpenError extends VesantError {\n constructor() {\n super('Circuit breaker is open — requests are temporarily blocked', 'CIRCUIT_BREAKER_OPEN', 503);\n this.name = 'CircuitBreakerOpenError';\n Object.setPrototypeOf(this, CircuitBreakerOpenError.prototype);\n }\n}\n","/**\n * Webhook signature verification utilities.\n *\n * Supports both Web Crypto API (browsers, Deno, Cloudflare Workers)\n * and Node.js crypto module with automatic runtime detection.\n * Uses constant-time comparison to prevent timing attacks.\n */\n\nimport { VesantError } from './errors';\n\n/**\n * Verify an HMAC-SHA256 webhook signature.\n *\n * @param payload - The raw request body string\n * @param signature - The signature from the webhook header\n * @param secret - The webhook signing secret\n * @returns true if the signature is valid\n */\nexport async function verifyWebhookSignature(\n payload: string,\n signature: string,\n secret: string\n): Promise<boolean> {\n const hexDigest = await computeHmacSha256(payload, secret);\n\n // Backend sends \"sha256=<hex>\" format — support both prefixed and raw signatures\n const expectedPrefixed = `sha256=${hexDigest}`;\n\n if (signature.startsWith('sha256=')) {\n return constantTimeEqual(signature, expectedPrefixed);\n }\n\n return constantTimeEqual(signature, hexDigest);\n}\n\n/**\n * Compute HMAC-SHA256 hex digest.\n * Automatically selects Web Crypto API or Node.js crypto.\n */\nasync function computeHmacSha256(message: string, secret: string): Promise<string> {\n // Try Web Crypto API first (browsers, Deno, Cloudflare Workers, Node 18+)\n if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.subtle) {\n const encoder = new TextEncoder();\n const key = await globalThis.crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n const sig = await globalThis.crypto.subtle.sign('HMAC', key, encoder.encode(message));\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Fallback to Node.js crypto module\n try {\n const { createHmac } = await import('crypto');\n return createHmac('sha256', secret).update(message).digest('hex');\n } catch {\n throw new VesantError(\n 'No crypto implementation available. Requires Web Crypto API or Node.js crypto module.',\n 'CRYPTO_UNAVAILABLE'\n );\n }\n}\n\n/**\n * Constant-time string comparison to prevent timing attacks.\n */\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n}\n","/**\n * Webhook event handler with signature verification and typed dispatch.\n */\n\nimport { verifyWebhookSignature } from '../core/webhook-utils';\nimport { ValidationError } from '../core/errors';\nimport type {\n WebhookEvent,\n WebhookEventType,\n WebhookEventHandler,\n WebhookAnyHandler,\n WebhookHandlerConfig,\n} from './types';\n\nexport class WebhookHandler {\n private handlers = new Map<WebhookEventType, WebhookEventHandler[]>();\n private anyHandlers: WebhookAnyHandler[] = [];\n private readonly secret: string;\n private readonly tolerance: number;\n private readonly replayProtection: boolean;\n private seenEventIds = new Map<string, number>();\n\n constructor(config: WebhookHandlerConfig) {\n this.secret = config.secret;\n this.tolerance = config.tolerance ?? 300000; // 5 minutes\n this.replayProtection = config.replayProtection ?? true;\n }\n\n /**\n * Register a handler for a specific event type.\n */\n on<T extends WebhookEventType>(\n eventType: T,\n handler: WebhookEventHandler<T>\n ): this {\n const existing = this.handlers.get(eventType) || [];\n existing.push(handler as unknown as WebhookEventHandler);\n this.handlers.set(eventType, existing);\n return this;\n }\n\n /**\n * Register a catch-all handler for all event types.\n */\n onAny(handler: WebhookAnyHandler): this {\n this.anyHandlers.push(handler);\n return this;\n }\n\n /**\n * Verify signature and parse the webhook body.\n */\n async verifyAndParse(body: string, signature: string): Promise<WebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret);\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n return this.parseAndValidate(body);\n }\n\n /**\n * Verify signature, parse, and dispatch to registered handlers.\n */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n await this.dispatch(event);\n }\n\n /**\n * Parse an event without signature verification (for testing).\n */\n parseEvent(body: string): WebhookEvent {\n return this.parseAndValidate(body);\n }\n\n private parseAndValidate(body: string): WebhookEvent {\n const event = JSON.parse(body) as WebhookEvent;\n\n if (!event.type || !event.id || !event.timestamp) {\n throw new ValidationError('Invalid webhook event: missing required fields (type, id, timestamp)', ['type', 'id', 'timestamp']);\n }\n\n // Timestamp tolerance check\n if (this.tolerance > 0) {\n const eventTime = new Date(event.timestamp).getTime();\n const now = Date.now();\n if (Math.abs(now - eventTime) > this.tolerance) {\n throw new ValidationError(\n `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,\n ['timestamp']\n );\n }\n }\n\n // Replay protection: reject duplicate event IDs within tolerance window\n if (this.replayProtection) {\n if (this.seenEventIds.has(event.id)) {\n throw new ValidationError(\n `Duplicate webhook event: ${event.id} has already been processed`,\n ['id']\n );\n }\n\n const now = Date.now();\n this.seenEventIds.set(event.id, now);\n\n // Purge expired entries to prevent unbounded memory growth\n if (this.seenEventIds.size > 1000) {\n for (const [id, seenAt] of this.seenEventIds) {\n if (now - seenAt > this.tolerance) {\n this.seenEventIds.delete(id);\n }\n }\n }\n }\n\n return event;\n }\n\n private async dispatch(event: WebhookEvent): Promise<void> {\n const typeHandlers = this.handlers.get(event.type) || [];\n const allHandlers = [...typeHandlers, ...this.anyHandlers];\n\n for (const handler of allHandlers) {\n await (handler as WebhookAnyHandler)(event);\n }\n }\n}\n","/**\n * Framework middleware helpers for webhook handling.\n *\n * Provides pre-built integrations for Express/Connect and Next.js App Router.\n */\n\nimport { WebhookHandler } from './handler';\nimport type { WebhookHandlerConfig, WebhookEventType, WebhookEventHandler, WebhookAnyHandler } from './types';\n\nexport interface WebhookMiddlewareOptions extends WebhookHandlerConfig {\n /** Event handlers to register */\n handlers?: Partial<Record<WebhookEventType, WebhookEventHandler>>;\n /** Catch-all handler */\n onAny?: WebhookAnyHandler;\n}\n\n/**\n * Create an Express/Connect-compatible middleware for webhook handling.\n *\n * Expects the raw body to be available as `req.body` (string).\n * Use `express.raw({ type: 'application/json' })` or similar middleware upstream.\n */\nexport function createWebhookMiddleware(options: WebhookMiddlewareOptions) {\n const handler = buildHandler(options);\n const signatureHeader = options.signatureHeader || 'x-webhook-signature';\n\n return async (\n req: { body: string | Buffer; headers: Record<string, string | string[] | undefined> },\n res: { status(code: number): { json(body: unknown): void }; json?(body: unknown): void },\n next?: (err?: unknown) => void\n ) => {\n try {\n const body = typeof req.body === 'string' ? req.body : req.body.toString('utf-8');\n const signature = req.headers[signatureHeader];\n\n if (!signature || typeof signature !== 'string') {\n res.status(401).json({ error: 'Missing webhook signature' });\n return;\n }\n\n await handler.handle(body, signature);\n res.status(200).json({ received: true });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Webhook processing failed';\n\n if (message.includes('signature')) {\n res.status(401).json({ error: message });\n } else if (message.includes('tolerance') || message.includes('timestamp')) {\n res.status(400).json({ error: message });\n } else if (message.includes('Duplicate')) {\n res.status(409).json({ error: message });\n } else if (next) {\n next(error);\n } else {\n res.status(500).json({ error: message });\n }\n }\n };\n}\n\n/**\n * Create a Next.js App Router-compatible webhook handler.\n *\n * Returns an async function `(request: Request) => Promise<Response>`.\n */\nexport function createNextWebhookHandler(\n options: WebhookMiddlewareOptions\n): (request: Request) => Promise<Response> {\n const handler = buildHandler(options);\n const signatureHeader = options.signatureHeader || 'x-webhook-signature';\n\n return async (request: Request): Promise<Response> => {\n try {\n const body = await request.text();\n const signature = request.headers.get(signatureHeader);\n\n if (!signature) {\n return new Response(JSON.stringify({ error: 'Missing webhook signature' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n await handler.handle(body, signature);\n\n return new Response(JSON.stringify({ received: true }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Webhook processing failed';\n const status = message.includes('signature')\n ? 401\n : message.includes('tolerance') || message.includes('timestamp')\n ? 400\n : message.includes('Duplicate')\n ? 409\n : 500;\n\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n\nfunction buildHandler(options: WebhookMiddlewareOptions): WebhookHandler {\n const handler = new WebhookHandler(options);\n\n if (options.handlers) {\n for (const [eventType, eventHandler] of Object.entries(options.handlers)) {\n if (eventHandler) {\n handler.on(eventType as WebhookEventType, eventHandler);\n }\n }\n }\n\n if (options.onAny) {\n handler.onAny(options.onAny);\n }\n\n return handler;\n}\n"]}
1
+ {"version":3,"sources":["../../src/core/errors.ts","../../src/core/webhook-utils.ts","../../src/core/dedup.ts","../../src/webhooks/handler.ts","../../src/webhooks/middleware.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error hierarchy for Vesant SDK\n *\n * Provides structured error handling with specific error types\n * for different failure scenarios.\n */\n\nexport class VesantError extends Error {\n public requestId?: string;\n\n constructor(\n message: string,\n public code: string,\n public statusCode?: number,\n public details?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'VesantError';\n Object.setPrototypeOf(this, VesantError.prototype);\n }\n}\n\n/** @deprecated Use VesantError instead */\nexport const CGSError = VesantError;\n/** @deprecated Use VesantError instead */\nexport type CGSError = VesantError;\n\nexport class NetworkError extends VesantError {\n constructor(message: string, public originalError?: unknown) {\n super(message, 'NETWORK_ERROR');\n this.name = 'NetworkError';\n Object.setPrototypeOf(this, NetworkError.prototype);\n }\n}\n\nexport class ValidationError extends VesantError {\n constructor(message: string, fields?: string[]) {\n super(message, 'VALIDATION_ERROR', 400, { fields });\n this.name = 'ValidationError';\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\nexport class ServiceUnavailableError extends VesantError {\n constructor(message: string = 'Service unavailable') {\n super(message, 'SERVICE_UNAVAILABLE', 503);\n this.name = 'ServiceUnavailableError';\n Object.setPrototypeOf(this, ServiceUnavailableError.prototype);\n }\n}\n\nexport class ComplianceBlockedError extends VesantError {\n constructor(reasons: string[]) {\n super('Access blocked due to compliance rules', 'COMPLIANCE_BLOCKED', 403, { reasons });\n this.name = 'ComplianceBlockedError';\n Object.setPrototypeOf(this, ComplianceBlockedError.prototype);\n }\n}\n\nexport class AuthenticationError extends VesantError {\n constructor(message: string = 'Authentication failed') {\n super(message, 'AUTHENTICATION_ERROR', 401);\n this.name = 'AuthenticationError';\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n}\n\nexport class RateLimitError extends VesantError {\n constructor(public retryAfter?: number) {\n super('Rate limit exceeded', 'RATE_LIMIT_EXCEEDED', 429, { retryAfter });\n this.name = 'RateLimitError';\n Object.setPrototypeOf(this, RateLimitError.prototype);\n }\n}\n\nexport class TimeoutError extends VesantError {\n constructor(public timeout: number) {\n super(`Request timeout after ${timeout}ms`, 'TIMEOUT', 408, { timeout });\n this.name = 'TimeoutError';\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n\nexport class ComplianceError extends VesantError {\n constructor(\n message: string,\n public originalError?: unknown,\n code: string = 'COMPLIANCE_ERROR'\n ) {\n super(message, code);\n this.name = 'ComplianceError';\n Object.setPrototypeOf(this, ComplianceError.prototype);\n }\n}\n\nexport class CircuitBreakerOpenError extends VesantError {\n constructor() {\n super('Circuit breaker is open — requests are temporarily blocked', 'CIRCUIT_BREAKER_OPEN', 503);\n this.name = 'CircuitBreakerOpenError';\n Object.setPrototypeOf(this, CircuitBreakerOpenError.prototype);\n }\n}\n","/**\n * Webhook signature verification utilities.\n *\n * Supports both Web Crypto API (browsers, Deno, Cloudflare Workers)\n * and Node.js crypto module with automatic runtime detection.\n * Uses constant-time comparison to prevent timing attacks.\n */\n\nimport { VesantError } from './errors';\n\nexport interface VerifyWebhookSignatureOptions {\n /** Max allowed clock skew in ms for the signed-timestamp scheme (default: 300000). */\n tolerance?: number;\n /** Clock source for the tolerance check (default: Date.now). */\n now?: () => number;\n /** Accept the legacy body-only `sha256=<hex>` format (default: true, for migration). */\n allowLegacy?: boolean;\n}\n\n/**\n * Verify an HMAC-SHA256 webhook signature.\n *\n * Supports two schemes:\n * - Signed-timestamp (preferred): `t=<unix>,v1=<hex>` where the HMAC is computed\n * over `${t}.${payload}`. The timestamp is inside the signed material, so a\n * replayed body is rejected once `|now - t|` exceeds the tolerance — the check\n * is done BEFORE the HMAC compare and authenticates the timestamp.\n * - Legacy (body-only): `sha256=<hex>` (or raw hex) over the payload alone. Kept\n * for the migration window; disable via `allowLegacy: false`.\n *\n * @returns true if the signature is valid\n */\nexport async function verifyWebhookSignature(\n payload: string,\n signature: string,\n secret: string,\n opts: VerifyWebhookSignatureOptions = {}\n): Promise<boolean> {\n const signed = parseSignedTimestamp(signature);\n if (signed) {\n const tolerance = opts.tolerance ?? 300000;\n const now = opts.now?.() ?? Date.now();\n // Authenticate the timestamp first — reject replays before any HMAC compare.\n if (tolerance > 0 && Math.abs(now - signed.t * 1000) > tolerance) {\n return false;\n }\n const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);\n return constantTimeEqual(signed.v1, expected);\n }\n\n if (opts.allowLegacy === false) {\n return false;\n }\n\n const hexDigest = await computeHmacSha256(payload, secret);\n // Backend sends \"sha256=<hex>\" format — support both prefixed and raw signatures\n const expectedPrefixed = `sha256=${hexDigest}`;\n\n if (signature.startsWith('sha256=')) {\n return constantTimeEqual(signature, expectedPrefixed);\n }\n\n return constantTimeEqual(signature, hexDigest);\n}\n\n/**\n * Parse the `t=<unix>,v1=<hex>` signed-timestamp scheme; returns null if the\n * signature is not in that form (e.g. legacy `sha256=<hex>`).\n */\nfunction parseSignedTimestamp(signature: string): { t: number; v1: string } | null {\n if (!signature.includes('t=') || !signature.includes('v1=')) {\n return null;\n }\n let t: number | undefined;\n let v1: string | undefined;\n for (const part of signature.split(',')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n const key = part.slice(0, eq).trim();\n const value = part.slice(eq + 1).trim();\n if (key === 't') t = Number(value);\n else if (key === 'v1') v1 = value;\n }\n if (t === undefined || Number.isNaN(t) || !v1) {\n return null;\n }\n return { t, v1 };\n}\n\n/**\n * Compute HMAC-SHA256 hex digest.\n * Automatically selects Web Crypto API or Node.js crypto.\n */\nasync function computeHmacSha256(message: string, secret: string): Promise<string> {\n // Try Web Crypto API first (browsers, Deno, Cloudflare Workers, Node 18+)\n if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.subtle) {\n const encoder = new TextEncoder();\n const key = await globalThis.crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n );\n const sig = await globalThis.crypto.subtle.sign('HMAC', key, encoder.encode(message));\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Fallback to Node.js crypto module\n try {\n const { createHmac } = await import('crypto');\n return createHmac('sha256', secret).update(message).digest('hex');\n } catch {\n throw new VesantError(\n 'No crypto implementation available. Requires Web Crypto API or Node.js crypto module.',\n 'CRYPTO_UNAVAILABLE'\n );\n }\n}\n\n/**\n * Constant-time string comparison to prevent timing attacks.\n */\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return result === 0;\n}\n","/**\n * Pluggable dedup store for webhook replay protection.\n *\n * The default InMemoryDedupStore is process-local — correct for a single\n * long-lived process, but useless across replicas / serverless invocations where\n * the backend's at-least-once retries land on different instances. Supply a\n * shared implementation (e.g. Redis with SETNX + TTL) via WebhookHandlerConfig\n * to dedup reliably in those deployments.\n */\n\nexport interface WebhookDedupStore {\n /** Has this event id already been processed? */\n seen(id: string): Promise<boolean>;\n /** Record this event id as processed, retained for at least ttlMs. */\n mark(id: string, ttlMs: number): Promise<void>;\n}\n\nexport interface InMemoryDedupStoreOptions {\n /** Purge expired entries once the map grows past this size (default: 1000). */\n maxEntries?: number;\n /** Injectable clock (default: Date.now). */\n now?: () => number;\n}\n\nexport class InMemoryDedupStore implements WebhookDedupStore {\n private readonly expiryById = new Map<string, number>();\n private readonly maxEntries: number;\n private readonly now: () => number;\n\n constructor(opts: InMemoryDedupStoreOptions = {}) {\n this.maxEntries = opts.maxEntries ?? 1000;\n this.now = opts.now ?? (() => Date.now());\n }\n\n async seen(id: string): Promise<boolean> {\n return this.expiryById.has(id);\n }\n\n async mark(id: string, ttlMs: number): Promise<void> {\n const now = this.now();\n this.expiryById.set(id, now + ttlMs);\n\n // Bounded memory: purge expired entries once the map grows too large.\n if (this.expiryById.size > this.maxEntries) {\n for (const [key, expiresAt] of this.expiryById) {\n if (now > expiresAt) {\n this.expiryById.delete(key);\n }\n }\n }\n }\n}\n","/**\n * Webhook event handler with signature verification and typed dispatch.\n */\n\nimport { verifyWebhookSignature } from '../core/webhook-utils';\nimport { ValidationError } from '../core/errors';\nimport { InMemoryDedupStore, type WebhookDedupStore } from '../core';\nimport type {\n WebhookEvent,\n WebhookEventType,\n WebhookEventHandler,\n WebhookAnyHandler,\n WebhookHandlerConfig,\n} from './types';\n\n/** Resolve the event id: the top-level `id`, or `notification.id` for the notification envelope. */\nfunction resolveEventId(event: WebhookEvent): string | undefined {\n if (event.id) {\n return event.id;\n }\n if (event.event_type === 'notification.created') {\n return event.notification?.id;\n }\n return undefined;\n}\n\nexport class WebhookHandler {\n private handlers = new Map<WebhookEventType, WebhookEventHandler[]>();\n private anyHandlers: WebhookAnyHandler[] = [];\n private readonly secret: string;\n private readonly tolerance: number;\n private readonly replayProtection: boolean;\n private readonly allowLegacySignature: boolean;\n private readonly dedupStore: WebhookDedupStore;\n\n constructor(config: WebhookHandlerConfig) {\n this.secret = config.secret;\n this.tolerance = config.tolerance ?? 300000; // 5 minutes\n this.replayProtection = config.replayProtection ?? true;\n this.allowLegacySignature = config.allowLegacySignature ?? true;\n this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();\n }\n\n /**\n * Register a handler for a specific event type.\n */\n on<T extends WebhookEventType>(\n eventType: T,\n handler: WebhookEventHandler<T>\n ): this {\n const existing = this.handlers.get(eventType) || [];\n existing.push(handler as unknown as WebhookEventHandler);\n this.handlers.set(eventType, existing);\n return this;\n }\n\n /**\n * Register a catch-all handler for all event types.\n */\n onAny(handler: WebhookAnyHandler): this {\n this.anyHandlers.push(handler);\n return this;\n }\n\n /**\n * Verify signature and parse the webhook body.\n */\n async verifyAndParse(body: string, signature: string): Promise<WebhookEvent> {\n const isValid = await verifyWebhookSignature(body, signature, this.secret, {\n tolerance: this.tolerance,\n allowLegacy: this.allowLegacySignature,\n });\n if (!isValid) {\n throw new ValidationError('Invalid webhook signature', ['signature']);\n }\n\n return this.parseAndValidate(body);\n }\n\n /**\n * Verify signature, parse, and dispatch to registered handlers.\n */\n async handle(body: string, signature: string): Promise<void> {\n const event = await this.verifyAndParse(body, signature);\n await this.dispatch(event);\n }\n\n /**\n * Parse an event without signature verification (for testing).\n */\n async parseEvent(body: string): Promise<WebhookEvent> {\n return this.parseAndValidate(body);\n }\n\n private async parseAndValidate(body: string): Promise<WebhookEvent> {\n const event = JSON.parse(body) as WebhookEvent;\n const eventId = resolveEventId(event);\n\n if (!event.event_type || !eventId || !event.timestamp) {\n throw new ValidationError(\n 'Invalid webhook event: missing required fields (event_type, id, timestamp)',\n ['event_type', 'id', 'timestamp']\n );\n }\n\n // Timestamp tolerance check (defense in depth; the signed-timestamp scheme\n // also authenticates the timestamp in verifyWebhookSignature).\n if (this.tolerance > 0) {\n const eventTime = new Date(event.timestamp).getTime();\n if (Math.abs(Date.now() - eventTime) > this.tolerance) {\n throw new ValidationError(\n `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,\n ['timestamp']\n );\n }\n }\n\n // Replay protection via the (pluggable) dedup store.\n if (this.replayProtection) {\n if (await this.dedupStore.seen(eventId)) {\n throw new ValidationError(\n `Duplicate webhook event: ${eventId} has already been processed`,\n ['id']\n );\n }\n await this.dedupStore.mark(eventId, this.tolerance);\n }\n\n return event;\n }\n\n private async dispatch(event: WebhookEvent): Promise<void> {\n const typeHandlers = this.handlers.get(event.event_type) || [];\n const allHandlers = [...typeHandlers, ...this.anyHandlers];\n\n for (const handler of allHandlers) {\n await (handler as WebhookAnyHandler)(event);\n }\n }\n}\n","/**\n * Framework middleware helpers for webhook handling.\n *\n * Provides pre-built integrations for Express/Connect and Next.js App Router.\n */\n\nimport { WebhookHandler } from './handler';\nimport type { WebhookHandlerConfig, WebhookEventType, WebhookEventHandler, WebhookAnyHandler } from './types';\n\nexport interface WebhookMiddlewareOptions extends WebhookHandlerConfig {\n /** Event handlers to register */\n handlers?: Partial<Record<WebhookEventType, WebhookEventHandler>>;\n /** Catch-all handler */\n onAny?: WebhookAnyHandler;\n}\n\n/**\n * Create an Express/Connect-compatible middleware for webhook handling.\n *\n * Expects the raw body to be available as `req.body` (string).\n * Use `express.raw({ type: 'application/json' })` or similar middleware upstream.\n */\nexport function createWebhookMiddleware(options: WebhookMiddlewareOptions) {\n const handler = buildHandler(options);\n const signatureHeader = options.signatureHeader || 'x-webhook-signature';\n\n return async (\n req: { body: string | Buffer; headers: Record<string, string | string[] | undefined> },\n res: { status(code: number): { json(body: unknown): void }; json?(body: unknown): void },\n next?: (err?: unknown) => void\n ) => {\n try {\n const body = typeof req.body === 'string' ? req.body : req.body.toString('utf-8');\n const signature = req.headers[signatureHeader];\n\n if (!signature || typeof signature !== 'string') {\n res.status(401).json({ error: 'Missing webhook signature' });\n return;\n }\n\n await handler.handle(body, signature);\n res.status(200).json({ received: true });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Webhook processing failed';\n\n if (message.includes('signature')) {\n res.status(401).json({ error: message });\n } else if (message.includes('tolerance') || message.includes('timestamp')) {\n res.status(400).json({ error: message });\n } else if (message.includes('Duplicate')) {\n res.status(409).json({ error: message });\n } else if (next) {\n next(error);\n } else {\n res.status(500).json({ error: message });\n }\n }\n };\n}\n\n/**\n * Create a Next.js App Router-compatible webhook handler.\n *\n * Returns an async function `(request: Request) => Promise<Response>`.\n */\nexport function createNextWebhookHandler(\n options: WebhookMiddlewareOptions\n): (request: Request) => Promise<Response> {\n const handler = buildHandler(options);\n const signatureHeader = options.signatureHeader || 'x-webhook-signature';\n\n return async (request: Request): Promise<Response> => {\n try {\n const body = await request.text();\n const signature = request.headers.get(signatureHeader);\n\n if (!signature) {\n return new Response(JSON.stringify({ error: 'Missing webhook signature' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n await handler.handle(body, signature);\n\n return new Response(JSON.stringify({ received: true }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Webhook processing failed';\n const status = message.includes('signature')\n ? 401\n : message.includes('tolerance') || message.includes('timestamp')\n ? 400\n : message.includes('Duplicate')\n ? 409\n : 500;\n\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n\nfunction buildHandler(options: WebhookMiddlewareOptions): WebhookHandler {\n const handler = new WebhookHandler(options);\n\n if (options.handlers) {\n for (const [eventType, eventHandler] of Object.entries(options.handlers)) {\n if (eventHandler) {\n handler.on(eventType as WebhookEventType, eventHandler);\n }\n }\n }\n\n if (options.onAny) {\n handler.onAny(options.onAny);\n }\n\n return handler;\n}\n"]}
@@ -18,7 +18,20 @@ var ValidationError = class _ValidationError extends VesantError {
18
18
  };
19
19
 
20
20
  // src/core/webhook-utils.ts
21
- async function verifyWebhookSignature(payload, signature, secret) {
21
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
22
+ const signed = parseSignedTimestamp(signature);
23
+ if (signed) {
24
+ const tolerance = opts.tolerance ?? 3e5;
25
+ const now = opts.now?.() ?? Date.now();
26
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
27
+ return false;
28
+ }
29
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
30
+ return constantTimeEqual(signed.v1, expected);
31
+ }
32
+ if (opts.allowLegacy === false) {
33
+ return false;
34
+ }
22
35
  const hexDigest = await computeHmacSha256(payload, secret);
23
36
  const expectedPrefixed = `sha256=${hexDigest}`;
24
37
  if (signature.startsWith("sha256=")) {
@@ -26,6 +39,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
26
39
  }
27
40
  return constantTimeEqual(signature, hexDigest);
28
41
  }
42
+ function parseSignedTimestamp(signature) {
43
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
44
+ return null;
45
+ }
46
+ let t;
47
+ let v1;
48
+ for (const part of signature.split(",")) {
49
+ const eq = part.indexOf("=");
50
+ if (eq === -1) continue;
51
+ const key = part.slice(0, eq).trim();
52
+ const value = part.slice(eq + 1).trim();
53
+ if (key === "t") t = Number(value);
54
+ else if (key === "v1") v1 = value;
55
+ }
56
+ if (t === void 0 || Number.isNaN(t) || !v1) {
57
+ return null;
58
+ }
59
+ return { t, v1 };
60
+ }
29
61
  async function computeHmacSha256(message, secret) {
30
62
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
31
63
  const encoder = new TextEncoder();
@@ -60,15 +92,48 @@ function constantTimeEqual(a, b) {
60
92
  return result === 0;
61
93
  }
62
94
 
95
+ // src/core/dedup.ts
96
+ var InMemoryDedupStore = class {
97
+ constructor(opts = {}) {
98
+ this.expiryById = /* @__PURE__ */ new Map();
99
+ this.maxEntries = opts.maxEntries ?? 1e3;
100
+ this.now = opts.now ?? (() => Date.now());
101
+ }
102
+ async seen(id) {
103
+ return this.expiryById.has(id);
104
+ }
105
+ async mark(id, ttlMs) {
106
+ const now = this.now();
107
+ this.expiryById.set(id, now + ttlMs);
108
+ if (this.expiryById.size > this.maxEntries) {
109
+ for (const [key, expiresAt] of this.expiryById) {
110
+ if (now > expiresAt) {
111
+ this.expiryById.delete(key);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ };
117
+
63
118
  // src/webhooks/handler.ts
119
+ function resolveEventId(event) {
120
+ if (event.id) {
121
+ return event.id;
122
+ }
123
+ if (event.event_type === "notification.created") {
124
+ return event.notification?.id;
125
+ }
126
+ return void 0;
127
+ }
64
128
  var WebhookHandler = class {
65
129
  constructor(config) {
66
130
  this.handlers = /* @__PURE__ */ new Map();
67
131
  this.anyHandlers = [];
68
- this.seenEventIds = /* @__PURE__ */ new Map();
69
132
  this.secret = config.secret;
70
133
  this.tolerance = config.tolerance ?? 3e5;
71
134
  this.replayProtection = config.replayProtection ?? true;
135
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
136
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
72
137
  }
73
138
  /**
74
139
  * Register a handler for a specific event type.
@@ -90,7 +155,10 @@ var WebhookHandler = class {
90
155
  * Verify signature and parse the webhook body.
91
156
  */
92
157
  async verifyAndParse(body, signature) {
93
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
158
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
159
+ tolerance: this.tolerance,
160
+ allowLegacy: this.allowLegacySignature
161
+ });
94
162
  if (!isValid) {
95
163
  throw new ValidationError("Invalid webhook signature", ["signature"]);
96
164
  }
@@ -106,18 +174,21 @@ var WebhookHandler = class {
106
174
  /**
107
175
  * Parse an event without signature verification (for testing).
108
176
  */
109
- parseEvent(body) {
177
+ async parseEvent(body) {
110
178
  return this.parseAndValidate(body);
111
179
  }
112
- parseAndValidate(body) {
180
+ async parseAndValidate(body) {
113
181
  const event = JSON.parse(body);
114
- if (!event.type || !event.id || !event.timestamp) {
115
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
182
+ const eventId = resolveEventId(event);
183
+ if (!event.event_type || !eventId || !event.timestamp) {
184
+ throw new ValidationError(
185
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
186
+ ["event_type", "id", "timestamp"]
187
+ );
116
188
  }
117
189
  if (this.tolerance > 0) {
118
190
  const eventTime = new Date(event.timestamp).getTime();
119
- const now = Date.now();
120
- if (Math.abs(now - eventTime) > this.tolerance) {
191
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
121
192
  throw new ValidationError(
122
193
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
123
194
  ["timestamp"]
@@ -125,26 +196,18 @@ var WebhookHandler = class {
125
196
  }
126
197
  }
127
198
  if (this.replayProtection) {
128
- if (this.seenEventIds.has(event.id)) {
199
+ if (await this.dedupStore.seen(eventId)) {
129
200
  throw new ValidationError(
130
- `Duplicate webhook event: ${event.id} has already been processed`,
201
+ `Duplicate webhook event: ${eventId} has already been processed`,
131
202
  ["id"]
132
203
  );
133
204
  }
134
- const now = Date.now();
135
- this.seenEventIds.set(event.id, now);
136
- if (this.seenEventIds.size > 1e3) {
137
- for (const [id, seenAt] of this.seenEventIds) {
138
- if (now - seenAt > this.tolerance) {
139
- this.seenEventIds.delete(id);
140
- }
141
- }
142
- }
205
+ await this.dedupStore.mark(eventId, this.tolerance);
143
206
  }
144
207
  return event;
145
208
  }
146
209
  async dispatch(event) {
147
- const typeHandlers = this.handlers.get(event.type) || [];
210
+ const typeHandlers = this.handlers.get(event.event_type) || [];
148
211
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
149
212
  for (const handler of allHandlers) {
150
213
  await handler(event);
@@ -225,6 +288,6 @@ function buildHandler(options) {
225
288
  return handler;
226
289
  }
227
290
 
228
- export { WebhookHandler, createNextWebhookHandler, createWebhookMiddleware, verifyWebhookSignature };
291
+ export { InMemoryDedupStore, WebhookHandler, createNextWebhookHandler, createWebhookMiddleware, verifyWebhookSignature };
229
292
  //# sourceMappingURL=index.mjs.map
230
293
  //# sourceMappingURL=index.mjs.map