@hookflo/tern 4.2.9-beta → 4.3.0-beta-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +283 -481
- package/dist/adapters/cloudflare.d.ts +2 -2
- package/dist/adapters/cloudflare.js +6 -1
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +6 -1
- package/dist/adapters/hono.d.ts +21 -0
- package/dist/adapters/hono.js +74 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +6 -1
- package/dist/adapters/shared.js +5 -2
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +5 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +29 -19
- package/dist/platforms/algorithms.js +65 -0
- package/dist/types.d.ts +6 -65
- package/dist/types.js +4 -0
- package/dist/verifiers/algorithms.d.ts +8 -0
- package/dist/verifiers/algorithms.js +145 -17
- package/package.json +13 -2
- package/dist/normalization/index.d.ts +0 -20
- package/dist/normalization/index.js +0 -78
- package/dist/normalization/providers/payment/paypal.d.ts +0 -2
- package/dist/normalization/providers/payment/paypal.js +0 -12
- package/dist/normalization/providers/payment/razorpay.d.ts +0 -2
- package/dist/normalization/providers/payment/razorpay.js +0 -13
- package/dist/normalization/providers/payment/stripe.d.ts +0 -2
- package/dist/normalization/providers/payment/stripe.js +0 -13
- package/dist/normalization/providers/registry.d.ts +0 -5
- package/dist/normalization/providers/registry.js +0 -21
- package/dist/normalization/simple.d.ts +0 -4
- package/dist/normalization/simple.js +0 -126
- package/dist/normalization/storage/interface.d.ts +0 -13
- package/dist/normalization/storage/interface.js +0 -2
- package/dist/normalization/storage/memory.d.ts +0 -12
- package/dist/normalization/storage/memory.js +0 -39
- package/dist/normalization/templates/base/auth.d.ts +0 -2
- package/dist/normalization/templates/base/auth.js +0 -22
- package/dist/normalization/templates/base/ecommerce.d.ts +0 -2
- package/dist/normalization/templates/base/ecommerce.js +0 -25
- package/dist/normalization/templates/base/payment.d.ts +0 -2
- package/dist/normalization/templates/base/payment.js +0 -25
- package/dist/normalization/templates/registry.d.ts +0 -6
- package/dist/normalization/templates/registry.js +0 -22
- package/dist/normalization/transformer/engine.d.ts +0 -11
- package/dist/normalization/transformer/engine.js +0 -86
- package/dist/normalization/transformer/validator.d.ts +0 -12
- package/dist/normalization/transformer/validator.js +0 -56
- package/dist/normalization/types.d.ts +0 -79
- package/dist/normalization/types.js +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebhookPlatform
|
|
1
|
+
import { WebhookPlatform } from '../types';
|
|
2
2
|
import { QueueOption } from '../upstash/types';
|
|
3
3
|
import type { AlertConfig, SendAlertOptions } from '../notifications/types';
|
|
4
4
|
export interface CloudflareWebhookHandlerOptions<TEnv = Record<string, unknown>, TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
|
|
@@ -6,7 +6,7 @@ export interface CloudflareWebhookHandlerOptions<TEnv = Record<string, unknown>,
|
|
|
6
6
|
secret?: string;
|
|
7
7
|
secretEnv?: string;
|
|
8
8
|
toleranceInSeconds?: number;
|
|
9
|
-
|
|
9
|
+
twilioBaseUrl?: string;
|
|
10
10
|
queue?: QueueOption;
|
|
11
11
|
alerts?: AlertConfig;
|
|
12
12
|
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
|
@@ -39,7 +39,12 @@ function createWebhookHandler(options) {
|
|
|
39
39
|
}
|
|
40
40
|
return response;
|
|
41
41
|
}
|
|
42
|
-
const result = await index_1.WebhookVerificationService.
|
|
42
|
+
const result = await index_1.WebhookVerificationService.verify(request, {
|
|
43
|
+
platform: options.platform,
|
|
44
|
+
secret,
|
|
45
|
+
toleranceInSeconds: options.toleranceInSeconds,
|
|
46
|
+
twilioBaseUrl: options.twilioBaseUrl,
|
|
47
|
+
});
|
|
43
48
|
if (!result.isValid) {
|
|
44
49
|
return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
|
|
45
50
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebhookPlatform, WebhookVerificationResult
|
|
1
|
+
import { WebhookPlatform, WebhookVerificationResult } from '../types';
|
|
2
2
|
import { QueueOption } from '../upstash/types';
|
|
3
3
|
import { MinimalNodeRequest } from './shared';
|
|
4
4
|
import type { AlertConfig, SendAlertOptions } from '../notifications/types';
|
|
@@ -14,7 +14,7 @@ export interface ExpressWebhookMiddlewareOptions {
|
|
|
14
14
|
platform: WebhookPlatform;
|
|
15
15
|
secret: string;
|
|
16
16
|
toleranceInSeconds?: number;
|
|
17
|
-
|
|
17
|
+
twilioBaseUrl?: string;
|
|
18
18
|
queue?: QueueOption;
|
|
19
19
|
alerts?: AlertConfig;
|
|
20
20
|
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
package/dist/adapters/express.js
CHANGED
|
@@ -48,7 +48,12 @@ function createWebhookMiddleware(options) {
|
|
|
48
48
|
}
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
const result = await index_1.WebhookVerificationService.
|
|
51
|
+
const result = await index_1.WebhookVerificationService.verify(webRequest, {
|
|
52
|
+
platform: options.platform,
|
|
53
|
+
secret: options.secret,
|
|
54
|
+
toleranceInSeconds: options.toleranceInSeconds,
|
|
55
|
+
twilioBaseUrl: options.twilioBaseUrl,
|
|
56
|
+
});
|
|
52
57
|
if (!result.isValid) {
|
|
53
58
|
res.status(400).json({
|
|
54
59
|
error: result.error,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { WebhookPlatform } from '../types';
|
|
2
|
+
import { QueueOption } from '../upstash/types';
|
|
3
|
+
import type { AlertConfig, SendAlertOptions } from '../notifications/types';
|
|
4
|
+
export interface HonoContextLike {
|
|
5
|
+
req: {
|
|
6
|
+
raw: Request;
|
|
7
|
+
};
|
|
8
|
+
json: (payload: unknown, status?: number) => Response;
|
|
9
|
+
}
|
|
10
|
+
export interface HonoWebhookHandlerOptions<TContext extends HonoContextLike = HonoContextLike, TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
|
|
11
|
+
platform: WebhookPlatform;
|
|
12
|
+
secret: string;
|
|
13
|
+
toleranceInSeconds?: number;
|
|
14
|
+
twilioBaseUrl?: string;
|
|
15
|
+
queue?: QueueOption;
|
|
16
|
+
alerts?: AlertConfig;
|
|
17
|
+
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
|
18
|
+
onError?: (error: Error) => void;
|
|
19
|
+
handler: (payload: TPayload, metadata: TMetadata, c: TContext) => Promise<TResponse> | TResponse;
|
|
20
|
+
}
|
|
21
|
+
export declare function createWebhookHandler<TContext extends HonoContextLike = HonoContextLike, TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown>(options: HonoWebhookHandlerOptions<TContext, TPayload, TMetadata, TResponse>): (c: TContext) => Promise<Response>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWebhookHandler = createWebhookHandler;
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
const queue_1 = require("../upstash/queue");
|
|
6
|
+
const dispatch_1 = require("../notifications/dispatch");
|
|
7
|
+
function createWebhookHandler(options) {
|
|
8
|
+
return async (c) => {
|
|
9
|
+
try {
|
|
10
|
+
const request = c.req.raw;
|
|
11
|
+
if (options.queue) {
|
|
12
|
+
const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
|
|
13
|
+
const response = await (0, queue_1.handleQueuedRequest)(request, {
|
|
14
|
+
platform: options.platform,
|
|
15
|
+
secret: options.secret,
|
|
16
|
+
queueConfig,
|
|
17
|
+
handler: (payload, metadata) => options.handler(payload, metadata, c),
|
|
18
|
+
toleranceInSeconds: options.toleranceInSeconds ?? 300,
|
|
19
|
+
});
|
|
20
|
+
if (response.ok) {
|
|
21
|
+
let eventId;
|
|
22
|
+
try {
|
|
23
|
+
const body = await response.clone().json();
|
|
24
|
+
eventId = typeof body.eventId === 'string' ? body.eventId : undefined;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
eventId = undefined;
|
|
28
|
+
}
|
|
29
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
30
|
+
alerts: options.alerts,
|
|
31
|
+
source: options.platform,
|
|
32
|
+
eventId,
|
|
33
|
+
alert: options.alert,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
const result = await index_1.WebhookVerificationService.verify(request, {
|
|
39
|
+
platform: options.platform,
|
|
40
|
+
secret: options.secret,
|
|
41
|
+
toleranceInSeconds: options.toleranceInSeconds,
|
|
42
|
+
twilioBaseUrl: options.twilioBaseUrl,
|
|
43
|
+
});
|
|
44
|
+
if (!result.isValid) {
|
|
45
|
+
return c.json({
|
|
46
|
+
error: result.error,
|
|
47
|
+
errorCode: result.errorCode,
|
|
48
|
+
platform: result.platform,
|
|
49
|
+
metadata: result.metadata,
|
|
50
|
+
}, 400);
|
|
51
|
+
}
|
|
52
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
53
|
+
alerts: options.alerts,
|
|
54
|
+
source: options.platform,
|
|
55
|
+
eventId: result.eventId,
|
|
56
|
+
alert: options.alert,
|
|
57
|
+
});
|
|
58
|
+
const data = await options.handler(result.payload, (result.metadata || {}), c);
|
|
59
|
+
if (data instanceof Response) {
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
return c.json(data);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (options.onError) {
|
|
66
|
+
options.onError(error);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.error('[tern/hono]', error);
|
|
70
|
+
}
|
|
71
|
+
return c.json({ error: error.message }, 500);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { createWebhookMiddleware, ExpressWebhookMiddlewareOptions, ExpressLikeRe
|
|
|
2
2
|
export { createWebhookHandler as createNextjsWebhookHandler, NextWebhookHandlerOptions, } from './nextjs';
|
|
3
3
|
export { createWebhookHandler as createCloudflareWebhookHandler, CloudflareWebhookHandlerOptions, } from './cloudflare';
|
|
4
4
|
export { toWebRequest, extractRawBody } from './shared';
|
|
5
|
+
export { createWebhookHandler as createHonoWebhookHandler, HonoWebhookHandlerOptions, HonoContextLike, } from './hono';
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractRawBody = exports.toWebRequest = exports.createCloudflareWebhookHandler = exports.createNextjsWebhookHandler = exports.createWebhookMiddleware = void 0;
|
|
3
|
+
exports.createHonoWebhookHandler = exports.extractRawBody = exports.toWebRequest = exports.createCloudflareWebhookHandler = exports.createNextjsWebhookHandler = exports.createWebhookMiddleware = void 0;
|
|
4
4
|
var express_1 = require("./express");
|
|
5
5
|
Object.defineProperty(exports, "createWebhookMiddleware", { enumerable: true, get: function () { return express_1.createWebhookMiddleware; } });
|
|
6
6
|
var nextjs_1 = require("./nextjs");
|
|
@@ -10,3 +10,5 @@ Object.defineProperty(exports, "createCloudflareWebhookHandler", { enumerable: t
|
|
|
10
10
|
var shared_1 = require("./shared");
|
|
11
11
|
Object.defineProperty(exports, "toWebRequest", { enumerable: true, get: function () { return shared_1.toWebRequest; } });
|
|
12
12
|
Object.defineProperty(exports, "extractRawBody", { enumerable: true, get: function () { return shared_1.extractRawBody; } });
|
|
13
|
+
var hono_1 = require("./hono");
|
|
14
|
+
Object.defineProperty(exports, "createHonoWebhookHandler", { enumerable: true, get: function () { return hono_1.createWebhookHandler; } });
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { WebhookPlatform
|
|
1
|
+
import { WebhookPlatform } from '../types';
|
|
2
2
|
import { QueueOption } from '../upstash/types';
|
|
3
3
|
import type { AlertConfig, SendAlertOptions } from '../notifications/types';
|
|
4
4
|
export interface NextWebhookHandlerOptions<TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
|
|
5
5
|
platform: WebhookPlatform;
|
|
6
6
|
secret: string;
|
|
7
7
|
toleranceInSeconds?: number;
|
|
8
|
-
|
|
8
|
+
twilioBaseUrl?: string;
|
|
9
9
|
queue?: QueueOption;
|
|
10
10
|
alerts?: AlertConfig;
|
|
11
11
|
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
package/dist/adapters/nextjs.js
CHANGED
|
@@ -34,7 +34,12 @@ function createWebhookHandler(options) {
|
|
|
34
34
|
}
|
|
35
35
|
return response;
|
|
36
36
|
}
|
|
37
|
-
const result = await index_1.WebhookVerificationService.
|
|
37
|
+
const result = await index_1.WebhookVerificationService.verify(request, {
|
|
38
|
+
platform: options.platform,
|
|
39
|
+
secret: options.secret,
|
|
40
|
+
toleranceInSeconds: options.toleranceInSeconds,
|
|
41
|
+
twilioBaseUrl: options.twilioBaseUrl,
|
|
42
|
+
});
|
|
38
43
|
if (!result.isValid) {
|
|
39
44
|
return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
|
|
40
45
|
}
|
package/dist/adapters/shared.js
CHANGED
|
@@ -90,8 +90,11 @@ function toHeadersInit(headers) {
|
|
|
90
90
|
return normalized;
|
|
91
91
|
}
|
|
92
92
|
async function toWebRequest(request) {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
93
|
+
const forwardedProto = getHeaderValue(request.headers, 'x-forwarded-proto')?.split(',')[0]?.trim();
|
|
94
|
+
const protocol = forwardedProto || request.protocol || 'https';
|
|
95
|
+
const forwardedHost = getHeaderValue(request.headers, 'x-forwarded-host')?.split(',')[0]?.trim();
|
|
96
|
+
const host = forwardedHost
|
|
97
|
+
|| request.get?.('host')
|
|
95
98
|
|| getHeaderValue(request.headers, 'host')
|
|
96
99
|
|| 'localhost';
|
|
97
100
|
const path = request.originalUrl || request.url || '/';
|
package/dist/hono.d.ts
ADDED
package/dist/hono.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWebhookHandler = void 0;
|
|
4
|
+
var hono_1 = require("./adapters/hono");
|
|
5
|
+
Object.defineProperty(exports, "createWebhookHandler", { enumerable: true, get: function () { return hono_1.createWebhookHandler; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebhookConfig, WebhookVerificationResult, WebhookPlatform, SignatureConfig, MultiPlatformSecrets
|
|
1
|
+
import { WebhookConfig, WebhookVerificationResult, WebhookPlatform, SignatureConfig, MultiPlatformSecrets } from './types';
|
|
2
2
|
import type { QueueOption } from './upstash/types';
|
|
3
3
|
import type { AlertConfig, SendAlertOptions } from './notifications/types';
|
|
4
4
|
export declare class WebhookVerificationService {
|
|
@@ -6,8 +6,8 @@ export declare class WebhookVerificationService {
|
|
|
6
6
|
private static getVerifier;
|
|
7
7
|
private static createAlgorithmBasedVerifier;
|
|
8
8
|
private static getLegacyVerifier;
|
|
9
|
-
static verifyWithPlatformConfig<TPayload = unknown>(request: Request, platform: WebhookPlatform, secret: string, toleranceInSeconds?: number
|
|
10
|
-
static verifyAny<TPayload = unknown>(request: Request, secrets: MultiPlatformSecrets, toleranceInSeconds?: number
|
|
9
|
+
static verifyWithPlatformConfig<TPayload = unknown>(request: Request, platform: WebhookPlatform, secret: string, toleranceInSeconds?: number): Promise<WebhookVerificationResult<TPayload>>;
|
|
10
|
+
static verifyAny<TPayload = unknown>(request: Request, secrets: MultiPlatformSecrets, toleranceInSeconds?: number): Promise<WebhookVerificationResult<TPayload>>;
|
|
11
11
|
private static resolveCanonicalEventId;
|
|
12
12
|
private static safeCompare;
|
|
13
13
|
private static pickString;
|
|
@@ -32,7 +32,6 @@ export * from './types';
|
|
|
32
32
|
export { getPlatformAlgorithmConfig, platformUsesAlgorithm, getPlatformsUsingAlgorithm, validateSignatureConfig, } from './platforms/algorithms';
|
|
33
33
|
export { createAlgorithmVerifier } from './verifiers/algorithms';
|
|
34
34
|
export { createCustomVerifier } from './verifiers/custom-algorithms';
|
|
35
|
-
export { normalizePayload, getPlatformNormalizationCategory, getPlatformsByCategory, } from './normalization/simple';
|
|
36
35
|
export * from './adapters';
|
|
37
36
|
export * from './alerts';
|
|
38
37
|
export default WebhookVerificationService;
|
package/dist/index.js
CHANGED
|
@@ -36,12 +36,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
36
36
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.
|
|
39
|
+
exports.createCustomVerifier = exports.createAlgorithmVerifier = exports.validateSignatureConfig = exports.getPlatformsUsingAlgorithm = exports.platformUsesAlgorithm = exports.getPlatformAlgorithmConfig = exports.WebhookVerificationService = void 0;
|
|
40
40
|
const crypto_1 = require("crypto");
|
|
41
41
|
const algorithms_1 = require("./verifiers/algorithms");
|
|
42
42
|
const custom_algorithms_1 = require("./verifiers/custom-algorithms");
|
|
43
43
|
const algorithms_2 = require("./platforms/algorithms");
|
|
44
|
-
const simple_1 = require("./normalization/simple");
|
|
45
44
|
const dispatch_1 = require("./notifications/dispatch");
|
|
46
45
|
class WebhookVerificationService {
|
|
47
46
|
static async verify(request, config) {
|
|
@@ -51,9 +50,6 @@ class WebhookVerificationService {
|
|
|
51
50
|
if (result.isValid) {
|
|
52
51
|
result.platform = config.platform;
|
|
53
52
|
result.eventId = this.resolveCanonicalEventId(config.platform, result.metadata, result.payload) ?? undefined;
|
|
54
|
-
if (config.normalize) {
|
|
55
|
-
result.payload = (0, simple_1.normalizePayload)(config.platform, result.payload, config.normalize);
|
|
56
|
-
}
|
|
57
53
|
}
|
|
58
54
|
return result;
|
|
59
55
|
}
|
|
@@ -70,12 +66,21 @@ class WebhookVerificationService {
|
|
|
70
66
|
if (!signatureConfig) {
|
|
71
67
|
throw new Error('Signature config is required for algorithm-based verification');
|
|
72
68
|
}
|
|
69
|
+
const effectiveSignatureConfig = {
|
|
70
|
+
...signatureConfig,
|
|
71
|
+
customConfig: {
|
|
72
|
+
...(signatureConfig.customConfig || {}),
|
|
73
|
+
...(config.platform === 'twilio' && config.twilioBaseUrl
|
|
74
|
+
? { twilioBaseUrl: config.twilioBaseUrl }
|
|
75
|
+
: {}),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
73
78
|
// Use custom verifiers for special cases (token-based, etc.)
|
|
74
|
-
if (
|
|
75
|
-
return (0, custom_algorithms_1.createCustomVerifier)(secret,
|
|
79
|
+
if (effectiveSignatureConfig.algorithm === 'custom') {
|
|
80
|
+
return (0, custom_algorithms_1.createCustomVerifier)(secret, effectiveSignatureConfig, toleranceInSeconds);
|
|
76
81
|
}
|
|
77
82
|
// Use algorithm-based verifiers for standard algorithms
|
|
78
|
-
return (0, algorithms_1.createAlgorithmVerifier)(secret,
|
|
83
|
+
return (0, algorithms_1.createAlgorithmVerifier)(secret, effectiveSignatureConfig, config.platform, toleranceInSeconds);
|
|
79
84
|
}
|
|
80
85
|
static getLegacyVerifier(config) {
|
|
81
86
|
// For legacy support, we'll use the algorithm-based approach
|
|
@@ -87,29 +92,28 @@ class WebhookVerificationService {
|
|
|
87
92
|
return this.createAlgorithmBasedVerifier(configWithSignature);
|
|
88
93
|
}
|
|
89
94
|
// New method to create verifier using platform algorithm config
|
|
90
|
-
static async verifyWithPlatformConfig(request, platform, secret, toleranceInSeconds = 300
|
|
95
|
+
static async verifyWithPlatformConfig(request, platform, secret, toleranceInSeconds = 300) {
|
|
91
96
|
const platformConfig = (0, algorithms_2.getPlatformAlgorithmConfig)(platform);
|
|
92
97
|
const config = {
|
|
93
98
|
platform,
|
|
94
99
|
secret,
|
|
95
100
|
toleranceInSeconds,
|
|
96
|
-
signatureConfig: platformConfig.signatureConfig
|
|
97
|
-
normalize,
|
|
101
|
+
signatureConfig: platformConfig.signatureConfig
|
|
98
102
|
};
|
|
99
103
|
return this.verify(request, config);
|
|
100
104
|
}
|
|
101
|
-
static async verifyAny(request, secrets, toleranceInSeconds = 300
|
|
105
|
+
static async verifyAny(request, secrets, toleranceInSeconds = 300) {
|
|
102
106
|
const requestClone = request.clone();
|
|
103
107
|
const detectedPlatform = this.detectPlatform(requestClone);
|
|
104
108
|
if (detectedPlatform !== 'unknown' && secrets[detectedPlatform]) {
|
|
105
|
-
return this.verifyWithPlatformConfig(requestClone, detectedPlatform, secrets[detectedPlatform], toleranceInSeconds
|
|
109
|
+
return this.verifyWithPlatformConfig(requestClone, detectedPlatform, secrets[detectedPlatform], toleranceInSeconds);
|
|
106
110
|
}
|
|
107
111
|
const failedAttempts = [];
|
|
108
112
|
const verificationResults = await Promise.all(Object.entries(secrets)
|
|
109
113
|
.filter(([, secret]) => Boolean(secret))
|
|
110
114
|
.map(async ([platform, secret]) => {
|
|
111
115
|
const normalizedPlatform = platform.toLowerCase();
|
|
112
|
-
const result = await this.verifyWithPlatformConfig(requestClone, normalizedPlatform, secret, toleranceInSeconds
|
|
116
|
+
const result = await this.verifyWithPlatformConfig(requestClone, normalizedPlatform, secret, toleranceInSeconds);
|
|
113
117
|
return {
|
|
114
118
|
platform: normalizedPlatform,
|
|
115
119
|
result,
|
|
@@ -189,6 +193,10 @@ class WebhookVerificationService {
|
|
|
189
193
|
case 'workos':
|
|
190
194
|
case 'sentry':
|
|
191
195
|
case 'vercel':
|
|
196
|
+
case 'linear':
|
|
197
|
+
case 'pagerduty':
|
|
198
|
+
case 'twilio':
|
|
199
|
+
case 'svix':
|
|
192
200
|
return this.pickString(payload?.id) || null;
|
|
193
201
|
case 'doppler':
|
|
194
202
|
return this.pickString(payload?.event?.id, metadata?.id) || null;
|
|
@@ -220,7 +228,13 @@ class WebhookVerificationService {
|
|
|
220
228
|
if (headers.has('x-hub-signature-256'))
|
|
221
229
|
return 'github';
|
|
222
230
|
if (headers.has('svix-signature'))
|
|
223
|
-
return 'clerk';
|
|
231
|
+
return headers.has('svix-id') ? 'svix' : 'clerk';
|
|
232
|
+
if (headers.has('linear-signature'))
|
|
233
|
+
return 'linear';
|
|
234
|
+
if (headers.has('x-pagerduty-signature'))
|
|
235
|
+
return 'pagerduty';
|
|
236
|
+
if (headers.has('x-twilio-signature'))
|
|
237
|
+
return 'twilio';
|
|
224
238
|
if (headers.has('workos-signature'))
|
|
225
239
|
return 'workos';
|
|
226
240
|
if (headers.has('webhook-signature')) {
|
|
@@ -365,10 +379,6 @@ var algorithms_4 = require("./verifiers/algorithms");
|
|
|
365
379
|
Object.defineProperty(exports, "createAlgorithmVerifier", { enumerable: true, get: function () { return algorithms_4.createAlgorithmVerifier; } });
|
|
366
380
|
var custom_algorithms_2 = require("./verifiers/custom-algorithms");
|
|
367
381
|
Object.defineProperty(exports, "createCustomVerifier", { enumerable: true, get: function () { return custom_algorithms_2.createCustomVerifier; } });
|
|
368
|
-
var simple_2 = require("./normalization/simple");
|
|
369
|
-
Object.defineProperty(exports, "normalizePayload", { enumerable: true, get: function () { return simple_2.normalizePayload; } });
|
|
370
|
-
Object.defineProperty(exports, "getPlatformNormalizationCategory", { enumerable: true, get: function () { return simple_2.getPlatformNormalizationCategory; } });
|
|
371
|
-
Object.defineProperty(exports, "getPlatformsByCategory", { enumerable: true, get: function () { return simple_2.getPlatformsByCategory; } });
|
|
372
382
|
__exportStar(require("./adapters"), exports);
|
|
373
383
|
__exportStar(require("./alerts"), exports);
|
|
374
384
|
exports.default = WebhookVerificationService;
|
|
@@ -51,6 +51,27 @@ exports.platformAlgorithmConfigs = {
|
|
|
51
51
|
},
|
|
52
52
|
description: "Clerk webhooks use HMAC-SHA256 with base64 encoding",
|
|
53
53
|
},
|
|
54
|
+
svix: {
|
|
55
|
+
platform: 'svix',
|
|
56
|
+
signatureConfig: {
|
|
57
|
+
algorithm: 'hmac-sha256',
|
|
58
|
+
headerName: 'svix-signature',
|
|
59
|
+
headerFormat: 'raw',
|
|
60
|
+
timestampHeader: 'svix-timestamp',
|
|
61
|
+
timestampFormat: 'unix',
|
|
62
|
+
payloadFormat: 'custom',
|
|
63
|
+
customConfig: {
|
|
64
|
+
signatureFormat: 'v1={signature}',
|
|
65
|
+
payloadFormat: '{id}.{timestamp}.{body}',
|
|
66
|
+
encoding: 'base64',
|
|
67
|
+
secretEncoding: 'base64',
|
|
68
|
+
idHeader: 'svix-id',
|
|
69
|
+
idHeaderAliases: ['webhook-id'],
|
|
70
|
+
timestampHeaderAliases: ['webhook-timestamp'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
description: 'Svix webhooks use HMAC-SHA256 with Standard Webhooks format',
|
|
74
|
+
},
|
|
54
75
|
dodopayments: {
|
|
55
76
|
platform: "dodopayments",
|
|
56
77
|
signatureConfig: {
|
|
@@ -287,6 +308,50 @@ exports.platformAlgorithmConfigs = {
|
|
|
287
308
|
},
|
|
288
309
|
description: "Sanity webhooks use Stripe-compatible HMAC-SHA256 with base64 encoded signature and plain UTF-8 secret",
|
|
289
310
|
},
|
|
311
|
+
linear: {
|
|
312
|
+
platform: 'linear',
|
|
313
|
+
signatureConfig: {
|
|
314
|
+
algorithm: 'hmac-sha256',
|
|
315
|
+
headerName: 'linear-signature',
|
|
316
|
+
headerFormat: 'raw',
|
|
317
|
+
payloadFormat: 'raw',
|
|
318
|
+
customConfig: {
|
|
319
|
+
replayToleranceMs: 60000,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
description: 'Linear webhooks use HMAC-SHA256 on the raw body with a 60s timestamp replay window',
|
|
323
|
+
},
|
|
324
|
+
pagerduty: {
|
|
325
|
+
platform: 'pagerduty',
|
|
326
|
+
signatureConfig: {
|
|
327
|
+
algorithm: 'hmac-sha256',
|
|
328
|
+
headerName: 'x-pagerduty-signature',
|
|
329
|
+
headerFormat: 'raw',
|
|
330
|
+
payloadFormat: 'raw',
|
|
331
|
+
prefix: 'v1=',
|
|
332
|
+
customConfig: {
|
|
333
|
+
signatureFormat: 'v1={signature}',
|
|
334
|
+
comparePrefixed: true,
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
description: 'PagerDuty webhooks use HMAC-SHA256 with v1=<hex> signatures',
|
|
338
|
+
},
|
|
339
|
+
twilio: {
|
|
340
|
+
platform: 'twilio',
|
|
341
|
+
signatureConfig: {
|
|
342
|
+
algorithm: 'hmac-sha1',
|
|
343
|
+
headerName: 'x-twilio-signature',
|
|
344
|
+
headerFormat: 'raw',
|
|
345
|
+
payloadFormat: 'custom',
|
|
346
|
+
customConfig: {
|
|
347
|
+
payloadFormat: '{url}',
|
|
348
|
+
encoding: 'base64',
|
|
349
|
+
secretEncoding: 'utf8',
|
|
350
|
+
validateBodySHA256: true,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
description: 'Twilio webhooks use HMAC-SHA1 with base64 signatures (URL canonicalization required)',
|
|
354
|
+
},
|
|
290
355
|
custom: {
|
|
291
356
|
platform: "custom",
|
|
292
357
|
signatureConfig: {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export type WebhookPlatform = 'custom' | 'clerk' | 'github' | 'stripe' | 'shopify' | 'vercel' | 'polar' | 'dodopayments' | 'gitlab' | 'paddle' | 'razorpay' | 'lemonsqueezy' | 'workos' | 'woocommerce' | 'replicateai' | 'falai' | 'sentry' | 'grafana' | 'doppler' | 'sanity' | 'unknown';
|
|
1
|
+
export type WebhookPlatform = 'custom' | 'clerk' | 'svix' | 'github' | 'stripe' | 'shopify' | 'vercel' | 'polar' | 'dodopayments' | 'gitlab' | 'paddle' | 'razorpay' | 'lemonsqueezy' | 'workos' | 'woocommerce' | 'replicateai' | 'falai' | 'sentry' | 'grafana' | 'doppler' | 'sanity' | 'linear' | 'pagerduty' | 'twilio' | 'unknown';
|
|
2
2
|
export declare enum WebhookPlatformKeys {
|
|
3
3
|
GitHub = "github",
|
|
4
4
|
Stripe = "stripe",
|
|
5
5
|
Clerk = "clerk",
|
|
6
|
+
Svix = "svix",
|
|
6
7
|
DodoPayments = "dodopayments",
|
|
7
8
|
Shopify = "shopify",
|
|
8
9
|
Vercel = "vercel",
|
|
@@ -19,6 +20,9 @@ export declare enum WebhookPlatformKeys {
|
|
|
19
20
|
Grafana = "grafana",
|
|
20
21
|
Doppler = "doppler",
|
|
21
22
|
Sanity = "sanity",
|
|
23
|
+
Linear = "linear",
|
|
24
|
+
PagerDuty = "pagerduty",
|
|
25
|
+
Twilio = "twilio",
|
|
22
26
|
Custom = "custom",
|
|
23
27
|
Unknown = "unknown"
|
|
24
28
|
}
|
|
@@ -35,69 +39,6 @@ export interface SignatureConfig {
|
|
|
35
39
|
customConfig?: Record<string, any>;
|
|
36
40
|
}
|
|
37
41
|
export type WebhookErrorCode = 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'TIMESTAMP_EXPIRED' | 'MISSING_TOKEN' | 'INVALID_TOKEN' | 'PLATFORM_NOT_SUPPORTED' | 'NORMALIZATION_ERROR' | 'VERIFICATION_ERROR';
|
|
38
|
-
export type NormalizationCategory = 'payment' | 'auth' | 'ecommerce' | 'infrastructure';
|
|
39
|
-
export interface BaseNormalizedWebhook {
|
|
40
|
-
category: NormalizationCategory;
|
|
41
|
-
event: string;
|
|
42
|
-
_platform: WebhookPlatform | string;
|
|
43
|
-
_raw: unknown;
|
|
44
|
-
occurred_at?: string;
|
|
45
|
-
}
|
|
46
|
-
export type PaymentWebhookEvent = 'payment.succeeded' | 'payment.failed' | 'payment.refunded' | 'subscription.created' | 'subscription.cancelled' | 'payment.unknown';
|
|
47
|
-
export interface PaymentWebhookNormalized extends BaseNormalizedWebhook {
|
|
48
|
-
category: 'payment';
|
|
49
|
-
event: PaymentWebhookEvent;
|
|
50
|
-
amount?: number;
|
|
51
|
-
currency?: string;
|
|
52
|
-
customer_id?: string;
|
|
53
|
-
transaction_id?: string;
|
|
54
|
-
subscription_id?: string;
|
|
55
|
-
refund_amount?: number;
|
|
56
|
-
failure_reason?: string;
|
|
57
|
-
metadata?: Record<string, string>;
|
|
58
|
-
}
|
|
59
|
-
export type AuthWebhookEvent = 'user.created' | 'user.updated' | 'user.deleted' | 'session.started' | 'session.ended' | 'auth.unknown';
|
|
60
|
-
export interface AuthWebhookNormalized extends BaseNormalizedWebhook {
|
|
61
|
-
category: 'auth';
|
|
62
|
-
event: AuthWebhookEvent;
|
|
63
|
-
user_id?: string;
|
|
64
|
-
email?: string;
|
|
65
|
-
phone?: string;
|
|
66
|
-
metadata?: Record<string, string>;
|
|
67
|
-
}
|
|
68
|
-
export interface EcommerceWebhookNormalized extends BaseNormalizedWebhook {
|
|
69
|
-
category: 'ecommerce';
|
|
70
|
-
event: string;
|
|
71
|
-
order_id?: string;
|
|
72
|
-
customer_id?: string;
|
|
73
|
-
amount?: number;
|
|
74
|
-
currency?: string;
|
|
75
|
-
metadata?: Record<string, string>;
|
|
76
|
-
}
|
|
77
|
-
export interface InfrastructureWebhookNormalized extends BaseNormalizedWebhook {
|
|
78
|
-
category: 'infrastructure';
|
|
79
|
-
event: string;
|
|
80
|
-
project_id?: string;
|
|
81
|
-
deployment_id?: string;
|
|
82
|
-
status?: 'queued' | 'building' | 'ready' | 'error' | 'unknown';
|
|
83
|
-
metadata?: Record<string, string>;
|
|
84
|
-
}
|
|
85
|
-
export interface UnknownNormalizedWebhook extends BaseNormalizedWebhook {
|
|
86
|
-
event: string;
|
|
87
|
-
warning?: string;
|
|
88
|
-
}
|
|
89
|
-
export type NormalizedPayloadByCategory = {
|
|
90
|
-
payment: PaymentWebhookNormalized;
|
|
91
|
-
auth: AuthWebhookNormalized;
|
|
92
|
-
ecommerce: EcommerceWebhookNormalized;
|
|
93
|
-
infrastructure: InfrastructureWebhookNormalized;
|
|
94
|
-
};
|
|
95
|
-
export type AnyNormalizedWebhook = PaymentWebhookNormalized | AuthWebhookNormalized | EcommerceWebhookNormalized | InfrastructureWebhookNormalized | UnknownNormalizedWebhook;
|
|
96
|
-
export interface NormalizeOptions {
|
|
97
|
-
enabled?: boolean;
|
|
98
|
-
category?: NormalizationCategory;
|
|
99
|
-
includeRaw?: boolean;
|
|
100
|
-
}
|
|
101
42
|
export interface WebhookVerificationResult<TPayload = unknown> {
|
|
102
43
|
isValid: boolean;
|
|
103
44
|
error?: string;
|
|
@@ -116,7 +57,7 @@ export interface WebhookConfig {
|
|
|
116
57
|
secret: string;
|
|
117
58
|
toleranceInSeconds?: number;
|
|
118
59
|
signatureConfig?: SignatureConfig;
|
|
119
|
-
|
|
60
|
+
twilioBaseUrl?: string;
|
|
120
61
|
}
|
|
121
62
|
export interface MultiPlatformSecrets {
|
|
122
63
|
[platform: string]: string | undefined;
|
package/dist/types.js
CHANGED
|
@@ -6,6 +6,7 @@ var WebhookPlatformKeys;
|
|
|
6
6
|
WebhookPlatformKeys["GitHub"] = "github";
|
|
7
7
|
WebhookPlatformKeys["Stripe"] = "stripe";
|
|
8
8
|
WebhookPlatformKeys["Clerk"] = "clerk";
|
|
9
|
+
WebhookPlatformKeys["Svix"] = "svix";
|
|
9
10
|
WebhookPlatformKeys["DodoPayments"] = "dodopayments";
|
|
10
11
|
WebhookPlatformKeys["Shopify"] = "shopify";
|
|
11
12
|
WebhookPlatformKeys["Vercel"] = "vercel";
|
|
@@ -22,6 +23,9 @@ var WebhookPlatformKeys;
|
|
|
22
23
|
WebhookPlatformKeys["Grafana"] = "grafana";
|
|
23
24
|
WebhookPlatformKeys["Doppler"] = "doppler";
|
|
24
25
|
WebhookPlatformKeys["Sanity"] = "sanity";
|
|
26
|
+
WebhookPlatformKeys["Linear"] = "linear";
|
|
27
|
+
WebhookPlatformKeys["PagerDuty"] = "pagerduty";
|
|
28
|
+
WebhookPlatformKeys["Twilio"] = "twilio";
|
|
25
29
|
WebhookPlatformKeys["Custom"] = "custom";
|
|
26
30
|
WebhookPlatformKeys["Unknown"] = "unknown";
|
|
27
31
|
})(WebhookPlatformKeys || (exports.WebhookPlatformKeys = WebhookPlatformKeys = {}));
|
|
@@ -5,11 +5,17 @@ export declare abstract class AlgorithmBasedVerifier extends WebhookVerifier {
|
|
|
5
5
|
protected platform: WebhookPlatform;
|
|
6
6
|
constructor(secret: string, config: SignatureConfig, platform: WebhookPlatform, toleranceInSeconds?: number);
|
|
7
7
|
abstract verify(request: Request): Promise<WebhookVerificationResult>;
|
|
8
|
+
protected getMissingSignatureMessage(): string;
|
|
9
|
+
protected getMissingTimestampMessage(): string;
|
|
10
|
+
protected getTimestampExpiredMessage(): string;
|
|
11
|
+
protected getInvalidSignatureMessage(): string;
|
|
12
|
+
protected getVerificationErrorMessage(error: Error): string;
|
|
8
13
|
protected parseDelimitedHeader(headerValue: string): Record<string, string>;
|
|
9
14
|
protected extractSignatures(request: Request): string[];
|
|
10
15
|
protected extractTimestamp(request: Request): number | null;
|
|
11
16
|
protected extractTimestampFromSignature(request: Request): number | null;
|
|
12
17
|
protected requiresTimestamp(): boolean;
|
|
18
|
+
protected resolveTwilioSignatureUrl(request: Request): string;
|
|
13
19
|
protected formatPayload(rawBody: string, request: Request): string;
|
|
14
20
|
protected formatCustomPayload(rawBody: string, request: Request): string;
|
|
15
21
|
protected verifyHMAC(payload: string, signature: string, algorithm?: string): boolean;
|
|
@@ -18,6 +24,8 @@ export declare abstract class AlgorithmBasedVerifier extends WebhookVerifier {
|
|
|
18
24
|
protected extractMetadata(request: Request): Record<string, any>;
|
|
19
25
|
}
|
|
20
26
|
export declare class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
27
|
+
private validateLinearReplayWindow;
|
|
28
|
+
private validateTwilioBodyHash;
|
|
21
29
|
private resolveSentryPayloadCandidates;
|
|
22
30
|
verify(request: Request): Promise<WebhookVerificationResult>;
|
|
23
31
|
}
|