diff --git a/.changeset/hungry-mangos-shout.md b/.changeset/hungry-mangos-shout.md new file mode 100644 index 00000000000..a17e239a436 --- /dev/null +++ b/.changeset/hungry-mangos-shout.md @@ -0,0 +1,18 @@ +--- +'@clerk/backend': minor +'@clerk/astro': minor +'@clerk/express': minor +'@clerk/fastify': minor +'@clerk/nextjs': minor +'@clerk/nuxt': minor +'@clerk/react-router': minor +'@clerk/tanstack-react-start': minor +--- + +The `svix` dependency is no longer needed when using the `verifyWebhook()` function. `verifyWebhook()` was refactored to not rely on `svix` anymore while keeping the same functionality and behavior. + +If you previously installed `svix` to use `verifyWebhook()` you can uninstall it now: + +```shell +npm uninstall svix +``` diff --git a/packages/backend/package.json b/packages/backend/package.json index a98b1b5acea..9978d559657 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -117,17 +117,8 @@ "@edge-runtime/vm": "5.0.0", "msw": "2.8.7", "npm-run-all": "^4.1.5", - "svix": "^1.66.0", "vitest-environment-miniflare": "2.14.4" }, - "peerDependencies": { - "svix": "^1.62.0" - }, - "peerDependenciesMeta": { - "svix": { - "optional": true - } - }, "engines": { "node": ">=18.17.0" }, diff --git a/packages/backend/src/webhooks.ts b/packages/backend/src/webhooks.ts index d1abc262d33..ca083ab8eb9 100644 --- a/packages/backend/src/webhooks.ts +++ b/packages/backend/src/webhooks.ts @@ -1,6 +1,6 @@ import { getEnvVariable } from '@clerk/shared/getEnvVariable'; +import crypto from 'crypto'; import { errorThrower } from 'src/util/shared'; -import { Webhook } from 'svix'; import type { WebhookEvent } from './api/resources/Webhooks'; @@ -69,12 +69,24 @@ export async function verifyWebhook(request: Request, options: VerifyWebhookOpti return errorThrower.throw(`Missing required Svix headers: ${missingHeaders.join(', ')}`); } - const sivx = new Webhook(secret); const body = await request.text(); - return sivx.verify(body, { - [SVIX_ID_HEADER]: svixId, - [SVIX_TIMESTAMP_HEADER]: svixTimestamp, - [SVIX_SIGNATURE_HEADER]: svixSignature, - }) as WebhookEvent; + const signedContent = `${svixId}.${svixTimestamp}.${body}`; + + const secretBytes = Buffer.from(secret.split('_')[1], 'base64'); + + const constructedSignature = crypto.createHmac('sha256', secretBytes).update(signedContent).digest('base64'); + + // svixSignature can be a string with one or more space separated signatures + if (svixSignature.split(' ').includes(constructedSignature)) { + return errorThrower.throw('Incoming webhook does not have a valid signature'); + } + + const payload = JSON.parse(body); + + return { + type: payload.type, + object: 'event', + data: payload.data, + } as WebhookEvent; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e32f9dd7686..884dcada42c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -388,9 +388,6 @@ importers: npm-run-all: specifier: ^4.1.5 version: 4.1.5 - svix: - specifier: ^1.66.0 - version: 1.66.0 vitest-environment-miniflare: specifier: 2.14.4 version: 2.14.4(vitest@3.0.5(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.15.29)(jiti@2.4.2)(jsdom@24.1.3)(lightningcss@1.27.0)(msw@2.8.7(@types/node@22.15.29)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)) @@ -2984,7 +2981,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -4549,9 +4546,6 @@ packages: '@speed-highlight/core@1.2.7': resolution: {integrity: sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==} - '@stablelib/base64@1.0.1': - resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} - '@statelyai/inspect@0.4.0': resolution: {integrity: sha512-VxQldRlKYcu6rzLY83RSXVwMYexkH6hNx85B89YWYyXYWtNGaWHFCwV7a/Kz8FFPeUz8EKVAnyMOg2kNpn07wQ==} peerDependencies: @@ -7620,9 +7614,6 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es6-promise@4.2.8: - resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - esbuild-plugin-file-path-extensions@2.1.4: resolution: {integrity: sha512-lNjylaAsJMprYg28zjUyBivP3y0ms9b7RJZ5tdhDUFLa3sCbqZw4wDnbFUSmnyZYWhCYDPxxp7KkXM2TXGw3PQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -8146,9 +8137,6 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-sha256@1.3.0: - resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} - fast-uri@2.4.0: resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==} @@ -13019,12 +13007,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - svix-fetch@3.0.0: - resolution: {integrity: sha512-rcADxEFhSqHbraZIsjyZNh4TF6V+koloX1OzZ+AQuObX9mZ2LIMhm1buZeuc5BIZPftZpJCMBsSiBaeszo9tRw==} - - svix@1.66.0: - resolution: {integrity: sha512-tGzdhXHdVebNxcflLGxhKUjbNvYv6oRnbFsQ4IpfpUCliZBb7QXMCf32kB1R6dkTX+1h0cbSn9sCL3d4/Bv7wA==} - swr@2.3.3: resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==} peerDependencies: @@ -18888,8 +18870,6 @@ snapshots: '@speed-highlight/core@1.2.7': {} - '@stablelib/base64@1.0.1': {} - '@statelyai/inspect@0.4.0(ws@8.18.1)(xstate@5.19.3)': dependencies: fast-safe-stringify: 2.1.1 @@ -22926,8 +22906,6 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es6-promise@4.2.8: {} - esbuild-plugin-file-path-extensions@2.1.4: {} esbuild@0.20.2: @@ -23844,8 +23822,6 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-sha256@1.3.0: {} - fast-uri@2.4.0: {} fast-uri@3.0.3: {} @@ -29855,24 +29831,6 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - svix-fetch@3.0.0: - dependencies: - node-fetch: 2.7.0 - whatwg-fetch: 3.6.20 - transitivePeerDependencies: - - encoding - - svix@1.66.0: - dependencies: - '@stablelib/base64': 1.0.1 - '@types/node': 22.15.29 - es6-promise: 4.2.8 - fast-sha256: 1.3.0 - svix-fetch: 3.0.0 - url-parse: 1.5.10 - transitivePeerDependencies: - - encoding - swr@2.3.3(react@18.3.1): dependencies: dequal: 2.0.3