@hookflo/tern
Version:
A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms
202 lines (201 loc) โข 9.01 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.runTests = runTests;
const crypto_1 = require("crypto");
const index_1 = require("./index");
const testSecret = 'whsec_test_secret_key_12345';
const testBody = JSON.stringify({ event: 'test', data: { id: '123' } });
function createMockRequest(headers, body = testBody) {
return new Request('https://example.com/webhook', {
method: 'POST',
headers,
body,
});
}
function createStripeSignature(body, secret, timestamp) {
const signedPayload = `${timestamp}.${body}`;
const hmac = (0, crypto_1.createHmac)('sha256', secret);
hmac.update(signedPayload);
const signature = hmac.digest('hex');
return `t=${timestamp},v1=${signature}`;
}
function createGitHubSignature(body, secret) {
const hmac = (0, crypto_1.createHmac)('sha256', secret);
hmac.update(body);
return `sha256=${hmac.digest('hex')}`;
}
function createClerkSignature(body, secret, id, timestamp) {
const signedContent = `${id}.${timestamp}.${body}`;
const secretBytes = new Uint8Array(Buffer.from(secret.split('_')[1], 'base64'));
const hmac = (0, crypto_1.createHmac)('sha256', secretBytes);
hmac.update(signedContent);
return `v1,${hmac.digest('base64')}`;
}
async function runTests() {
console.log('๐งช Running Webhook Verification Tests...\n');
// Test 1: Stripe Webhook
console.log('1. Testing Stripe Webhook...');
try {
const timestamp = Math.floor(Date.now() / 1000);
const stripeSignature = createStripeSignature(testBody, testSecret, timestamp);
const stripeRequest = createMockRequest({
'stripe-signature': stripeSignature,
'content-type': 'application/json',
});
const stripeResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(stripeRequest, 'stripe', testSecret);
console.log(' โ
Stripe:', stripeResult.isValid ? 'PASSED' : 'FAILED');
if (!stripeResult.isValid) {
console.log(' โ Error:', stripeResult.error);
}
}
catch (error) {
console.log(' โ Stripe test failed:', error);
}
// Test 2: GitHub Webhook
console.log('\n2. Testing GitHub Webhook...');
try {
const githubSignature = createGitHubSignature(testBody, testSecret);
const githubRequest = createMockRequest({
'x-hub-signature-256': githubSignature,
'x-github-event': 'push',
'x-github-delivery': 'test-delivery-id',
'content-type': 'application/json',
});
const githubResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(githubRequest, 'github', testSecret);
console.log(' โ
GitHub:', githubResult.isValid ? 'PASSED' : 'FAILED');
if (!githubResult.isValid) {
console.log(' โ Error:', githubResult.error);
}
}
catch (error) {
console.log(' โ GitHub test failed:', error);
}
// Test 3: Clerk Webhook
console.log('\n3. Testing Clerk Webhook...');
try {
// Create a proper Clerk-style secret (whsec_ + base64 encoded secret)
const base64Secret = Buffer.from(testSecret).toString('base64');
const clerkSecret = `whsec_${base64Secret}`;
const id = 'test-id-123';
const timestamp = Math.floor(Date.now() / 1000);
const clerkSignature = createClerkSignature(testBody, clerkSecret, id, timestamp);
const clerkRequest = createMockRequest({
'svix-signature': clerkSignature,
'svix-id': id,
'svix-timestamp': timestamp.toString(),
'content-type': 'application/json',
});
const clerkResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(clerkRequest, 'clerk', clerkSecret);
console.log(' โ
Clerk:', clerkResult.isValid ? 'PASSED' : 'FAILED');
if (!clerkResult.isValid) {
console.log(' โ Error:', clerkResult.error);
}
}
catch (error) {
console.log(' โ Clerk test failed:', error);
}
// Test 4: Standard HMAC-SHA256 (Generic)
console.log('\n4. Testing Standard HMAC-SHA256...');
try {
const hmac = (0, crypto_1.createHmac)('sha256', testSecret);
hmac.update(testBody);
const signature = hmac.digest('hex');
const genericRequest = createMockRequest({
'x-webhook-signature': signature,
'content-type': 'application/json',
});
const genericResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(genericRequest, 'unknown', testSecret);
console.log(' โ
Generic HMAC-SHA256:', genericResult.isValid ? 'PASSED' : 'FAILED');
if (!genericResult.isValid) {
console.log(' โ Error:', genericResult.error);
}
}
catch (error) {
console.log(' โ Generic test failed:', error);
}
// Test 4.5: Dodo Payments (Standard Webhooks / svix-style)
console.log('\n4.5. Testing Dodo Payments...');
try {
const webhookId = 'test-webhook-id-123';
const timestamp = Math.floor(Date.now() / 1000);
// Create a proper secret format for Standard Webhooks (whsec_ + base64 encoded secret)
const base64Secret = Buffer.from(testSecret).toString('base64');
const dodoSecret = `whsec_${base64Secret}`;
// Create svix-style signature: {webhook-id}.{webhook-timestamp}.{payload}
const signedContent = `${webhookId}.${timestamp}.${testBody}`;
// Use the base64-decoded secret for HMAC (like the Standard Webhooks library)
const secretBytes = new Uint8Array(Buffer.from(base64Secret, 'base64'));
const hmac = (0, crypto_1.createHmac)('sha256', secretBytes);
hmac.update(signedContent);
const signature = `v1,${hmac.digest('base64')}`;
const dodoRequest = createMockRequest({
'webhook-signature': signature,
'webhook-id': webhookId,
'webhook-timestamp': timestamp.toString(),
'content-type': 'application/json',
});
const dodoResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(dodoRequest, 'dodopayments', dodoSecret);
console.log(' โ
Dodo Payments:', dodoResult.isValid ? 'PASSED' : 'FAILED');
if (!dodoResult.isValid) {
console.log(' โ Error:', dodoResult.error);
}
}
catch (error) {
console.log(' โ Dodo Payments test failed:', error);
}
// Test 5: Token-based (Supabase)
console.log('\n5. Testing Token-based Authentication...');
try {
const webhookId = 'test-webhook-id';
const webhookToken = 'test-webhook-token';
const tokenRequest = createMockRequest({
'x-webhook-id': webhookId,
'x-webhook-token': webhookToken,
'content-type': 'application/json',
});
const tokenResult = await index_1.WebhookVerificationService.verifyTokenBased(tokenRequest, webhookId, webhookToken);
console.log(' โ
Token-based:', tokenResult.isValid ? 'PASSED' : 'FAILED');
if (!tokenResult.isValid) {
console.log(' โ Error:', tokenResult.error);
}
}
catch (error) {
console.log(' โ Token-based test failed:', error);
}
// Test 6: Invalid signatures
console.log('\n6. Testing Invalid Signatures...');
try {
const invalidRequest = createMockRequest({
'stripe-signature': 't=1234567890,v1=invalid_signature',
'content-type': 'application/json',
});
const invalidResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(invalidRequest, 'stripe', testSecret);
console.log(' โ
Invalid signature correctly rejected:', !invalidResult.isValid ? 'PASSED' : 'FAILED');
if (invalidResult.isValid) {
console.log(' โ Should have been rejected');
}
}
catch (error) {
console.log(' โ Invalid signature test failed:', error);
}
// Test 7: Missing headers
console.log('\n7. Testing Missing Headers...');
try {
const missingHeaderRequest = createMockRequest({
'content-type': 'application/json',
});
const missingHeaderResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(missingHeaderRequest, 'stripe', testSecret);
console.log(' โ
Missing headers correctly rejected:', !missingHeaderResult.isValid ? 'PASSED' : 'FAILED');
if (missingHeaderResult.isValid) {
console.log(' โ Should have been rejected');
}
}
catch (error) {
console.log(' โ Missing headers test failed:', error);
}
console.log('\n๐ All tests completed!');
}
// Run tests if this file is executed directly
if (require.main === module) {
runTests().catch(console.error);
}