@hookflo/tern 1.0.6 → 2.0.2-experimental.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.
Files changed (60) hide show
  1. package/README.md +204 -71
  2. package/dist/adapters/cloudflare.d.ts +11 -0
  3. package/dist/adapters/cloudflare.js +25 -0
  4. package/dist/adapters/express.d.ts +18 -0
  5. package/dist/adapters/express.js +23 -0
  6. package/dist/adapters/index.d.ts +4 -0
  7. package/dist/adapters/index.js +12 -0
  8. package/dist/adapters/nextjs.d.ts +10 -0
  9. package/dist/adapters/nextjs.js +20 -0
  10. package/dist/adapters/shared.d.ts +13 -0
  11. package/dist/adapters/shared.js +67 -0
  12. package/dist/cloudflare.d.ts +2 -0
  13. package/dist/cloudflare.js +5 -0
  14. package/dist/express.d.ts +2 -0
  15. package/dist/express.js +5 -0
  16. package/dist/index.d.ts +8 -4
  17. package/dist/index.js +66 -14
  18. package/dist/nextjs.d.ts +2 -0
  19. package/dist/nextjs.js +5 -0
  20. package/dist/normalization/index.d.ts +20 -0
  21. package/dist/normalization/index.js +78 -0
  22. package/dist/normalization/providers/payment/paypal.d.ts +2 -0
  23. package/dist/normalization/providers/payment/paypal.js +12 -0
  24. package/dist/normalization/providers/payment/razorpay.d.ts +2 -0
  25. package/dist/normalization/providers/payment/razorpay.js +13 -0
  26. package/dist/normalization/providers/payment/stripe.d.ts +2 -0
  27. package/dist/normalization/providers/payment/stripe.js +13 -0
  28. package/dist/normalization/providers/registry.d.ts +5 -0
  29. package/dist/normalization/providers/registry.js +23 -0
  30. package/dist/normalization/simple.d.ts +4 -0
  31. package/dist/normalization/simple.js +138 -0
  32. package/dist/normalization/storage/interface.d.ts +13 -0
  33. package/dist/normalization/storage/interface.js +2 -0
  34. package/dist/normalization/storage/memory.d.ts +12 -0
  35. package/dist/normalization/storage/memory.js +39 -0
  36. package/dist/normalization/templates/base/auth.d.ts +2 -0
  37. package/dist/normalization/templates/base/auth.js +22 -0
  38. package/dist/normalization/templates/base/ecommerce.d.ts +2 -0
  39. package/dist/normalization/templates/base/ecommerce.js +25 -0
  40. package/dist/normalization/templates/base/payment.d.ts +2 -0
  41. package/dist/normalization/templates/base/payment.js +25 -0
  42. package/dist/normalization/templates/registry.d.ts +6 -0
  43. package/dist/normalization/templates/registry.js +22 -0
  44. package/dist/normalization/transformer/engine.d.ts +11 -0
  45. package/dist/normalization/transformer/engine.js +86 -0
  46. package/dist/normalization/transformer/validator.d.ts +12 -0
  47. package/dist/normalization/transformer/validator.js +56 -0
  48. package/dist/normalization/types.d.ts +79 -0
  49. package/dist/normalization/types.js +2 -0
  50. package/dist/platforms/algorithms.d.ts +1 -1
  51. package/dist/platforms/algorithms.js +103 -89
  52. package/dist/test.js +98 -2
  53. package/dist/types.d.ts +73 -3
  54. package/dist/types.js +1 -0
  55. package/dist/verifiers/algorithms.d.ts +2 -2
  56. package/dist/verifiers/algorithms.js +66 -62
  57. package/dist/verifiers/base.d.ts +1 -1
  58. package/dist/verifiers/custom-algorithms.d.ts +2 -2
  59. package/dist/verifiers/custom-algorithms.js +11 -8
  60. package/package.json +22 -2
package/dist/test.js CHANGED
@@ -24,6 +24,10 @@ function createGitHubSignature(body, secret) {
24
24
  hmac.update(body);
25
25
  return `sha256=${hmac.digest('hex')}`;
26
26
  }
