{"id":30134921,"url":"https://github.com/hookflo/tern","last_synced_at":"2026-02-19T17:03:32.922Z","repository":{"id":308892328,"uuid":"1034434790","full_name":"Hookflo/tern","owner":"Hookflo","description":"A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms.","archived":false,"fork":false,"pushed_at":"2026-02-18T18:03:20.000Z","size":285,"stargazers_count":21,"open_issues_count":12,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-18T19:27:24.885Z","etag":null,"topics":["hmac","npm","svix","typescript","webhook-verifier","webhooks"],"latest_commit_sha":null,"homepage":"https://tern.hookflo.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hookflo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-08T11:38:56.000Z","updated_at":"2026-02-18T18:03:25.000Z","dependencies_parsed_at":"2025-08-08T15:04:07.865Z","dependency_job_id":"b57bb6e6-c019-40b4-a8ff-09c5618adb01","html_url":"https://github.com/Hookflo/tern","commit_stats":null,"previous_names":["hookflo/tern"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/Hookflo/tern","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookflo%2Ftern","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookflo%2Ftern/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookflo%2Ftern/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookflo%2Ftern/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hookflo","download_url":"https://codeload.github.com/Hookflo/tern/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookflo%2Ftern/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29623548,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T13:04:20.082Z","status":"ssl_error","status_checked_at":"2026-02-19T13:03:33.775Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hmac","npm","svix","typescript","webhook-verifier","webhooks"],"created_at":"2025-08-10T22:01:03.395Z","updated_at":"2026-02-19T17:03:27.913Z","avatar_url":"https://github.com/Hookflo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tern - Algorithm Agnostic Webhook Verification Framework\n\nA robust, algorithm-agnostic webhook verification framework that supports multiple platforms with accurate signature verification and payload retrieval.\nThe same framework that secures webhook verification at [Hookflo](https://hookflo.com).\n\n⭐ Star this repo to support the project and help others discover it!  \n\n💬 Join the discussion \u0026 contribute in our Discord: [Hookflo Community](https://discord.com/invite/SNmCjU97nr)\n\n```bash\nnpm install @hookflo/tern\n```\n\n[![npm version](https://img.shields.io/npm/v/@hookflo/tern)](https://www.npmjs.com/package/@hookflo/tern)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue)](https://www.typescriptlang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nTern is a zero-dependency TypeScript framework for robust webhook verification across multiple platforms and algorithms.\n\n\u003cimg width=\"1396\" height=\"470\" style=\"border-radius: 10px\" alt=\"tern bird nature\" src=\"https://github.com/user-attachments/assets/5f0da3e6-1aba-4f88-a9d7-9d8698845c39\" /\u003e\n\n## Features\n\n- **Algorithm Agnostic**: Decouples platform logic from signature verification — verify based on cryptographic algorithm, not hardcoded platform rules.\nSupports HMAC-SHA256, HMAC-SHA1, HMAC-SHA512, and custom algorithms\n\n- **Platform Specific**: Accurate implementations for **Stripe, GitHub, Supabase, Clerk**, and other platforms\n- **Flexible Configuration**: Custom signature configurations for any webhook format\n- **Type Safe**: Full TypeScript support with comprehensive type definitions\n- **Framework Agnostic**: Works with Express.js, Next.js, Cloudflare Workers, and more\n\n## Why Tern?\n\nMost webhook verifiers are tightly coupled to specific platforms or hardcoded logic. Tern introduces a flexible, scalable, algorithm-first approach that:\n\n- Works across all major platforms\n- Supports custom signing logic\n- Keeps your code clean and modular\n- Avoids unnecessary dependencies\n- Is written in strict, modern TypeScript\n\n## Installation\n\n```bash\nnpm install @hookflo/tern\n```\n\n## Quick Start\n\n### Basic Usage\n\n```typescript\nimport { WebhookVerificationService, platformManager } from '@hookflo/tern';\n\n// Method 1: Using the service (recommended)\nconst result = await WebhookVerificationService.verifyWithPlatformConfig(\n  request,\n  'stripe',\n  'whsec_your_stripe_webhook_secret'\n);\n\n// Method 2: Using platform manager (for platform-specific operations)\nconst stripeResult = await platformManager.verify(request, 'stripe', 'whsec_your_secret');\n\nif (result.isValid) {\n  console.log('Webhook verified!', result.payload);\n} else {\n  console.log('Verification failed:', result.error);\n}\n```\n\n### Platform-Specific Usage\n\n```typescript\nimport { platformManager } from '@hookflo/tern';\n\n// Run tests for a specific platform\nconst testsPassed = await platformManager.runPlatformTests('stripe');\n\n// Get platform configuration\nconst config = platformManager.getConfig('stripe');\n\n// Get platform documentation\nconst docs = platformManager.getDocumentation('stripe');\n```\n\n### Platform-Specific Configurations\n\n```typescript\nimport { WebhookVerificationService } from '@hookflo/tern';\n\n// Stripe webhook\nconst stripeConfig = {\n  platform: 'stripe',\n  secret: 'whsec_your_stripe_webhook_secret',\n  toleranceInSeconds: 300,\n};\n\n// GitHub webhook\nconst githubConfig = {\n  platform: 'github',\n  secret: 'your_github_webhook_secret',\n  toleranceInSeconds: 300,\n};\n\n// Clerk webhook\nconst clerkConfig = {\n  platform: 'clerk',\n  secret: 'whsec_your_clerk_webhook_secret',\n  toleranceInSeconds: 300,\n};\n\nconst result = await WebhookVerificationService.verify(request, stripeConfig);\n```\n\n## Supported Platforms\n\n### Stripe\n- **Signature Format**: `t={timestamp},v1={signature}`\n- **Algorithm**: HMAC-SHA256\n- **Payload Format**: `{timestamp}.{body}`\n\n### GitHub\n- **Signature Format**: `sha256={signature}`\n- **Algorithm**: HMAC-SHA256\n- **Payload Format**: Raw body\n\n### Clerk\n- **Signature Format**: `v1,{signature}` (space-separated)\n- **Algorithm**: HMAC-SHA256 with base64 encoding\n- **Payload Format**: `{id}.{timestamp}.{body}`\n\n### Other Platforms\n- **Dodo Payments**: HMAC-SHA256\n- **Shopify**: HMAC-SHA256\n- **Vercel**: HMAC-SHA256\n- **Polar**: HMAC-SHA256\n- **Supabase**: Token-based authentication\n- **GitLab**: Token-based authentication \n\n## Custom Platform Configuration\n\nThis framework is fully configuration-driven. You can verify webhooks from any provider—even if it is not built-in—by supplying a custom configuration object. This allows you to support new or proprietary platforms instantly, without waiting for a library update.\n\n### Example: Standard HMAC-SHA256 Webhook\n\n```typescript\nimport { WebhookVerificationService } from '@hookflo/tern';\n\nconst acmeConfig = {\n  platform: 'acmepay',\n  secret: 'acme_secret',\n  signatureConfig: {\n    algorithm: 'hmac-sha256',\n    headerName: 'x-acme-signature',\n    headerFormat: 'raw',\n    timestampHeader: 'x-acme-timestamp',\n    timestampFormat: 'unix',\n    payloadFormat: 'timestamped', // signs as {timestamp}.{body}\n  }\n};\n\nconst result = await WebhookVerificationService.verify(request, acmeConfig);\n```\n\n### Example: Svix/Standard Webhooks (Clerk, Dodo Payments, etc.)\n\n```typescript\nconst svixConfig = {\n  platform: 'my-svix-platform',\n  secret: 'whsec_abc123...',\n  signatureConfig: {\n    algorithm: 'hmac-sha256',\n    headerName: 'webhook-signature',\n    headerFormat: 'raw',\n    timestampHeader: 'webhook-timestamp',\n    timestampFormat: 'unix',\n    payloadFormat: 'custom',\n    customConfig: {\n      payloadFormat: '{id}.{timestamp}.{body}',\n      idHeader: 'webhook-id',\n      // encoding: 'base64' // only if the provider uses base64, otherwise omit\n    }\n  }\n};\n\nconst result = await WebhookVerificationService.verify(request, svixConfig);\n```\n\nYou can configure any combination of algorithm, header, payload, and encoding. See the `SignatureConfig` type for all options.\n\n## Webhook Verification OK Tested Platforms\n- **Stripe**\n- **Supabase**\n- **Github**\n- **Clerk**\n- **Dodo Payments**\n\n- **Other Platforms** : Yet to verify....\n\n\n## Custom Configurations\n\n### Custom HMAC-SHA256\n\n```typescript\nconst customConfig = {\n  platform: 'custom',\n  secret: 'your_custom_secret',\n  signatureConfig: {\n    algorithm: 'hmac-sha256',\n    headerName: 'x-custom-signature',\n    headerFormat: 'prefixed',\n    prefix: 'sha256=',\n    payloadFormat: 'raw',\n  },\n};\n```\n\n### Custom Timestamped Payload\n\n```typescript\nconst timestampedConfig = {\n  platform: 'custom',\n  secret: 'your_custom_secret',\n  signatureConfig: {\n    algorithm: 'hmac-sha256',\n    headerName: 'x-webhook-signature',\n    headerFormat: 'raw',\n    timestampHeader: 'x-webhook-timestamp',\n    timestampFormat: 'unix',\n    payloadFormat: 'timestamped',\n  },\n};\n```\n\n## Framework Integration\n\n### Express.js\n\n```typescript\napp.post('/webhooks/stripe', async (req, res) =\u003e {\n  const result = await WebhookVerificationService.verifyWithPlatformConfig(\n    req,\n    'stripe',\n    process.env.STRIPE_WEBHOOK_SECRET\n  );\n\n  if (!result.isValid) {\n    return res.status(400).json({ error: result.error });\n  }\n\n  // Process the webhook\n  console.log('Stripe event:', result.payload.type);\n  res.json({ received: true });\n});\n```\n\n### Next.js API Route\n\n```typescript\n// pages/api/webhooks/github.js\nexport default async function handler(req, res) {\n  if (req.method !== 'POST') {\n    return res.status(405).json({ error: 'Method not allowed' });\n  }\n\n  const result = await WebhookVerificationService.verifyWithPlatformConfig(\n    req,\n    'github',\n    process.env.GITHUB_WEBHOOK_SECRET\n  );\n\n  if (!result.isValid) {\n    return res.status(400).json({ error: result.error });\n  }\n\n  // Handle GitHub webhook\n  const event = req.headers['x-github-event'];\n  console.log('GitHub event:', event);\n  \n  res.json({ received: true });\n}\n```\n\n### Cloudflare Workers\n\n```typescript\naddEventListener('fetch', event =\u003e {\n  event.respondWith(handleRequest(event.request));\n});\n\nasync function handleRequest(request) {\n  if (request.url.includes('/webhooks/clerk')) {\n    const result = await WebhookVerificationService.verifyWithPlatformConfig(\n      request,\n      'clerk',\n      CLERK_WEBHOOK_SECRET\n    );\n\n    if (!result.isValid) {\n      return new Response(JSON.stringify({ error: result.error }), {\n        status: 400,\n        headers: { 'Content-Type': 'application/json' }\n      });\n    }\n\n    // Process Clerk webhook\n    console.log('Clerk event:', result.payload.type);\n    return new Response(JSON.stringify({ received: true }));\n  }\n}\n```\n\n## API Reference\n\n### WebhookVerificationService\n\n#### `verify(request: Request, config: WebhookConfig): Promise\u003cWebhookVerificationResult\u003e`\n\nVerifies a webhook using the provided configuration.\n\n#### `verifyWithPlatformConfig(request: Request, platform: WebhookPlatform, secret: string, toleranceInSeconds?: number): Promise\u003cWebhookVerificationResult\u003e`\n\nSimplified verification using platform-specific configurations.\n\n#### `verifyTokenBased(request: Request, webhookId: string, webhookToken: string): Promise\u003cWebhookVerificationResult\u003e`\n\nVerifies token-based webhooks (like Supabase).\n\n### Types\n\n#### `WebhookVerificationResult`\n\n```typescript\ninterface WebhookVerificationResult {\n  isValid: boolean;\n  error?: string;\n  platform: WebhookPlatform;\n  payload?: any;\n  metadata?: {\n    timestamp?: string;\n    id?: string | null;\n    [key: string]: any;\n  };\n}\n```\n\n#### `WebhookConfig`\n\n```typescript\ninterface WebhookConfig {\n  platform: WebhookPlatform;\n  secret: string;\n  toleranceInSeconds?: number;\n  signatureConfig?: SignatureConfig;\n}\n```\n\n## Testing\n\n### Run All Tests\n\n```bash\nnpm test\n```\n\n### Platform-Specific Testing\n\n```bash\n# Test a specific platform\nnpm run test:platform stripe\n\n# Test all platforms\nnpm run test:all\n```\n\n### Documentation and Analysis\n\n```bash\n# Fetch platform documentation\nnpm run docs:fetch\n\n# Generate diffs between versions\nnpm run docs:diff\n\n# Analyze changes and generate reports\nnpm run docs:analyze\n```\n\n## Examples\n\nSee the [examples.ts](./src/examples.ts) file for comprehensive usage examples.\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for detailed information on how to:\n\n- Set up your development environment\n- Add new platforms\n- Write tests\n- Submit pull requests\n- Follow our code style guidelines\n\n### Quick Start for Contributors\n\n1. Fork the repository\n2. Clone your fork: `git clone https://github.com/your-username/tern.git`\n3. Create a feature branch: `git checkout -b feature/your-feature-name`\n4. Make your changes\n5. Run tests: `npm test`\n6. Submit a pull request\n\n### Adding a New Platform\n\nSee our [Platform Development Guide](CONTRIBUTING.md#adding-new-platforms) for step-by-step instructions on adding support for new webhook platforms.\n\n## Code of Conduct\n\nThis project adheres to our [Code of Conduct](CODE_OF_CONDUCT.md). Please read it before contributing.\n\n## 📄 License\n\nMIT License - see [LICENSE](./LICENSE) for details.\n\n## 🔗 Links\n\n- [Documentation](./USAGE.md)\n- [Framework Summary](./FRAMEWORK_SUMMARY.md)\n- [Issues](https://github.com/Hookflo/tern/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhookflo%2Ftern","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhookflo%2Ftern","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhookflo%2Ftern/lists"}