@hookflo/tern
Version:
A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms
175 lines (174 loc) • 5.88 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.platformAlgorithmConfigs = void 0;
exports.getPlatformAlgorithmConfig = getPlatformAlgorithmConfig;
exports.platformUsesAlgorithm = platformUsesAlgorithm;
exports.getPlatformsUsingAlgorithm = getPlatformsUsingAlgorithm;
exports.validateSignatureConfig = validateSignatureConfig;
exports.platformAlgorithmConfigs = {
github: {
platform: "github",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "x-hub-signature-256",
headerFormat: "prefixed",
prefix: "sha256=",
timestampHeader: undefined,
payloadFormat: "raw",
},
description: "GitHub webhooks use HMAC-SHA256 with sha256= prefix",
},
stripe: {
platform: "stripe",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "stripe-signature",
headerFormat: "comma-separated",
timestampHeader: undefined,
payloadFormat: "timestamped",
customConfig: {
signatureFormat: "t={timestamp},v1={signature}",
},
},
description: "Stripe webhooks use HMAC-SHA256 with comma-separated format",
},
clerk: {
platform: "clerk",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "svix-signature",
headerFormat: "raw",
timestampHeader: "svix-timestamp",
timestampFormat: "unix",
payloadFormat: "custom",
customConfig: {
signatureFormat: "v1={signature}",
payloadFormat: "{id}.{timestamp}.{body}",
encoding: "base64",
idHeader: "svix-id",
},
},
description: "Clerk webhooks use HMAC-SHA256 with base64 encoding",
},
dodopayments: {
platform: "dodopayments",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "webhook-signature",
headerFormat: "raw",
timestampHeader: "webhook-timestamp",
timestampFormat: "unix",
payloadFormat: "custom",
customConfig: {
signatureFormat: "v1={signature}",
payloadFormat: "{id}.{timestamp}.{body}",
encoding: "base64",
idHeader: "webhook-id",
},
},
description: "Dodo Payments webhooks use HMAC-SHA256 with svix-style format (Standard Webhooks)",
},
shopify: {
platform: "shopify",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "x-shopify-hmac-sha256",
headerFormat: "raw",
timestampHeader: "x-shopify-shop-domain",
payloadFormat: "raw",
},
description: "Shopify webhooks use HMAC-SHA256",
},
vercel: {
platform: "vercel",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "x-vercel-signature",
headerFormat: "raw",
timestampHeader: "x-vercel-timestamp",
timestampFormat: "unix",
payloadFormat: "raw",
},
description: "Vercel webhooks use HMAC-SHA256",
},
polar: {
platform: "polar",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "x-polar-signature",
headerFormat: "raw",
timestampHeader: "x-polar-timestamp",
timestampFormat: "unix",
payloadFormat: "raw",
},
description: "Polar webhooks use HMAC-SHA256",
},
supabase: {
platform: "supabase",
signatureConfig: {
algorithm: "custom",
headerName: "x-webhook-token",
headerFormat: "raw",
payloadFormat: "raw",
customConfig: {
type: "token-based",
idHeader: "x-webhook-id",
},
},
description: "Supabase webhooks use token-based authentication",
},
custom: {
platform: "custom",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "x-webhook-token",
headerFormat: "raw",
payloadFormat: "raw",
customConfig: {
type: "token-based",
idHeader: "x-webhook-id",
},
},
description: "Custom webhook configuration",
},
unknown: {
platform: "unknown",
signatureConfig: {
algorithm: "hmac-sha256",
headerName: "x-webhook-signature",
headerFormat: "raw",
payloadFormat: "raw",
},
description: "Unknown platform - using default HMAC-SHA256",
},
};
function getPlatformAlgorithmConfig(platform) {
return exports.platformAlgorithmConfigs[platform] || exports.platformAlgorithmConfigs.unknown;
}
function platformUsesAlgorithm(platform, algorithm) {
const config = getPlatformAlgorithmConfig(platform);
return config.signatureConfig.algorithm === algorithm;
}
function getPlatformsUsingAlgorithm(algorithm) {
return Object.entries(exports.platformAlgorithmConfigs)
.filter(([_, config]) => config.signatureConfig.algorithm === algorithm)
.map(([platform, _]) => platform);
}
function validateSignatureConfig(config) {
if (!config.algorithm || !config.headerName) {
return false;
}
switch (config.algorithm) {
case "hmac-sha256":
case "hmac-sha1":
case "hmac-sha512":
return true;
case "rsa-sha256":
case "ed25519":
return !!config.customConfig?.publicKey;
case "custom":
return !!config.customConfig;
default:
return false;
}
}