27
+ function createGitLabSignature(body, secret) {
28
+ // GitLab just compares the token in X-Gitlab-Token header
29
+ return secret;
30
+ }
27
31
  function createClerkSignature(body, secret, id, timestamp) {
28
32
  const signedContent = `${id}.${timestamp}.${body}`;
29
33
  const secretBytes = new Uint8Array(Buffer.from(secret.split('_')[1], 'base64'));
@@ -170,7 +174,9 @@ async function runTests() {
170
174
  'content-type': 'application/json',
171
175
  });
172
176
  const invalidResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(invalidRequest, 'stripe', testSecret);
173
- console.log(' ✅ Invalid signature correctly rejected:', !invalidResult.isValid ? 'PASSED' : 'FAILED');
177
+ const invalidSigPassed = !invalidResult.isValid && (invalidResult.errorCode === 'INVALID_SIGNATURE'
178
+ || invalidResult.errorCode === 'TIMESTAMP_EXPIRED');
179
+ console.log(' ✅ Invalid signature correctly rejected:', invalidSigPassed ? 'PASSED' : 'FAILED');
174
180
  if (invalidResult.isValid) {
175
181
  console.log(' ❌ Should have been rejected');
176
182
  }
@@ -185,7 +191,8 @@ async function runTests() {
185
191
  'content-type': 'application/json',
186
192
  });
187
193
  const missingHeaderResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(missingHeaderRequest, 'stripe', testSecret);
188
- console.log(' ✅ Missing headers correctly rejected:', !missingHeaderResult.isValid ? 'PASSED' : 'FAILED');
194
+ const missingHeaderPassed = !missingHeaderResult.isValid && missingHeaderResult.errorCode === 'MISSING_SIGNATURE';
195
+ console.log(' ✅ Missing headers correctly rejected:', missingHeaderPassed ? 'PASSED' : 'FAILED');
189
196
  if (missingHeaderResult.isValid) {
190
197
  console.log(' ❌ Should have been rejected');
191
198
  }
@@ -193,6 +200,95 @@ async function runTests() {
193
200
  catch (error) {
194
201
  console.log(' ❌ Missing headers test failed:', error);
195
202
  }
203
+ // Test 8: GitLab Webhook
204
+ console.log('\n8. Testing GitLab Webhook...');
205
+ try {
206
+ const gitlabSecret = testSecret;
207
+ const gitlabRequest = createMockRequest({
208
+ 'X-Gitlab-Token': gitlabSecret,
209
+ 'content-type': 'application/json',
210
+ });
211
+ const gitlabResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(gitlabRequest, 'gitlab', gitlabSecret);
212
+ console.log(' ✅ GitLab:', gitlabResult.isValid ? 'PASSED' : 'FAILED');
213
+ if (!gitlabResult.isValid) {
214
+ console.log(' ❌ Error:', gitlabResult.error);
215
+ }
216
+ }
217
+ catch (error) {
218
+ console.log(' ❌ GitLab test failed:', error);
219
+ }
220
+ // Test 9: GitLab Invalid Token
221
+ console.log('\n9. Testing GitLab Invalid Token...');
222
+ try {
223
+ const gitlabRequest = createMockRequest({
224
+ 'X-Gitlab-Token': 'wrong_secret',
225
+ 'content-type': 'application/json',
226
+ });
227
+ const gitlabResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(gitlabRequest, 'gitlab', testSecret);
228
+ console.log(' ✅ Invalid token correctly rejected:', !gitlabResult.isValid ? 'PASSED' : 'FAILED');
229
+ }
230
+ catch (error) {
231
+ console.log(' ❌ GitLab invalid token test failed:', error);
232
+ }
233
+ // Test 10: verifyAny should auto-detect Stripe
234
+ console.log('\n10. Testing verifyAny auto-detection...');
235
+ try {
236
+ const timestamp = Math.floor(Date.now() / 1000);
237
+ const stripeSignature = createStripeSignature(testBody, testSecret, timestamp);
238
+ const request = createMockRequest({
239
+ 'stripe-signature': stripeSignature,
240
+ 'content-type': 'application/json',
241
+ });
242
+ const result = await index_1.WebhookVerificationService.verifyAny(request, {
243
+ stripe: testSecret,
244
+ github: 'wrong-secret',
245
+ });
246
+ console.log(' ✅ verifyAny:', result.isValid && result.platform === 'stripe' ? 'PASSED' : 'FAILED');
247
+ }
248
+ catch (error) {
249
+ console.log(' ❌ verifyAny test failed:', error);
250
+ }
251
+ // Test 11: Normalization for Stripe
252
+ console.log('\n11. Testing payload normalization...');
253
+ try {
254
+ const normalizedStripeBody = JSON.stringify({
255
+ type: 'payment_intent.succeeded',
256
+ data: {
257
+ object: {
258
+ id: 'pi_123',
259
+ amount: 5000,
260
+ currency: 'usd',
261
+ customer: 'cus_456',
262
+ },
263
+ },
264
+ });
265
+ const timestamp = Math.floor(Date.now() / 1000);
266
+ const stripeSignature = createStripeSignature(normalizedStripeBody, testSecret, timestamp);
267
+ const request = createMockRequest({
268
+ 'stripe-signature': stripeSignature,
269
+ 'content-type': 'application/json',
270
+ }, normalizedStripeBody);
271
+ const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'stripe', testSecret, 300, true);
272
+ const payload = result.payload;
273
+ const passed = result.isValid
274
+ && payload.event === 'payment.succeeded'
275
+ && payload.currency === 'USD'
276
+ && payload.transaction_id === 'pi_123';
277
+ console.log(' ✅ Normalization:', passed ? 'PASSED' : 'FAILED');
278
+ }
279
+ catch (error) {
280
+ console.log(' ❌ Normalization test failed:', error);
281
+ }
282
+ // Test 12: Category-aware normalization registry
283
+ console.log('\n12. Testing category-based platform registry...');
284
+ try {
285
+ const paymentPlatforms = (0, index_1.getPlatformsByCategory)('payment');
286
+ const hasStripeAndPolar = paymentPlatforms.includes('stripe') && paymentPlatforms.includes('polar');
287
+ console.log(' ✅ Category registry:', hasStripeAndPolar ? 'PASSED' : 'FAILED');
288
+ }
289
+ catch (error) {
290
+ console.log(' ❌ Category registry test failed:', error);
291
+ }
196
292
  console.log('\n🎉 All tests completed!');
