@clerk/backend
Version:
Clerk Backend SDK - REST Client for Backend API & JWT verification utilities
1 lines • 6.23 kB
Source Map (JSON)
{"version":3,"sources":["../src/webhooks.ts"],"sourcesContent":["import { getEnvVariable } from '@clerk/shared/getEnvVariable';\nimport { errorThrower } from 'src/util/shared';\nimport { Webhook } from 'standardwebhooks';\n\nimport type { WebhookEvent } from './api/resources/Webhooks';\n\n/**\n * @inline\n */\nexport type VerifyWebhookOptions = {\n /**\n * The signing secret for the webhook. It's recommended to use the [`CLERK_WEBHOOK_SIGNING_SECRET` environment variable](https://clerk.com/docs/deployments/clerk-environment-variables#webhooks) instead.\n */\n signingSecret?: string;\n};\n\n// Standard Webhooks header names\nconst STANDARD_WEBHOOK_ID_HEADER = 'webhook-id';\nconst STANDARD_WEBHOOK_TIMESTAMP_HEADER = 'webhook-timestamp';\nconst STANDARD_WEBHOOK_SIGNATURE_HEADER = 'webhook-signature';\n\n// Svix header names (for mapping)\nconst SVIX_ID_HEADER = 'svix-id';\nconst SVIX_TIMESTAMP_HEADER = 'svix-timestamp';\nconst SVIX_SIGNATURE_HEADER = 'svix-signature';\n\nexport * from './api/resources/Webhooks';\n\n/**\n * Maps Svix headers to Standard Webhooks headers for compatibility\n */\nfunction createStandardWebhookHeaders(request: Request): Record<string, string> {\n const headers: Record<string, string> = {};\n\n // Map Svix headers to Standard Webhooks headers\n const svixId = request.headers.get(SVIX_ID_HEADER)?.trim();\n const svixTimestamp = request.headers.get(SVIX_TIMESTAMP_HEADER)?.trim();\n const svixSignature = request.headers.get(SVIX_SIGNATURE_HEADER)?.trim();\n\n if (svixId) {\n headers[STANDARD_WEBHOOK_ID_HEADER] = svixId;\n }\n if (svixTimestamp) {\n headers[STANDARD_WEBHOOK_TIMESTAMP_HEADER] = svixTimestamp;\n }\n if (svixSignature) {\n headers[STANDARD_WEBHOOK_SIGNATURE_HEADER] = svixSignature;\n }\n\n return headers;\n}\n\n/**\n * Verifies the authenticity of a webhook request using Standard Webhooks. Returns a promise that resolves to the verified webhook event data.\n *\n * @param request - The request object.\n * @param options - Optional configuration object.\n *\n * @displayFunctionSignature\n *\n * @example\n * See the [guide on syncing data](https://clerk.com/docs/webhooks/sync-data) for more comprehensive and framework-specific examples that you can copy and paste into your app.\n *\n * ```ts\n * try {\n * const evt = await verifyWebhook(request)\n *\n * // Access the event data\n * const { id } = evt.data\n * const eventType = evt.type\n *\n * // Handle specific event types\n * if (evt.type === 'user.created') {\n * console.log('New user created:', evt.data.id)\n * // Handle user creation\n * }\n *\n * return new Response('Success', { status: 200 })\n * } catch (err) {\n * console.error('Webhook verification failed:', err)\n * return new Response('Webhook verification failed', { status: 400 })\n * }\n * ```\n */\nexport async function verifyWebhook(request: Request, options: VerifyWebhookOptions = {}): Promise<WebhookEvent> {\n const secret = options.signingSecret ?? getEnvVariable('CLERK_WEBHOOK_SIGNING_SECRET');\n\n if (!secret) {\n return errorThrower.throw(\n 'Missing webhook signing secret. Set the CLERK_WEBHOOK_SIGNING_SECRET environment variable with the webhook secret from the Clerk Dashboard.',\n );\n }\n\n // Check for required Svix headers\n const webhookId = request.headers.get(SVIX_ID_HEADER)?.trim();\n const webhookTimestamp = request.headers.get(SVIX_TIMESTAMP_HEADER)?.trim();\n const webhookSignature = request.headers.get(SVIX_SIGNATURE_HEADER)?.trim();\n\n if (!webhookId || !webhookTimestamp || !webhookSignature) {\n const missingHeaders = [];\n\n if (!webhookId) {\n missingHeaders.push(SVIX_ID_HEADER);\n }\n if (!webhookTimestamp) {\n missingHeaders.push(SVIX_TIMESTAMP_HEADER);\n }\n if (!webhookSignature) {\n missingHeaders.push(SVIX_SIGNATURE_HEADER);\n }\n\n return errorThrower.throw(`Missing required webhook headers: ${missingHeaders.join(', ')}`);\n }\n\n const body = await request.text();\n\n // Create Standard Webhooks compatible headers mapping\n const standardHeaders = createStandardWebhookHeaders(request);\n\n // Initialize Standard Webhooks verifier\n const webhook = new Webhook(secret);\n\n try {\n // Verify using Standard Webhooks - this provides constant-time comparison\n // and proper signature format handling\n const payload = webhook.verify(body, standardHeaders) as Record<string, unknown>;\n\n return {\n type: payload.type,\n object: 'event',\n data: payload.data,\n event_attributes: payload.event_attributes,\n } as WebhookEvent;\n } catch (e) {\n return errorThrower.throw(`Unable to verify incoming webhook: ${e instanceof Error ? e.message : 'Unknown error'}`);\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,sBAAsB;AAE/B,SAAS,eAAe;AAexB,IAAM,6BAA6B;AACnC,IAAM,oCAAoC;AAC1C,IAAM,oCAAoC;AAG1C,IAAM,iBAAiB;AACvB,IAAM,wBAAwB;AAC9B,IAAM,wBAAwB;AAO9B,SAAS,6BAA6B,SAA0C;AAC9E,QAAM,UAAkC,CAAC;AAGzC,QAAM,SAAS,QAAQ,QAAQ,IAAI,cAAc,GAAG,KAAK;AACzD,QAAM,gBAAgB,QAAQ,QAAQ,IAAI,qBAAqB,GAAG,KAAK;AACvE,QAAM,gBAAgB,QAAQ,QAAQ,IAAI,qBAAqB,GAAG,KAAK;AAEvE,MAAI,QAAQ;AACV,YAAQ,0BAA0B,IAAI;AAAA,EACxC;AACA,MAAI,eAAe;AACjB,YAAQ,iCAAiC,IAAI;AAAA,EAC/C;AACA,MAAI,eAAe;AACjB,YAAQ,iCAAiC,IAAI;AAAA,EAC/C;AAEA,SAAO;AACT;AAkCA,eAAsB,cAAc,SAAkB,UAAgC,CAAC,GAA0B;AAC/G,QAAM,SAAS,QAAQ,iBAAiB,eAAe,8BAA8B;AAErF,MAAI,CAAC,QAAQ;AACX,WAAO,aAAa;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,QAAQ,QAAQ,IAAI,cAAc,GAAG,KAAK;AAC5D,QAAM,mBAAmB,QAAQ,QAAQ,IAAI,qBAAqB,GAAG,KAAK;AAC1E,QAAM,mBAAmB,QAAQ,QAAQ,IAAI,qBAAqB,GAAG,KAAK;AAE1E,MAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,kBAAkB;AACxD,UAAM,iBAAiB,CAAC;AAExB,QAAI,CAAC,WAAW;AACd,qBAAe,KAAK,cAAc;AAAA,IACpC;AACA,QAAI,CAAC,kBAAkB;AACrB,qBAAe,KAAK,qBAAqB;AAAA,IAC3C;AACA,QAAI,CAAC,kBAAkB;AACrB,qBAAe,KAAK,qBAAqB;AAAA,IAC3C;AAEA,WAAO,aAAa,MAAM,qCAAqC,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,EAC5F;AAEA,QAAM,OAAO,MAAM,QAAQ,KAAK;AAGhC,QAAM,kBAAkB,6BAA6B,OAAO;AAG5D,QAAM,UAAU,IAAI,QAAQ,MAAM;AAElC,MAAI;AAGF,UAAM,UAAU,QAAQ,OAAO,MAAM,eAAe;AAEpD,WAAO;AAAA,MACL,MAAM,QAAQ;AAAA,MACd,QAAQ;AAAA,MACR,MAAM,QAAQ;AAAA,MACd,kBAAkB,QAAQ;AAAA,IAC5B;AAAA,EACF,SAAS,GAAG;AACV,WAAO,aAAa,MAAM,sCAAsC,aAAa,QAAQ,EAAE,UAAU,eAAe,EAAE;AAAA,EACpH;AACF;","names":[]}