@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.
- package/README.md +204 -71
- package/dist/adapters/cloudflare.d.ts +11 -0
- package/dist/adapters/cloudflare.js +25 -0
- package/dist/adapters/express.d.ts +18 -0
- package/dist/adapters/express.js +23 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/nextjs.d.ts +10 -0
- package/dist/adapters/nextjs.js +20 -0
- package/dist/adapters/shared.d.ts +13 -0
- package/dist/adapters/shared.js +67 -0
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.js +5 -0
- package/dist/express.d.ts +2 -0
- package/dist/express.js +5 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.js +66 -14
- package/dist/nextjs.d.ts +2 -0
- package/dist/nextjs.js +5 -0
- package/dist/normalization/index.d.ts +20 -0
- package/dist/normalization/index.js +78 -0
- package/dist/normalization/providers/payment/paypal.d.ts +2 -0
- package/dist/normalization/providers/payment/paypal.js +12 -0
- package/dist/normalization/providers/payment/razorpay.d.ts +2 -0
- package/dist/normalization/providers/payment/razorpay.js +13 -0
- package/dist/normalization/providers/payment/stripe.d.ts +2 -0
- package/dist/normalization/providers/payment/stripe.js +13 -0
- package/dist/normalization/providers/registry.d.ts +5 -0
- package/dist/normalization/providers/registry.js +23 -0
- package/dist/normalization/simple.d.ts +4 -0
- package/dist/normalization/simple.js +138 -0
- package/dist/normalization/storage/interface.d.ts +13 -0
- package/dist/normalization/storage/interface.js +2 -0
- package/dist/normalization/storage/memory.d.ts +12 -0
- package/dist/normalization/storage/memory.js +39 -0
- package/dist/normalization/templates/base/auth.d.ts +2 -0
- package/dist/normalization/templates/base/auth.js +22 -0
- package/dist/normalization/templates/base/ecommerce.d.ts +2 -0
- package/dist/normalization/templates/base/ecommerce.js +25 -0
- package/dist/normalization/templates/base/payment.d.ts +2 -0
- package/dist/normalization/templates/base/payment.js +25 -0
- package/dist/normalization/templates/registry.d.ts +6 -0
- package/dist/normalization/templates/registry.js +22 -0
- package/dist/normalization/transformer/engine.d.ts +11 -0
- package/dist/normalization/transformer/engine.js +86 -0
- package/dist/normalization/transformer/validator.d.ts +12 -0
- package/dist/normalization/transformer/validator.js +56 -0
- package/dist/normalization/types.d.ts +79 -0
- package/dist/normalization/types.js +2 -0
- package/dist/platforms/algorithms.d.ts +1 -1
- package/dist/platforms/algorithms.js +103 -89
- package/dist/test.js +98 -2
- package/dist/types.d.ts +73 -3
- package/dist/types.js +1 -0
- package/dist/verifiers/algorithms.d.ts +2 -2
- package/dist/verifiers/algorithms.js +66 -62
- package/dist/verifiers/base.d.ts +1 -1
- package/dist/verifiers/custom-algorithms.d.ts +2 -2
- package/dist/verifiers/custom-algorithms.js +11 -8
- package/package.json +22 -2
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.paymentBaseTemplate = void 0;
|
|
4
|
+
exports.paymentBaseTemplate = {
|
|
5
|
+
id: 'payment_v1',
|
|
6
|
+
category: 'payment',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
fields: [
|
|
9
|
+
{
|
|
10
|
+
id: 'event_type', name: 'event_type', type: 'string', required: true, description: 'Type of payment event',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'amount', name: 'amount', type: 'number', required: true, description: 'Amount in the smallest currency unit',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'currency', name: 'currency', type: 'string', required: true, description: 'Three-letter currency code',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'transaction_id', name: 'transaction_id', type: 'string', required: true, description: 'Unique transaction identifier',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'customer_id', name: 'customer_id', type: 'string', required: false, description: 'Customer identifier',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.templateRegistry = void 0;
|
|
4
|
+
const payment_1 = require("./base/payment");
|
|
5
|
+
const auth_1 = require("./base/auth");
|
|
6
|
+
const ecommerce_1 = require("./base/ecommerce");
|
|
7
|
+
const templates = {
|
|
8
|
+
[payment_1.paymentBaseTemplate.id]: payment_1.paymentBaseTemplate,
|
|
9
|
+
[auth_1.authBaseTemplate.id]: auth_1.authBaseTemplate,
|
|
10
|
+
[ecommerce_1.ecommerceBaseTemplate.id]: ecommerce_1.ecommerceBaseTemplate,
|
|
11
|
+
};
|
|
12
|
+
exports.templateRegistry = {
|
|
13
|
+
getById(id) {
|
|
14
|
+
return templates[id];
|
|
15
|
+
},
|
|
16
|
+
listByCategory(category) {
|
|
17
|
+
return Object.values(templates).filter((t) => t.category === category);
|
|
18
|
+
},
|
|
19
|
+
listAll() {
|
|
20
|
+
return Object.values(templates);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NormalizedResult, TransformParams } from '../types';
|
|
2
|
+
import { StorageAdapter } from '../storage/interface';
|
|
3
|
+
import { SchemaValidator } from './validator';
|
|
4
|
+
export declare class NormalizationEngine {
|
|
5
|
+
private readonly storage;
|
|
6
|
+
private readonly validator;
|
|
7
|
+
constructor(storage: StorageAdapter, validator?: SchemaValidator);
|
|
8
|
+
transform(params: TransformParams): Promise<NormalizedResult>;
|
|
9
|
+
private extractValue;
|
|
10
|
+
private applyTransform;
|
|
11
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NormalizationEngine = void 0;
|
|
4
|
+
const registry_1 = require("../templates/registry");
|
|
5
|
+
const validator_1 = require("./validator");
|
|
6
|
+
class NormalizationEngine {
|
|
7
|
+
constructor(storage, validator = new validator_1.SchemaValidator()) {
|
|
8
|
+
this.storage = storage;
|
|
9
|
+
this.validator = validator;
|
|
10
|
+
}
|
|
11
|
+
async transform(params) {
|
|
12
|
+
const { rawPayload, provider, schemaId } = params;
|
|
13
|
+
const schema = await this.storage.getSchema(schemaId);
|
|
14
|
+
if (!schema)
|
|
15
|
+
throw new Error(`Schema not found: ${schemaId}`);
|
|
16
|
+
const baseTemplate = await this.storage.getBaseTemplate(schema.baseTemplateId) || registry_1.templateRegistry.getById(schema.baseTemplateId);
|
|
17
|
+
if (!baseTemplate)
|
|
18
|
+
throw new Error(`Base template not found: ${schema.baseTemplateId}`);
|
|
19
|
+
const validation = this.validator.validateSchema(schema, baseTemplate);
|
|
20
|
+
if (!validation.valid) {
|
|
21
|
+
throw new Error(`Invalid schema: ${validation.errors.join('; ')}`);
|
|
22
|
+
}
|
|
23
|
+
const providerMapping = schema.providerMappings.find((m) => m.provider === provider);
|
|
24
|
+
if (!providerMapping)
|
|
25
|
+
throw new Error(`No mapping found for provider: ${provider}`);
|
|
26
|
+
const normalized = {};
|
|
27
|
+
for (const field of schema.fields) {
|
|
28
|
+
if (!field.enabled)
|
|
29
|
+
continue;
|
|
30
|
+
const mapping = providerMapping.fieldMappings.find((m) => m.schemaFieldId === field.id);
|
|
31
|
+
if (mapping) {
|
|
32
|
+
const value = this.extractValue(rawPayload, mapping.providerPath);
|
|
33
|
+
const finalValue = this.applyTransform(value, mapping.transform);
|
|
34
|
+
normalized[field.name] = finalValue ?? field.defaultValue;
|
|
35
|
+
}
|
|
36
|
+
else if (field.required) {
|
|
37
|
+
if (field.defaultValue !== undefined) {
|
|
38
|
+
normalized[field.name] = field.defaultValue;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw new Error(`Required field ${field.name} has no mapping`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const outValidation = this.validator.validateOutput(normalized, schema, baseTemplate);
|
|
46
|
+
if (!outValidation.valid) {
|
|
47
|
+
throw new Error(`Normalized output invalid: ${outValidation.errors.join('; ')}`);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
normalized,
|
|
51
|
+
meta: {
|
|
52
|
+
provider,
|
|
53
|
+
schemaId,
|
|
54
|
+
schemaVersion: schema.baseTemplateId,
|
|
55
|
+
transformedAt: new Date(),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
extractValue(obj, path) {
|
|
60
|
+
if (!path)
|
|
61
|
+
return undefined;
|
|
62
|
+
return path.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), obj);
|
|
63
|
+
}
|
|
64
|
+
applyTransform(value, transform) {
|
|
65
|
+
if (transform == null)
|
|
66
|
+
return value;
|
|
67
|
+
if (value == null)
|
|
68
|
+
return value;
|
|
69
|
+
if (transform === 'toUpperCase')
|
|
70
|
+
return String(value).toUpperCase();
|
|
71
|
+
if (transform === 'toLowerCase')
|
|
72
|
+
return String(value).toLowerCase();
|
|
73
|
+
if (transform === 'toNumber')
|
|
74
|
+
return typeof value === 'number' ? value : Number(value);
|
|
75
|
+
if (transform.startsWith('divide:')) {
|
|
76
|
+
const denominator = Number(transform.split(':')[1]);
|
|
77
|
+
return Number(value) / denominator;
|
|
78
|
+
}
|
|
79
|
+
if (transform.startsWith('multiply:')) {
|
|
80
|
+
const factor = Number(transform.split(':')[1]);
|
|
81
|
+
return Number(value) * factor;
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.NormalizationEngine = NormalizationEngine;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseTemplate, UserSchema } from '../types';
|
|
2
|
+
export declare class SchemaValidator {
|
|
3
|
+
validateSchema(userSchema: UserSchema, baseTemplate: BaseTemplate): {
|
|
4
|
+
valid: boolean;
|
|
5
|
+
errors: string[];
|
|
6
|
+
};
|
|
7
|
+
validateOutput(output: Record<string, unknown>, userSchema: UserSchema, baseTemplate: BaseTemplate): {
|
|
8
|
+
valid: boolean;
|
|
9
|
+
errors: string[];
|
|
10
|
+
};
|
|
11
|
+
private matchesType;
|
|
12
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaValidator = void 0;
|
|
4
|
+
class SchemaValidator {
|
|
5
|
+
validateSchema(userSchema, baseTemplate) {
|
|
6
|
+
const errors = [];
|
|
7
|
+
// Ensure required base fields exist and are enabled or have defaults
|
|
8
|
+
for (const baseField of baseTemplate.fields) {
|
|
9
|
+
if (!baseField.required)
|
|
10
|
+
continue;
|
|
11
|
+
const userField = userSchema.fields.find((f) => f.id === baseField.id);
|
|
12
|
+
if (!userField) {
|
|
13
|
+
errors.push(`Missing required field in schema: ${baseField.id}`);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (!userField.enabled && baseField.defaultValue === undefined) {
|
|
17
|
+
errors.push(`Required field disabled without default: ${baseField.id}`);
|
|
18
|
+
}
|
|
19
|
+
if (userField.type !== baseField.type) {
|
|
20
|
+
errors.push(`Type mismatch for field ${baseField.id}: expected ${baseField.type}, got ${userField.type}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { valid: errors.length === 0, errors };
|
|
24
|
+
}
|
|
25
|
+
validateOutput(output, userSchema, baseTemplate) {
|
|
26
|
+
const errors = [];
|
|
27
|
+
for (const field of userSchema.fields) {
|
|
28
|
+
if (!field.enabled)
|
|
29
|
+
continue;
|
|
30
|
+
const value = output[field.name];
|
|
31
|
+
if (value === undefined) {
|
|
32
|
+
if (field.required)
|
|
33
|
+
errors.push(`Missing required field in output: ${field.name}`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (!this.matchesType(value, field.type)) {
|
|
37
|
+
errors.push(`Type mismatch for output field ${field.name}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { valid: errors.length === 0, errors };
|
|
41
|
+
}
|
|
42
|
+
matchesType(value, type) {
|
|
43
|
+
if (type === 'number')
|
|
44
|
+
return typeof value === 'number' && !Number.isNaN(value);
|
|
45
|
+
if (type === 'string')
|
|
46
|
+
return typeof value === 'string';
|
|
47
|
+
if (type === 'boolean')
|
|
48
|
+
return typeof value === 'boolean';
|
|
49
|
+
if (type === 'object')
|
|
50
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
51
|
+
if (type === 'array')
|
|
52
|
+
return Array.isArray(value);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.SchemaValidator = SchemaValidator;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type TemplateCategory = 'payment' | 'auth' | 'ecommerce';
|
|
2
|
+
export interface TemplateField {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
6
|
+
required: boolean;
|
|
7
|
+
description?: string;
|
|
8
|
+
defaultValue?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface BaseTemplate {
|
|
11
|
+
id: string;
|
|
12
|
+
category: TemplateCategory;
|
|
13
|
+
version: string;
|
|
14
|
+
fields: TemplateField[];
|
|
15
|
+
}
|
|
16
|
+
export interface UserSchemaField {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
type: TemplateField['type'];
|
|
20
|
+
required: boolean;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
defaultValue?: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface FieldMapping {
|
|
25
|
+
schemaFieldId: string;
|
|
26
|
+
providerPath: string;
|
|
27
|
+
transform?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ProviderMapping {
|
|
30
|
+
provider: string;
|
|
31
|
+
fieldMappings: FieldMapping[];
|
|
32
|
+
}
|
|
33
|
+
export interface UserSchema {
|
|
34
|
+
id: string;
|
|
35
|
+
userId: string;
|
|
36
|
+
baseTemplateId: string;
|
|
37
|
+
category: TemplateCategory;
|
|
38
|
+
fields: UserSchemaField[];
|
|
39
|
+
providerMappings: ProviderMapping[];
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
updatedAt: Date;
|
|
42
|
+
}
|
|
43
|
+
export interface NormalizedPayloadMeta {
|
|
44
|
+
provider: string;
|
|
45
|
+
schemaId: string;
|
|
46
|
+
schemaVersion: string;
|
|
47
|
+
transformedAt: Date;
|
|
48
|
+
}
|
|
49
|
+
export interface NormalizedResult {
|
|
50
|
+
normalized: Record<string, unknown>;
|
|
51
|
+
meta: NormalizedPayloadMeta;
|
|
52
|
+
}
|
|
53
|
+
export interface CreateSchemaInput {
|
|
54
|
+
userId: string;
|
|
55
|
+
baseTemplateId: string;
|
|
56
|
+
category: TemplateCategory;
|
|
57
|
+
fields: UserSchemaField[];
|
|
58
|
+
providerMappings: ProviderMapping[];
|
|
59
|
+
}
|
|
60
|
+
export interface UpdateSchemaInput {
|
|
61
|
+
fields?: UserSchemaField[];
|
|
62
|
+
providerMappings?: ProviderMapping[];
|
|
63
|
+
}
|
|
64
|
+
export interface ProviderInfoField {
|
|
65
|
+
path: string;
|
|
66
|
+
type?: TemplateField['type'];
|
|
67
|
+
description?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ProviderInfo {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
category: TemplateCategory;
|
|
73
|
+
samplePaths?: ProviderInfoField[];
|
|
74
|
+
}
|
|
75
|
+
export interface TransformParams {
|
|
76
|
+
rawPayload: unknown;
|
|
77
|
+
provider: string;
|
|
78
|
+
schemaId: string;
|
|
79
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from
|
|
1
|
+
import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from '../types';
|
|
2
2
|
export declare const platformAlgorithmConfigs: Record<WebhookPlatform, PlatformAlgorithmConfig>;
|
|
3
3
|
export declare function getPlatformAlgorithmConfig(platform: WebhookPlatform): PlatformAlgorithmConfig;
|
|
4
4
|
export declare function platformUsesAlgorithm(platform: WebhookPlatform, algorithm: string): boolean;
|
|
@@ -7,139 +7,153 @@ exports.getPlatformsUsingAlgorithm = getPlatformsUsingAlgorithm;
|
|
|
7
7
|
exports.validateSignatureConfig = validateSignatureConfig;
|
|
8
8
|
exports.platformAlgorithmConfigs = {
|
|
9
9
|
github: {
|
|
10
|
-
platform:
|
|
10
|
+
platform: 'github',
|
|
11
11
|
signatureConfig: {
|
|
12
|
-
algorithm:
|
|
13
|
-
headerName:
|
|
14
|
-
headerFormat:
|
|
15
|
-
prefix:
|
|
12
|
+
algorithm: 'hmac-sha256',
|
|
13
|
+
headerName: 'x-hub-signature-256',
|
|
14
|
+
headerFormat: 'prefixed',
|
|
15
|
+
prefix: 'sha256=',
|
|
16
16
|
timestampHeader: undefined,
|
|
17
|
-
payloadFormat:
|
|
17
|
+
payloadFormat: 'raw',
|
|
18
18
|
},
|
|
19
|
-
description:
|
|
19
|
+
description: 'GitHub webhooks use HMAC-SHA256 with sha256= prefix',
|
|
20
20
|
},
|
|
21
21
|
stripe: {
|
|
22
|
-
platform:
|
|
22
|
+
platform: 'stripe',
|
|
23
23
|
signatureConfig: {
|
|
24
|
-
algorithm:
|
|
25
|
-
headerName:
|
|
26
|
-
headerFormat:
|
|
24
|
+
algorithm: 'hmac-sha256',
|
|
25
|
+
headerName: 'stripe-signature',
|
|
26
|
+
headerFormat: 'comma-separated',
|
|
27
27
|
timestampHeader: undefined,
|
|
28
|
-
payloadFormat:
|
|
28
|
+
payloadFormat: 'timestamped',
|
|
29
29
|
customConfig: {
|
|
30
|
-
signatureFormat:
|
|
30
|
+
signatureFormat: 't={timestamp},v1={signature}',
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
|
-
description:
|
|
33
|
+
description: 'Stripe webhooks use HMAC-SHA256 with comma-separated format',
|
|
34
34
|
},
|
|
35
35
|
clerk: {
|
|
36
|
-
platform:
|
|
36
|
+
platform: 'clerk',
|
|
37
37
|
signatureConfig: {
|
|
38
|
-
algorithm:
|
|
39
|
-
headerName:
|
|
40
|
-
headerFormat:
|
|
41
|
-
timestampHeader:
|
|
42
|
-
timestampFormat:
|
|
43
|
-
payloadFormat:
|
|
38
|
+
algorithm: 'hmac-sha256',
|
|
39
|
+
headerName: 'svix-signature',
|
|
40
|
+
headerFormat: 'raw',
|
|
41
|
+
timestampHeader: 'svix-timestamp',
|
|
42
|
+
timestampFormat: 'unix',
|
|
43
|
+
payloadFormat: 'custom',
|
|
44
44
|
customConfig: {
|
|
45
|
-
signatureFormat:
|
|
46
|
-
payloadFormat:
|
|
47
|
-
encoding:
|
|
48
|
-
idHeader:
|
|
45
|
+
signatureFormat: 'v1={signature}',
|
|
46
|
+
payloadFormat: '{id}.{timestamp}.{body}',
|
|
47
|
+
encoding: 'base64',
|
|
48
|
+
idHeader: 'svix-id',
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
|
-
description:
|
|
51
|
+
description: 'Clerk webhooks use HMAC-SHA256 with base64 encoding',
|
|
52
52
|
},
|
|
53
53
|
dodopayments: {
|
|
54
|
-
platform:
|
|
54
|
+
platform: 'dodopayments',
|
|
55
55
|
signatureConfig: {
|
|
56
|
-
algorithm:
|
|
57
|
-
headerName:
|
|
58
|
-
headerFormat:
|
|
59
|
-
timestampHeader:
|
|
60
|
-
timestampFormat:
|
|
61
|
-
payloadFormat:
|
|
56
|
+
algorithm: 'hmac-sha256',
|
|
57
|
+
headerName: 'webhook-signature',
|
|
58
|
+
headerFormat: 'raw',
|
|
59
|
+
timestampHeader: 'webhook-timestamp',
|
|
60
|
+
timestampFormat: 'unix',
|
|
61
|
+
payloadFormat: 'custom',
|
|
62
62
|
customConfig: {
|
|
63
|
-
signatureFormat:
|
|
64
|
-
payloadFormat:
|
|
65
|
-
encoding:
|
|
66
|
-
idHeader:
|
|
63
|
+
signatureFormat: 'v1={signature}',
|
|
64
|
+
payloadFormat: '{id}.{timestamp}.{body}',
|
|
65
|
+
encoding: 'base64',
|
|
66
|
+
idHeader: 'webhook-id',
|
|
67
67
|
},
|
|
68
68
|
},
|
|
69
|
-
description:
|
|
69
|
+
description: 'Dodo Payments webhooks use HMAC-SHA256 with svix-style format (Standard Webhooks)',
|
|
70
70
|
},
|
|
71
71
|
shopify: {
|
|
72
|
-
platform:
|
|
72
|
+
platform: 'shopify',
|
|
73
73
|
signatureConfig: {
|
|
74
|
-
algorithm:
|
|
75
|
-
headerName:
|
|
76
|
-
headerFormat:
|
|
77
|
-
timestampHeader:
|
|
78
|
-
payloadFormat:
|
|
74
|
+
algorithm: 'hmac-sha256',
|
|
75
|
+
headerName: 'x-shopify-hmac-sha256',
|
|
76
|
+
headerFormat: 'raw',
|
|
77
|
+
timestampHeader: 'x-shopify-shop-domain',
|
|
78
|
+
payloadFormat: 'raw',
|
|
79
79
|
},
|
|
80
|
-
description:
|
|
80
|
+
description: 'Shopify webhooks use HMAC-SHA256',
|
|
81
81
|
},
|
|
82
82
|
vercel: {
|
|
83
|
-
platform:
|
|
83
|
+
platform: 'vercel',
|
|
84
84
|
signatureConfig: {
|
|
85
|
-
algorithm:
|
|
86
|
-
headerName:
|
|
87
|
-
headerFormat:
|
|
88
|
-
timestampHeader:
|
|
89
|
-
timestampFormat:
|
|
90
|
-
payloadFormat:
|
|
85
|
+
algorithm: 'hmac-sha256',
|
|
86
|
+
headerName: 'x-vercel-signature',
|
|
87
|
+
headerFormat: 'raw',
|
|
88
|
+
timestampHeader: 'x-vercel-timestamp',
|
|
89
|
+
timestampFormat: 'unix',
|
|
90
|
+
payloadFormat: 'raw',
|
|
91
91
|
},
|
|
92
|
-
description:
|
|
92
|
+
description: 'Vercel webhooks use HMAC-SHA256',
|
|
93
93
|
},
|
|
94
94
|
polar: {
|
|
95
|
-
platform:
|
|
95
|
+
platform: 'polar',
|
|
96
96
|
signatureConfig: {
|
|
97
|
-
algorithm:
|
|
98
|
-
headerName:
|
|
99
|
-
headerFormat:
|
|
100
|
-
timestampHeader:
|
|
101
|
-
timestampFormat:
|
|
102
|
-
payloadFormat:
|
|
97
|
+
algorithm: 'hmac-sha256',
|
|
98
|
+
headerName: 'x-polar-signature',
|
|
99
|
+
headerFormat: 'raw',
|
|
100
|
+
timestampHeader: 'x-polar-timestamp',
|
|
101
|
+
timestampFormat: 'unix',
|
|
102
|
+
payloadFormat: 'raw',
|
|
103
103
|
},
|
|
104
|
-
description:
|
|
104
|
+
description: 'Polar webhooks use HMAC-SHA256',
|
|
105
105
|
},
|
|
106
106
|
supabase: {
|
|
107
|
-
platform:
|
|
107
|
+
platform: 'supabase',
|
|
108
108
|
signatureConfig: {
|
|
109
|
-
algorithm:
|
|
110
|
-
headerName:
|
|
111
|
-
headerFormat:
|
|
112
|
-
payloadFormat:
|
|
109
|
+
algorithm: 'custom',
|
|
110
|
+
headerName: 'x-webhook-token',
|
|
111
|
+
headerFormat: 'raw',
|
|
112
|
+
payloadFormat: 'raw',
|
|
113
113
|
customConfig: {
|
|
114
|
-
type:
|
|
115
|
-
idHeader:
|
|
114
|
+
type: 'token-based',
|
|
115
|
+
idHeader: 'x-webhook-id',
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
|
-
description:
|
|
118
|
+
description: 'Supabase webhooks use token-based authentication',
|
|
119
|
+
},
|
|
120
|
+
gitlab: {
|
|
121
|
+
platform: 'gitlab',
|
|
122
|
+
signatureConfig: {
|
|
123
|
+
algorithm: 'custom',
|
|
124
|
+
headerName: 'X-Gitlab-Token',
|
|
125
|
+
headerFormat: 'raw',
|
|
126
|
+
payloadFormat: 'raw',
|
|
127
|
+
customConfig: {
|
|
128
|
+
type: 'token-based',
|
|
129
|
+
idHeader: 'X-Gitlab-Token',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
description: 'GitLab webhooks use HMAC-SHA256 with X-Gitlab-Token header',
|
|
119
133
|
},
|
|
120
134
|
custom: {
|
|
121
|
-
platform:
|
|
135
|
+
platform: 'custom',
|
|
122
136
|
signatureConfig: {
|
|
123
|
-
algorithm:
|
|
124
|
-
headerName:
|
|
125
|
-
headerFormat:
|
|
126
|
-
payloadFormat:
|
|
137
|
+
algorithm: 'hmac-sha256',
|
|
138
|
+
headerName: 'x-webhook-token',
|
|
139
|
+
headerFormat: 'raw',
|
|
140
|
+
payloadFormat: 'raw',
|
|
127
141
|
customConfig: {
|
|
128
|
-
type:
|
|
129
|
-
idHeader:
|
|
142
|
+
type: 'token-based',
|
|
143
|
+
idHeader: 'x-webhook-id',
|
|
130
144
|
},
|
|
131
145
|
},
|
|
132
|
-
description:
|
|
146
|
+
description: 'Custom webhook configuration',
|
|
133
147
|
},
|
|
134
148
|
unknown: {
|
|
135
|
-
platform:
|
|
149
|
+
platform: 'unknown',
|
|
136
150
|
signatureConfig: {
|
|
137
|
-
algorithm:
|
|
138
|
-
headerName:
|
|
139
|
-
headerFormat:
|
|
140
|
-
payloadFormat:
|
|
151
|
+
algorithm: 'hmac-sha256',
|
|
152
|
+
headerName: 'x-webhook-signature',
|
|
153
|
+
headerFormat: 'raw',
|
|
154
|
+
payloadFormat: 'raw',
|
|
141
155
|
},
|
|
142
|
-
description:
|
|
156
|
+
description: 'Unknown platform - using default HMAC-SHA256',
|
|
143
157
|
},
|
|
144
158
|
};
|
|
145
159
|
function getPlatformAlgorithmConfig(platform) {
|
|
@@ -159,14 +173,14 @@ function validateSignatureConfig(config) {
|
|
|
159
173
|
return false;
|
|
160
174
|
}
|
|
161
175
|
switch (config.algorithm) {
|
|
162
|
-
case
|
|
163
|
-
case
|
|
164
|
-
case
|
|
176
|
+
case 'hmac-sha256':
|
|
177
|
+
case 'hmac-sha1':
|
|
178
|
+
case 'hmac-sha512':
|
|
165
179
|
return true;
|
|
166
|
-
case
|
|
167
|
-
case
|
|
180
|
+
case 'rsa-sha256':
|
|
181
|
+
case 'ed25519':
|
|
168
182
|
return !!config.customConfig?.publicKey;
|
|
169
|
-
case
|
|
183
|
+
case 'custom':
|
|
170
184
|
return !!config.customConfig;
|
|
171
185
|
default:
|
|
172
186
|
return false;
|