197
293
  }
198
294
  // Run tests if this file is executed directly
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type WebhookPlatform = 'custom' | 'clerk' | 'supabase' | 'github' | 'stripe' | 'shopify' | 'vercel' | 'polar' | 'dodopayments' | 'unknown';
1
+ export type WebhookPlatform = 'custom' | 'clerk' | 'supabase' | 'github' | 'stripe' | 'shopify' | 'vercel' | 'polar' | 'dodopayments' | 'gitlab' | 'unknown';
2
2
  export declare enum WebhookPlatformKeys {
3
3
  GitHub = "github",
4
4
  Stripe = "stripe",
@@ -8,6 +8,7 @@ export declare enum WebhookPlatformKeys {
8
8
  Vercel = "vercel",
9
9
  Polar = "polar",
10
10
  Supabase = "supabase",
11
+ GitLab = "gitlab",
11
12
  Custom = "custom",
12
13
  Unknown = "unknown"
13
14
  }
@@ -22,11 +23,76 @@ export interface SignatureConfig {
22
23
  payloadFormat?: 'raw' | 'timestamped' | 'custom';
23
24
  customConfig?: Record<string, any>;
24
25
  }
25
- export interface WebhookVerificationResult {
26
+ export type WebhookErrorCode = 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'TIMESTAMP_EXPIRED' | 'MISSING_TOKEN' | 'INVALID_TOKEN' | 'PLATFORM_NOT_SUPPORTED' | 'NORMALIZATION_ERROR' | 'VERIFICATION_ERROR';
27
+ export type NormalizationCategory = 'payment' | 'auth' | 'ecommerce' | 'infrastructure';
28
+ export interface BaseNormalizedWebhook {
29
+ category: NormalizationCategory;
30
+ event: string;
31
+ _platform: WebhookPlatform | string;
32
+ _raw: unknown;
33
+ occurred_at?: string;
34
+ }
35
+ export type PaymentWebhookEvent = 'payment.succeeded' | 'payment.failed' | 'payment.refunded' | 'subscription.created' | 'subscription.cancelled' | 'payment.unknown';
36
+ export interface PaymentWebhookNormalized extends BaseNormalizedWebhook {
37
+ category: 'payment';
38
+ event: PaymentWebhookEvent;
39
+ amount?: number;
40
+ currency?: string;
41
+ customer_id?: string;
42
+ transaction_id?: string;
43
+ subscription_id?: string;
44
+ refund_amount?: number;
45
+ failure_reason?: string;
46
+ metadata?: Record<string, string>;
47
+ }
48
+ export type AuthWebhookEvent = 'user.created' | 'user.updated' | 'user.deleted' | 'session.started' | 'session.ended' | 'auth.unknown';
49
+ export interface AuthWebhookNormalized extends BaseNormalizedWebhook {
50
+ category: 'auth';
51
+ event: AuthWebhookEvent;
52
+ user_id?: string;
53
+ email?: string;
54
+ phone?: string;
55
+ metadata?: Record<string, string>;
56
+ }
57
+ export interface EcommerceWebhookNormalized extends BaseNormalizedWebhook {
58
+ category: 'ecommerce';
59
+ event: string;
60
+ order_id?: string;
61
+ customer_id?: string;
62
+ amount?: number;
63
+ currency?: string;
64
+ metadata?: Record<string, string>;
65
+ }
66
+ export interface InfrastructureWebhookNormalized extends BaseNormalizedWebhook {
67
+ category: 'infrastructure';
68
+ event: string;
69
+ project_id?: string;
70
+ deployment_id?: string;
71
+ status?: 'queued' | 'building' | 'ready' | 'error' | 'unknown';
72
+ metadata?: Record<string, string>;
73
+ }
74
+ export interface UnknownNormalizedWebhook extends BaseNormalizedWebhook {
75
+ event: string;
76
+ warning?: string;
77
+ }
78
+ export type NormalizedPayloadByCategory = {
79
+ payment: PaymentWebhookNormalized;
80
+ auth: AuthWebhookNormalized;
81
+ ecommerce: EcommerceWebhookNormalized;
82
+ infrastructure: InfrastructureWebhookNormalized;
83
+ };
84
+ export type AnyNormalizedWebhook = PaymentWebhookNormalized | AuthWebhookNormalized | EcommerceWebhookNormalized | InfrastructureWebhookNormalized | UnknownNormalizedWebhook;
85
+ export interface NormalizeOptions {
86
+ enabled?: boolean;
87
+ category?: NormalizationCategory;
88
+ includeRaw?: boolean;
89
+ }
90
+ export interface WebhookVerificationResult<TPayload = unknown> {
26
91
  isValid: boolean;
27
92
  error?: string;
93
+ errorCode?: WebhookErrorCode;
28
94
  platform: WebhookPlatform;
29
- payload?: any;
95
+ payload?: TPayload;
30
96
  metadata?: {
31
97
  timestamp?: string;
32
98
  id?: string | null;
@@ -38,6 +104,10 @@ export interface WebhookConfig {
38
104
  secret: string;
39
105
  toleranceInSeconds?: number;
40
106
  signatureConfig?: SignatureConfig;
107
+ normalize?: boolean | NormalizeOptions;
108
+ }
109
+ export interface MultiPlatformSecrets {
110
+ [platform: string]: string | undefined;
41
111
  }
42
112
  export interface PlatformAlgorithmConfig {
43
113
  platform: WebhookPlatform;
package/dist/types.js CHANGED
@@ -11,6 +11,7 @@ var WebhookPlatformKeys;
11
11
  WebhookPlatformKeys["Vercel"] = "vercel";
12
12
  WebhookPlatformKeys["Polar"] = "polar";
13
13
  WebhookPlatformKeys["Supabase"] = "supabase";
14
+ WebhookPlatformKeys["GitLab"] = "gitlab";
14
15
  WebhookPlatformKeys["Custom"] = "custom";
15
16
  WebhookPlatformKeys["Unknown"] = "unknown";
16
17
  })(WebhookPlatformKeys || (exports.WebhookPlatformKeys = WebhookPlatformKeys = {}));
@@ -1,5 +1,5 @@
1
- import { WebhookVerifier } from "./base";
2
- import { WebhookVerificationResult, SignatureConfig, WebhookPlatform } from "../types";
1
+ import { WebhookVerifier } from './base';
2
+ import { WebhookVerificationResult, SignatureConfig, WebhookPlatform } from '../types';
3
3
  export declare abstract class AlgorithmBasedVerifier extends WebhookVerifier {
4
4
  protected config: SignatureConfig;
5
5
  protected platform: WebhookPlatform;
@@ -15,27 +15,27 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
15
15
  if (!headerValue)
16
16
  return null;
17
17
  switch (this.config.headerFormat) {
18
- case "prefixed":
18
+ case 'prefixed':
19
19
  // For GitHub, return the full signature including prefix for comparison
20
20
  return headerValue;
21
- case "comma-separated":
21
+ case 'comma-separated':
22
22
  // Handle comma-separated format like Stripe: "t=1234567890,v1=abc123"
23
- const parts = headerValue.split(",");
23
+ const parts = headerValue.split(',');
24
24
  const sigMap = {};
25
25
  for (const part of parts) {
26
- const [key, value] = part.split("=");
26
+ const [key, value] = part.split('=');
27
27
  if (key && value) {
28
28
  sigMap[key] = value;
29
29
  }
30
30
  }
31
31
  return sigMap.v1 || sigMap.signature || null;
32
- case "raw":
32
+ case 'raw':
33
33
  default:
34
- if (this.platform === "clerk" || this.platform === "dodopayments") {
35
- const signatures = headerValue.split(" ");
34
+ if (this.platform === 'clerk' || this.platform === 'dodopayments') {
35
+ const signatures = headerValue.split(' ');
36
36
  for (const sig of signatures) {
37
- const [version, signature] = sig.split(",");
38
- if (version === "v1") {
37
+ const [version, signature] = sig.split(',');
38
+ if (version === 'v1') {
39
39
  return signature;
40
40
  }
41
41
  }
@@ -51,11 +51,11 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
51
51
  if (!timestampHeader)
52
52
  return null;
53
53
  switch (this.config.timestampFormat) {
54
- case "unix":
54
+ case 'unix':
55
55
  return parseInt(timestampHeader, 10);
56
- case "iso":
56
+ case 'iso':
57
57
  return Math.floor(new Date(timestampHeader).getTime() / 1000);
58
- case "custom":
58
+ case 'custom':
59
59
  // Custom timestamp parsing logic can be added here
60
60
  return parseInt(timestampHeader, 10);
61
61
  default:
@@ -64,14 +64,14 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
64
64
  }
65
65
  extractTimestampFromSignature(request) {
66
66
  // For platforms like Stripe where timestamp is embedded in signature
67
- if (this.config.headerFormat === "comma-separated") {
67
+ if (this.config.headerFormat === 'comma-separated') {
68
68
  const headerValue = request.headers.get(this.config.headerName);
69
69
  if (!headerValue)
70
70
  return null;
71
- const parts = headerValue.split(",");
71
+ const parts = headerValue.split(',');
72
72
  const sigMap = {};
73
73
  for (const part of parts) {
74
- const [key, value] = part.split("=");
74
+ const [key, value] = part.split('=');
75
75
  if (key && value) {
76
76
  sigMap[key] = value;
77
77
  }
@@ -82,14 +82,14 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
82
82
  }
83
83
  formatPayload(rawBody, request) {
84
84
  switch (this.config.payloadFormat) {
85
- case "timestamped":
85
+ case 'timestamped':
86
86
  // For Stripe, timestamp is embedded in signature
87
- const timestamp = this.extractTimestampFromSignature(request) ||
88
- this.extractTimestamp(request);
87
+ const timestamp = this.extractTimestampFromSignature(request)
88
+ || this.extractTimestamp(request);
89
89
  return timestamp ? `${timestamp}.${rawBody}` : rawBody;
90
- case "custom":
90
+ case 'custom':
91
91
  return this.formatCustomPayload(rawBody, request);
92
- case "raw":
92
+ case 'raw':
93
93
  default:
94
94
  return rawBody;
95
95
  }
@@ -100,42 +100,42 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
100
100
  }
101
101
  const customFormat = this.config.customConfig.payloadFormat;
102
102
  // Handle Clerk-style format: {id}.{timestamp}.{body}
103
- if (customFormat.includes("{id}") && customFormat.includes("{timestamp}")) {
104
- const id = request.headers.get(this.config.customConfig.idHeader || "x-webhook-id");
105
- const timestamp = request.headers.get(this.config.timestampHeader || "x-webhook-timestamp");
103
+ if (customFormat.includes('{id}') && customFormat.includes('{timestamp}')) {
104
+ const id = request.headers.get(this.config.customConfig.idHeader || 'x-webhook-id');
105
+ const timestamp = request.headers.get(this.config.timestampHeader || 'x-webhook-timestamp');
106
106
  return customFormat
107
- .replace("{id}", id || "")
108
- .replace("{timestamp}", timestamp || "")
109
- .replace("{body}", rawBody);
107
+ .replace('{id}', id || '')
108
+ .replace('{timestamp}', timestamp || '')
109
+ .replace('{body}', rawBody);
110
110
  }
111
111
  // Handle Stripe-style format: {timestamp}.{body}
112
- if (customFormat.includes("{timestamp}") &&
113
- customFormat.includes("{body}")) {
112
+ if (customFormat.includes('{timestamp}')
113
+ && customFormat.includes('{body}')) {
114
114
  const timestamp = this.extractTimestamp(request);
115
115
  return customFormat
116
- .replace("{timestamp}", timestamp?.toString() || "")
117
- .replace("{body}", rawBody);
116
+ .replace('{timestamp}', timestamp?.toString() || '')
117
+ .replace('{body}', rawBody);
118
118
  }
119
119
  return rawBody;
120
120
  }
121
- verifyHMAC(payload, signature, algorithm = "sha256") {
121
+ verifyHMAC(payload, signature, algorithm = 'sha256') {
122
122
  const hmac = (0, crypto_1.createHmac)(algorithm, this.secret);
123
123
  hmac.update(payload);
124
- const expectedSignature = hmac.digest("hex");
124
+ const expectedSignature = hmac.digest('hex');
125
125
  return this.safeCompare(signature, expectedSignature);
126
126
  }
127
- verifyHMACWithPrefix(payload, signature, algorithm = "sha256") {
127
+ verifyHMACWithPrefix(payload, signature, algorithm = 'sha256') {
128
128
  const hmac = (0, crypto_1.createHmac)(algorithm, this.secret);
129
129
  hmac.update(payload);
130
- const expectedSignature = `${this.config.prefix || ""}${hmac.digest("hex")}`;
130
+ const expectedSignature = `${this.config.prefix || ''}${hmac.digest('hex')}`;
131
131
  return this.safeCompare(signature, expectedSignature);
132
132
  }
133
- verifyHMACWithBase64(payload, signature, algorithm = "sha256") {
133
+ verifyHMACWithBase64(payload, signature, algorithm = 'sha256') {
134
134
  // For platforms like Clerk that use base64 encoding
135
- const secretBytes = new Uint8Array(Buffer.from(this.secret.split("_")[1], "base64"));
135
+ const secretBytes = new Uint8Array(Buffer.from(this.secret.split('_')[1], 'base64'));
136
136
  const hmac = (0, crypto_1.createHmac)(algorithm, secretBytes);
137
137
  hmac.update(payload);
138
- const expectedSignature = hmac.digest("base64");
138
+ const expectedSignature = hmac.digest('base64');
139
139
  return this.safeCompare(signature, expectedSignature);
140
140
  }
141
141
  extractMetadata(request) {
@@ -149,18 +149,18 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
149
149
  }
150
150
  // Add platform-specific metadata
151
151
  switch (this.platform) {
152
- case "github":
153
- metadata.event = request.headers.get("x-github-event");
154
- metadata.delivery = request.headers.get("x-github-delivery");
152
+ case 'github':
153
+ metadata.event = request.headers.get('x-github-event');
154
+ metadata.delivery = request.headers.get('x-github-delivery');
155
155
  break;
156
- case "stripe":
156
+ case 'stripe':
157
157
  // Extract Stripe-specific metadata from signature
158
158
  const headerValue = request.headers.get(this.config.headerName);
159
- if (headerValue && this.config.headerFormat === "comma-separated") {
160
- const parts = headerValue.split(",");
159
+ if (headerValue && this.config.headerFormat === 'comma-separated') {
160
+ const parts = headerValue.split(',');
161
161
  const sigMap = {};
162
162
  for (const part of parts) {
163
- const [key, value] = part.split("=");
163
+ const [key, value] = part.split('=');
164
164
  if (key && value) {
165
165
  sigMap[key] = value;
166
166
  }
@@ -168,8 +168,8 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
168
168
  metadata.id = sigMap.id;
169
169
  }
170
170
  break;
171
- case "clerk":
172
- metadata.id = request.headers.get("svix-id");
171
+ case 'clerk':
172
+ metadata.id = request.headers.get('svix-id');
173
173
  break;
174
174
  }
175
175
  return metadata;
@@ -185,13 +185,14 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
185
185
  return {
186
186
  isValid: false,
187
187
  error: `Missing signature header: ${this.config.headerName}`,
188
+ errorCode: 'MISSING_SIGNATURE',
188
189
  platform: this.platform,
189
190
  };
190
191
  }
191
192
  const rawBody = await request.text();
192
193
  // Extract timestamp based on platform configuration
193
194
  let timestamp = null;
194
- if (this.config.headerFormat === "comma-separated") {
195
+ if (this.config.headerFormat === 'comma-separated') {
195
196
  // For platforms like Stripe where timestamp is embedded in signature
196
197
  timestamp = this.extractTimestampFromSignature(request);
197
198
  }
@@ -203,7 +204,8 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
203
204
  if (timestamp && !this.isTimestampValid(timestamp)) {
204
205
  return {
205
206
  isValid: false,
206
- error: "Webhook timestamp expired",
207
+ error: 'Webhook timestamp expired',
208
+ errorCode: 'TIMESTAMP_EXPIRED',
207
209
  platform: this.platform,
208
210
  };
209
211
  }
@@ -211,12 +213,12 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
211
213
  const payload = this.formatPayload(rawBody, request);
212
214
  // Verify signature based on platform configuration
213
215
  let isValid = false;
214
- const algorithm = this.config.algorithm.replace("hmac-", "");
215
- if (this.config.customConfig?.encoding === "base64") {
216
+ const algorithm = this.config.algorithm.replace('hmac-', '');
217
+ if (this.config.customConfig?.encoding === 'base64') {
216
218
  // For platforms like Clerk that use base64 encoding
217
219
  isValid = this.verifyHMACWithBase64(payload, signature, algorithm);
218
220
  }
219
- else if (this.config.headerFormat === "prefixed") {
221
+ else if (this.config.headerFormat === 'prefixed') {
220
222
  // For platforms like GitHub that use prefixed signatures
221
223
  isValid = this.verifyHMACWithPrefix(payload, signature, algorithm);
222
224
  }
@@ -227,7 +229,8 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
227
229
  if (!isValid) {
228
230
  return {
229
231
  isValid: false,
230
- error: "Invalid signature",
232
+ error: 'Invalid signature',
233
+ errorCode: 'INVALID_SIGNATURE',
231
234
  platform: this.platform,
232
235
  };
233
236
  }
@@ -252,6 +255,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
252
255
  return {
253
256
  isValid: false,
254
257
  error: `${this.platform} verification error: ${error.message}`,
258
+ errorCode: 'VERIFICATION_ERROR',
255
259
  platform: this.platform,
256
260
  };
257
261
  }
@@ -260,33 +264,33 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
260
264
  exports.GenericHMACVerifier = GenericHMACVerifier;
261
265
  // Legacy verifiers for backward compatibility
262
266
  class HMACSHA256Verifier extends GenericHMACVerifier {
263
- constructor(secret, config, platform = "unknown", toleranceInSeconds = 300) {
267
+ constructor(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
264
268
  super(secret, config, platform, toleranceInSeconds);
265
269
  }
266
270
  }
267
271
  exports.HMACSHA256Verifier = HMACSHA256Verifier;
268
272
  class HMACSHA1Verifier extends GenericHMACVerifier {
269
- constructor(secret, config, platform = "unknown", toleranceInSeconds = 300) {
273
+ constructor(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
270
274
  super(secret, config, platform, toleranceInSeconds);
271
275
  }
272
276
  }
273
277
  exports.HMACSHA1Verifier = HMACSHA1Verifier;
274
278
  class HMACSHA512Verifier extends GenericHMACVerifier {
275
- constructor(secret, config, platform = "unknown", toleranceInSeconds = 300) {
279
+ constructor(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
276
280
  super(secret, config, platform, toleranceInSeconds);
277
281
  }
278
282
  }
279
283
  exports.HMACSHA512Verifier = HMACSHA512Verifier;
280
284
  // Factory function to create verifiers based on algorithm
281
- function createAlgorithmVerifier(secret, config, platform = "unknown", toleranceInSeconds = 300) {
285
+ function createAlgorithmVerifier(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
282
286
  switch (config.algorithm) {
283
- case "hmac-sha256":
284
- case "hmac-sha1":
285
- case "hmac-sha512":
287
+ case 'hmac-sha256':
288
+ case 'hmac-sha1':
289
+ case 'hmac-sha512':
286
290
  return new GenericHMACVerifier(secret, config, platform, toleranceInSeconds);
287
- case "rsa-sha256":
288
- case "ed25519":
289
- case "custom":
291
+ case 'rsa-sha256':
292
+ case 'ed25519':
293
+ case 'custom':
290
294
  // These can be implemented as needed
291
295
  throw new Error(`Algorithm ${config.algorithm} not yet implemented`);
292
296
  default:
@@ -1,4 +1,4 @@
1
- import { WebhookVerificationResult } from "../types";
1
+ import { WebhookVerificationResult } from '../types';
2
2
  export declare abstract class WebhookVerifier {
3
3
  protected secret: string;
4
4
  protected toleranceInSeconds: number;
@@ -1,5 +1,5 @@
1
- import { WebhookVerifier } from "./base";
2
- import { WebhookVerificationResult, SignatureConfig } from "../types";
1
+ import { WebhookVerifier } from './base';
2
+ import { WebhookVerificationResult, SignatureConfig } from '../types';
3
3
  export declare class TokenBasedVerifier extends WebhookVerifier {
4
4
  private config;
5
5
  constructor(secret: string, config: SignatureConfig, toleranceInSeconds?: number);
@@ -12,12 +12,13 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
12
12
  async verify(request) {
13
13
  try {
14
14
  const token = request.headers.get(this.config.headerName);
15
- const id = request.headers.get(this.config.customConfig?.idHeader || "x-webhook-id");
15
+ const id = request.headers.get(this.config.customConfig?.idHeader || 'x-webhook-id');
16
16
  if (!token) {
17
17
  return {
18
18
  isValid: false,
19
19
  error: `Missing token header: ${this.config.headerName}`,
20
- platform: "custom",
20
+ errorCode: 'MISSING_TOKEN',
21
+ platform: 'custom',
21
22
  };
22
23
  }
23
24
  // Simple token comparison
@@ -25,8 +26,9 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
25
26
  if (!isValid) {
26
27
  return {
27
28
  isValid: false,
28
- error: "Invalid token",
29
- platform: "custom",
29
+ error: 'Invalid token',
30
+ errorCode: 'INVALID_TOKEN',
31
+ platform: 'custom',
30
32
  };
31
33
  }
32
34
  const rawBody = await request.text();
@@ -39,11 +41,11 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
39
41
  }
40
42
  return {
41
43
  isValid: true,
42
- platform: "custom",
44
+ platform: 'custom',
43
45
  payload,
44
46
  metadata: {
45
47
  id,
46
- algorithm: "token-based",
48
+ algorithm: 'token-based',
47
49
  },
48
50
  };
49
51
  }
@@ -51,7 +53,8 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
51
53
  return {
52
54
  isValid: false,
53
55
  error: `Token-based verification error: ${error.message}`,
54
- platform: "custom",
56
+ errorCode: 'VERIFICATION_ERROR',
57
+ platform: 'custom',
55
58
  };
56
59
  }
57
60
  }
@@ -61,7 +64,7 @@ exports.TokenBasedVerifier = TokenBasedVerifier;
61
64
  function createCustomVerifier(secret, config, toleranceInSeconds = 300) {
62
65
  const customType = config.customConfig?.type;
63
66
  switch (customType) {
64
- case "token-based":
67
+ case 'token-based':
65
68
  return new TokenBasedVerifier(secret, config, toleranceInSeconds);
66
69
  default:
67
70
  // Fallback to token-based for unknown custom types