diff --git a/.changeset/sixty-regions-camp.md b/.changeset/sixty-regions-camp.md new file mode 100644 index 00000000000..5058f6d0c62 --- /dev/null +++ b/.changeset/sixty-regions-camp.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': patch +--- + +Add `event_attributes` to the `Webhook` type. diff --git a/packages/backend/src/__tests__/webhooks.test.ts b/packages/backend/src/__tests__/webhooks.test.ts index 7eb2ee15367..b1ce678a927 100644 --- a/packages/backend/src/__tests__/webhooks.test.ts +++ b/packages/backend/src/__tests__/webhooks.test.ts @@ -217,4 +217,35 @@ describe('verifyWebhook', () => { // All should be treated as missing await expect(verifyWebhook(mockRequest)).rejects.toThrow('svix-id, svix-timestamp, svix-signature'); }); + + it('should parse event_attributes', async () => { + const clerkPayload = JSON.stringify({ + type: 'user.created', + data: { id: 'user_123', email: 'test@example.com' }, + event_attributes: { + http_request: { + client_ip: '127.0.0.1', + user_agent: 'Mozilla/5.0 (Test)', + }, + }, + }); + const svixId = 'msg_123'; + const svixTimestamp = (Date.now() / 1000).toString(); + const validSignature = createValidSignature(svixId, svixTimestamp, clerkPayload); + + const mockRequest = new Request('https://clerk.com/webhooks', { + method: 'POST', + body: clerkPayload, + headers: new Headers({ + 'svix-id': svixId, + 'svix-timestamp': svixTimestamp, + 'svix-signature': validSignature, + }), + }); + + const result = await verifyWebhook(mockRequest, { signingSecret: mockSecret }); + expect(result).toHaveProperty('type', 'user.created'); + expect(result).toHaveProperty('event_attributes.http_request.client_ip', '127.0.0.1'); + expect(result).toHaveProperty('event_attributes.http_request.user_agent', 'Mozilla/5.0 (Test)'); + }); }); diff --git a/packages/backend/src/api/resources/Webhooks.ts b/packages/backend/src/api/resources/Webhooks.ts index 5cec4379e3a..aba2cbe33f5 100644 --- a/packages/backend/src/api/resources/Webhooks.ts +++ b/packages/backend/src/api/resources/Webhooks.ts @@ -13,7 +13,14 @@ import type { WaitlistEntryJSON, } from './JSON'; -type Webhook = { type: EvtType; object: 'event'; data: Data }; +type WebhookEventAttributes = { + http_request: { + client_ip: string; + user_agent: string; + }; +}; + +type Webhook = { type: EvtType; object: 'event'; data: Data; event_attributes: WebhookEventAttributes }; export type UserWebhookEvent = | Webhook<'user.created' | 'user.updated', UserJSON> diff --git a/packages/backend/src/webhooks.ts b/packages/backend/src/webhooks.ts index 280bff5b918..90a49b5c82e 100644 --- a/packages/backend/src/webhooks.ts +++ b/packages/backend/src/webhooks.ts @@ -129,6 +129,7 @@ export async function verifyWebhook(request: Request, options: VerifyWebhookOpti type: payload.type, object: 'event', data: payload.data, + event_attributes: payload.event_attributes, } as WebhookEvent; } catch (e) { return errorThrower.throw(`Unable to verify incoming webhook: ${e instanceof Error ? e.message : 'Unknown error'}`);