diff --git a/.oagen-manifest.json b/.oagen-manifest.json new file mode 100644 index 000000000..f8a5578ef --- /dev/null +++ b/.oagen-manifest.json @@ -0,0 +1,27 @@ +{ + "version": 2, + "language": "node", + "generatedAt": "2026-05-19T16:22:01.987Z", + "files": [ + "src/webhooks/fixtures/create-webhook-endpoint.json", + "src/webhooks/fixtures/list-webhook-endpoint.json", + "src/webhooks/fixtures/update-webhook-endpoint.json", + "src/webhooks/fixtures/webhook-endpoint.json", + "src/webhooks/interfaces/create-webhook-endpoint-events.interface.ts", + "src/webhooks/interfaces/create-webhook-endpoint.interface.ts", + "src/webhooks/interfaces/index.ts", + "src/webhooks/interfaces/update-webhook-endpoint-events.interface.ts", + "src/webhooks/interfaces/update-webhook-endpoint-status.interface.ts", + "src/webhooks/interfaces/update-webhook-endpoint.interface.ts", + "src/webhooks/interfaces/webhook-endpoint-status.interface.ts", + "src/webhooks/interfaces/webhook-endpoint.interface.ts", + "src/webhooks/serializers.spec.ts", + "src/webhooks/serializers/create-webhook-endpoint.serializer.ts", + "src/webhooks/serializers/index.ts", + "src/webhooks/serializers/update-webhook-endpoint.serializer.ts", + "src/webhooks/serializers/webhook-endpoint.serializer.ts", + "src/webhooks/webhooks.spec.ts", + "src/webhooks/webhooks.ts" + ], + "operations": {} +} diff --git a/src/index.ts b/src/index.ts index 8733e8a0d..459f54c4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -74,7 +74,7 @@ class WorkOSNode extends WorkOS { /** @override */ createWebhookClient(): Webhooks { - return new Webhooks(this.getCryptoProvider()); + return new Webhooks(this); } override getCryptoProvider(): CryptoProvider { diff --git a/src/index.worker.ts b/src/index.worker.ts index 5ac0d6094..08f482cf5 100644 --- a/src/index.worker.ts +++ b/src/index.worker.ts @@ -62,9 +62,7 @@ class WorkOSWorker extends WorkOS { /** @override */ createWebhookClient(): Webhooks { - const cryptoProvider = new SubtleCryptoProvider(); - - return new Webhooks(cryptoProvider); + return new Webhooks(this); } override getCryptoProvider(): CryptoProvider { diff --git a/src/webhooks/fixtures/create-webhook-endpoint.json b/src/webhooks/fixtures/create-webhook-endpoint.json new file mode 100644 index 000000000..81a07af09 --- /dev/null +++ b/src/webhooks/fixtures/create-webhook-endpoint.json @@ -0,0 +1,4 @@ +{ + "endpoint_url": "https://example.com/webhooks", + "events": ["user.created", "dsync.user.created"] +} diff --git a/src/webhooks/fixtures/list-webhook-endpoint.json b/src/webhooks/fixtures/list-webhook-endpoint.json new file mode 100644 index 000000000..e4587c5c1 --- /dev/null +++ b/src/webhooks/fixtures/list-webhook-endpoint.json @@ -0,0 +1,18 @@ +{ + "data": [ + { + "object": "webhook_endpoint", + "id": "we_0123456789", + "endpoint_url": "https://example.com/webhooks", + "secret": "whsec_0FWAiVGkEfGBqqsJH4aNAGBJ4", + "status": "enabled", + "events": ["user.created", "dsync.user.created"], + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/src/webhooks/fixtures/update-webhook-endpoint.json b/src/webhooks/fixtures/update-webhook-endpoint.json new file mode 100644 index 000000000..e73642781 --- /dev/null +++ b/src/webhooks/fixtures/update-webhook-endpoint.json @@ -0,0 +1,5 @@ +{ + "endpoint_url": "https://example.com/webhooks", + "status": "enabled", + "events": ["user.created", "dsync.user.created"] +} diff --git a/src/webhooks/fixtures/webhook-endpoint.json b/src/webhooks/fixtures/webhook-endpoint.json new file mode 100644 index 000000000..592c7cd23 --- /dev/null +++ b/src/webhooks/fixtures/webhook-endpoint.json @@ -0,0 +1,10 @@ +{ + "object": "webhook_endpoint", + "id": "we_0123456789", + "endpoint_url": "https://example.com/webhooks", + "secret": "whsec_0FWAiVGkEfGBqqsJH4aNAGBJ4", + "status": "enabled", + "events": ["user.created", "dsync.user.created"], + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/src/webhooks/interfaces/create-webhook-endpoint-events.interface.ts b/src/webhooks/interfaces/create-webhook-endpoint-events.interface.ts new file mode 100644 index 000000000..1127d7544 --- /dev/null +++ b/src/webhooks/interfaces/create-webhook-endpoint-events.interface.ts @@ -0,0 +1,87 @@ +// This file is auto-generated by oagen. Do not edit. + +export const CreateWebhookEndpointEvents = { + AuthenticationEmailVerificationSucceeded: + 'authentication.email_verification_succeeded', + AuthenticationMagicAuthFailed: 'authentication.magic_auth_failed', + AuthenticationMagicAuthSucceeded: 'authentication.magic_auth_succeeded', + AuthenticationMfaSucceeded: 'authentication.mfa_succeeded', + AuthenticationOAuthFailed: 'authentication.oauth_failed', + AuthenticationOAuthSucceeded: 'authentication.oauth_succeeded', + AuthenticationPasswordFailed: 'authentication.password_failed', + AuthenticationPasswordSucceeded: 'authentication.password_succeeded', + AuthenticationPasskeyFailed: 'authentication.passkey_failed', + AuthenticationPasskeySucceeded: 'authentication.passkey_succeeded', + AuthenticationSSOFailed: 'authentication.sso_failed', + AuthenticationSSOStarted: 'authentication.sso_started', + AuthenticationSSOSucceeded: 'authentication.sso_succeeded', + AuthenticationSSOTimedOut: 'authentication.sso_timed_out', + AuthenticationRadarRiskDetected: 'authentication.radar_risk_detected', + ApiKeyCreated: 'api_key.created', + ApiKeyRevoked: 'api_key.revoked', + ConnectionActivated: 'connection.activated', + ConnectionDeactivated: 'connection.deactivated', + ConnectionSAMLCertificateRenewalRequired: + 'connection.saml_certificate_renewal_required', + ConnectionSAMLCertificateRenewed: 'connection.saml_certificate_renewed', + ConnectionDeleted: 'connection.deleted', + DsyncActivated: 'dsync.activated', + DsyncDeleted: 'dsync.deleted', + DsyncGroupCreated: 'dsync.group.created', + DsyncGroupDeleted: 'dsync.group.deleted', + DsyncGroupUpdated: 'dsync.group.updated', + DsyncGroupUserAdded: 'dsync.group.user_added', + DsyncGroupUserRemoved: 'dsync.group.user_removed', + DsyncUserCreated: 'dsync.user.created', + DsyncUserDeleted: 'dsync.user.deleted', + DsyncUserUpdated: 'dsync.user.updated', + EmailVerificationCreated: 'email_verification.created', + GroupCreated: 'group.created', + GroupDeleted: 'group.deleted', + GroupMemberAdded: 'group.member_added', + GroupMemberRemoved: 'group.member_removed', + GroupUpdated: 'group.updated', + FlagCreated: 'flag.created', + FlagDeleted: 'flag.deleted', + FlagUpdated: 'flag.updated', + FlagRuleUpdated: 'flag.rule_updated', + InvitationAccepted: 'invitation.accepted', + InvitationCreated: 'invitation.created', + InvitationResent: 'invitation.resent', + InvitationRevoked: 'invitation.revoked', + MagicAuthCreated: 'magic_auth.created', + OrganizationCreated: 'organization.created', + OrganizationDeleted: 'organization.deleted', + OrganizationUpdated: 'organization.updated', + OrganizationDomainCreated: 'organization_domain.created', + OrganizationDomainDeleted: 'organization_domain.deleted', + OrganizationDomainUpdated: 'organization_domain.updated', + OrganizationDomainVerified: 'organization_domain.verified', + OrganizationDomainVerificationFailed: + 'organization_domain.verification_failed', + PasswordResetCreated: 'password_reset.created', + PasswordResetSucceeded: 'password_reset.succeeded', + UserCreated: 'user.created', + UserUpdated: 'user.updated', + UserDeleted: 'user.deleted', + OrganizationMembershipCreated: 'organization_membership.created', + OrganizationMembershipDeleted: 'organization_membership.deleted', + OrganizationMembershipUpdated: 'organization_membership.updated', + RoleCreated: 'role.created', + RoleDeleted: 'role.deleted', + RoleUpdated: 'role.updated', + OrganizationRoleCreated: 'organization_role.created', + OrganizationRoleDeleted: 'organization_role.deleted', + OrganizationRoleUpdated: 'organization_role.updated', + PermissionCreated: 'permission.created', + PermissionDeleted: 'permission.deleted', + PermissionUpdated: 'permission.updated', + SessionCreated: 'session.created', + SessionRevoked: 'session.revoked', + WaitlistUserApproved: 'waitlist_user.approved', + WaitlistUserCreated: 'waitlist_user.created', + WaitlistUserDenied: 'waitlist_user.denied', +} as const; + +export type CreateWebhookEndpointEvents = + (typeof CreateWebhookEndpointEvents)[keyof typeof CreateWebhookEndpointEvents]; diff --git a/src/webhooks/interfaces/create-webhook-endpoint.interface.ts b/src/webhooks/interfaces/create-webhook-endpoint.interface.ts new file mode 100644 index 000000000..0f8cb0742 --- /dev/null +++ b/src/webhooks/interfaces/create-webhook-endpoint.interface.ts @@ -0,0 +1,15 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { CreateWebhookEndpointEvents } from './create-webhook-endpoint-events.interface'; + +export interface CreateWebhookEndpoint { + /** The HTTPS URL where webhooks will be sent. */ + endpointUrl: string; + /** The events that the Webhook Endpoint is subscribed to. */ + events: CreateWebhookEndpointEvents[]; +} + +export interface CreateWebhookEndpointResponse { + endpoint_url: string; + events: CreateWebhookEndpointEvents[]; +} diff --git a/src/webhooks/interfaces/index.ts b/src/webhooks/interfaces/index.ts new file mode 100644 index 000000000..34216c628 --- /dev/null +++ b/src/webhooks/interfaces/index.ts @@ -0,0 +1,9 @@ +// This file is auto-generated by oagen. Do not edit. + +export * from './create-webhook-endpoint-events.interface'; +export * from './create-webhook-endpoint.interface'; +export * from './update-webhook-endpoint-events.interface'; +export * from './update-webhook-endpoint-status.interface'; +export * from './update-webhook-endpoint.interface'; +export * from './webhook-endpoint-status.interface'; +export * from './webhook-endpoint.interface'; diff --git a/src/webhooks/interfaces/update-webhook-endpoint-events.interface.ts b/src/webhooks/interfaces/update-webhook-endpoint-events.interface.ts new file mode 100644 index 000000000..743bd0c69 --- /dev/null +++ b/src/webhooks/interfaces/update-webhook-endpoint-events.interface.ts @@ -0,0 +1,87 @@ +// This file is auto-generated by oagen. Do not edit. + +export const UpdateWebhookEndpointEvents = { + AuthenticationEmailVerificationSucceeded: + 'authentication.email_verification_succeeded', + AuthenticationMagicAuthFailed: 'authentication.magic_auth_failed', + AuthenticationMagicAuthSucceeded: 'authentication.magic_auth_succeeded', + AuthenticationMfaSucceeded: 'authentication.mfa_succeeded', + AuthenticationOAuthFailed: 'authentication.oauth_failed', + AuthenticationOAuthSucceeded: 'authentication.oauth_succeeded', + AuthenticationPasswordFailed: 'authentication.password_failed', + AuthenticationPasswordSucceeded: 'authentication.password_succeeded', + AuthenticationPasskeyFailed: 'authentication.passkey_failed', + AuthenticationPasskeySucceeded: 'authentication.passkey_succeeded', + AuthenticationSSOFailed: 'authentication.sso_failed', + AuthenticationSSOStarted: 'authentication.sso_started', + AuthenticationSSOSucceeded: 'authentication.sso_succeeded', + AuthenticationSSOTimedOut: 'authentication.sso_timed_out', + AuthenticationRadarRiskDetected: 'authentication.radar_risk_detected', + ApiKeyCreated: 'api_key.created', + ApiKeyRevoked: 'api_key.revoked', + ConnectionActivated: 'connection.activated', + ConnectionDeactivated: 'connection.deactivated', + ConnectionSAMLCertificateRenewalRequired: + 'connection.saml_certificate_renewal_required', + ConnectionSAMLCertificateRenewed: 'connection.saml_certificate_renewed', + ConnectionDeleted: 'connection.deleted', + DsyncActivated: 'dsync.activated', + DsyncDeleted: 'dsync.deleted', + DsyncGroupCreated: 'dsync.group.created', + DsyncGroupDeleted: 'dsync.group.deleted', + DsyncGroupUpdated: 'dsync.group.updated', + DsyncGroupUserAdded: 'dsync.group.user_added', + DsyncGroupUserRemoved: 'dsync.group.user_removed', + DsyncUserCreated: 'dsync.user.created', + DsyncUserDeleted: 'dsync.user.deleted', + DsyncUserUpdated: 'dsync.user.updated', + EmailVerificationCreated: 'email_verification.created', + GroupCreated: 'group.created', + GroupDeleted: 'group.deleted', + GroupMemberAdded: 'group.member_added', + GroupMemberRemoved: 'group.member_removed', + GroupUpdated: 'group.updated', + FlagCreated: 'flag.created', + FlagDeleted: 'flag.deleted', + FlagUpdated: 'flag.updated', + FlagRuleUpdated: 'flag.rule_updated', + InvitationAccepted: 'invitation.accepted', + InvitationCreated: 'invitation.created', + InvitationResent: 'invitation.resent', + InvitationRevoked: 'invitation.revoked', + MagicAuthCreated: 'magic_auth.created', + OrganizationCreated: 'organization.created', + OrganizationDeleted: 'organization.deleted', + OrganizationUpdated: 'organization.updated', + OrganizationDomainCreated: 'organization_domain.created', + OrganizationDomainDeleted: 'organization_domain.deleted', + OrganizationDomainUpdated: 'organization_domain.updated', + OrganizationDomainVerified: 'organization_domain.verified', + OrganizationDomainVerificationFailed: + 'organization_domain.verification_failed', + PasswordResetCreated: 'password_reset.created', + PasswordResetSucceeded: 'password_reset.succeeded', + UserCreated: 'user.created', + UserUpdated: 'user.updated', + UserDeleted: 'user.deleted', + OrganizationMembershipCreated: 'organization_membership.created', + OrganizationMembershipDeleted: 'organization_membership.deleted', + OrganizationMembershipUpdated: 'organization_membership.updated', + RoleCreated: 'role.created', + RoleDeleted: 'role.deleted', + RoleUpdated: 'role.updated', + OrganizationRoleCreated: 'organization_role.created', + OrganizationRoleDeleted: 'organization_role.deleted', + OrganizationRoleUpdated: 'organization_role.updated', + PermissionCreated: 'permission.created', + PermissionDeleted: 'permission.deleted', + PermissionUpdated: 'permission.updated', + SessionCreated: 'session.created', + SessionRevoked: 'session.revoked', + WaitlistUserApproved: 'waitlist_user.approved', + WaitlistUserCreated: 'waitlist_user.created', + WaitlistUserDenied: 'waitlist_user.denied', +} as const; + +export type UpdateWebhookEndpointEvents = + (typeof UpdateWebhookEndpointEvents)[keyof typeof UpdateWebhookEndpointEvents]; diff --git a/src/webhooks/interfaces/update-webhook-endpoint-status.interface.ts b/src/webhooks/interfaces/update-webhook-endpoint-status.interface.ts new file mode 100644 index 000000000..0b9a26e7e --- /dev/null +++ b/src/webhooks/interfaces/update-webhook-endpoint-status.interface.ts @@ -0,0 +1,9 @@ +// This file is auto-generated by oagen. Do not edit. + +export const UpdateWebhookEndpointStatus = { + Enabled: 'enabled', + Disabled: 'disabled', +} as const; + +export type UpdateWebhookEndpointStatus = + (typeof UpdateWebhookEndpointStatus)[keyof typeof UpdateWebhookEndpointStatus]; diff --git a/src/webhooks/interfaces/update-webhook-endpoint.interface.ts b/src/webhooks/interfaces/update-webhook-endpoint.interface.ts new file mode 100644 index 000000000..7702709cb --- /dev/null +++ b/src/webhooks/interfaces/update-webhook-endpoint.interface.ts @@ -0,0 +1,19 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { UpdateWebhookEndpointStatus } from './update-webhook-endpoint-status.interface'; +import type { UpdateWebhookEndpointEvents } from './update-webhook-endpoint-events.interface'; + +export interface UpdateWebhookEndpoint { + /** The HTTPS URL where webhooks will be sent. */ + endpointUrl?: string; + /** Whether the Webhook Endpoint is enabled or disabled. */ + status?: UpdateWebhookEndpointStatus; + /** The events that the Webhook Endpoint is subscribed to. */ + events?: UpdateWebhookEndpointEvents[]; +} + +export interface UpdateWebhookEndpointResponse { + endpoint_url?: string; + status?: UpdateWebhookEndpointStatus; + events?: UpdateWebhookEndpointEvents[]; +} diff --git a/src/webhooks/interfaces/webhook-endpoint-status.interface.ts b/src/webhooks/interfaces/webhook-endpoint-status.interface.ts new file mode 100644 index 000000000..4f974bc15 --- /dev/null +++ b/src/webhooks/interfaces/webhook-endpoint-status.interface.ts @@ -0,0 +1,9 @@ +// This file is auto-generated by oagen. Do not edit. + +export const WebhookEndpointStatus = { + Enabled: 'enabled', + Disabled: 'disabled', +} as const; + +export type WebhookEndpointStatus = + (typeof WebhookEndpointStatus)[keyof typeof WebhookEndpointStatus]; diff --git a/src/webhooks/interfaces/webhook-endpoint.interface.ts b/src/webhooks/interfaces/webhook-endpoint.interface.ts new file mode 100644 index 000000000..d5e661028 --- /dev/null +++ b/src/webhooks/interfaces/webhook-endpoint.interface.ts @@ -0,0 +1,33 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WebhookEndpointStatus } from './webhook-endpoint-status.interface'; + +export interface WebhookEndpoint { + /** Distinguishes the Webhook Endpoint object. */ + object: 'webhook_endpoint'; + /** Unique identifier of the Webhook Endpoint. */ + id: string; + /** The URL to which webhooks are sent. */ + endpointUrl: string; + /** The secret used to sign webhook payloads. */ + secret: string; + /** Whether the Webhook Endpoint is enabled or disabled. */ + status: WebhookEndpointStatus; + /** The events that the Webhook Endpoint is subscribed to. */ + events: string[]; + /** An ISO 8601 timestamp. */ + createdAt: Date; + /** An ISO 8601 timestamp. */ + updatedAt: Date; +} + +export interface WebhookEndpointResponse { + object: 'webhook_endpoint'; + id: string; + endpoint_url: string; + secret: string; + status: WebhookEndpointStatus; + events: string[]; + created_at: string; + updated_at: string; +} diff --git a/src/webhooks/serializers.spec.ts b/src/webhooks/serializers.spec.ts new file mode 100644 index 000000000..2daca5f9f --- /dev/null +++ b/src/webhooks/serializers.spec.ts @@ -0,0 +1,37 @@ +// This file is auto-generated by oagen. Do not edit. + +import { serializeCreateWebhookEndpoint } from './serializers/create-webhook-endpoint.serializer'; +import { serializeUpdateWebhookEndpoint } from './serializers/update-webhook-endpoint.serializer'; +import { deserializeWebhookEndpoint } from './serializers/webhook-endpoint.serializer'; +import type { CreateWebhookEndpointResponse } from './interfaces/create-webhook-endpoint.interface'; +import type { UpdateWebhookEndpointResponse } from './interfaces/update-webhook-endpoint.interface'; +import type { WebhookEndpointResponse } from './interfaces/webhook-endpoint.interface'; +import createWebhookEndpointFixture from './fixtures/create-webhook-endpoint.json'; +import updateWebhookEndpointFixture from './fixtures/update-webhook-endpoint.json'; +import webhookEndpointFixture from './fixtures/webhook-endpoint.json'; + +describe('CreateWebhookEndpointSerializer', () => { + it('serializes correctly', () => { + const fixture = + createWebhookEndpointFixture as CreateWebhookEndpointResponse; + const serialized = serializeCreateWebhookEndpoint(fixture as any); + expect(serialized).toBeDefined(); + }); +}); + +describe('UpdateWebhookEndpointSerializer', () => { + it('serializes correctly', () => { + const fixture = + updateWebhookEndpointFixture as UpdateWebhookEndpointResponse; + const serialized = serializeUpdateWebhookEndpoint(fixture as any); + expect(serialized).toBeDefined(); + }); +}); + +describe('WebhookEndpointSerializer', () => { + it('deserializes correctly', () => { + const fixture = webhookEndpointFixture as WebhookEndpointResponse; + const deserialized = deserializeWebhookEndpoint(fixture); + expect(deserialized).toBeDefined(); + }); +}); diff --git a/src/webhooks/serializers/create-webhook-endpoint.serializer.ts b/src/webhooks/serializers/create-webhook-endpoint.serializer.ts new file mode 100644 index 000000000..791d5af91 --- /dev/null +++ b/src/webhooks/serializers/create-webhook-endpoint.serializer.ts @@ -0,0 +1,13 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + CreateWebhookEndpoint, + CreateWebhookEndpointResponse, +} from '../interfaces/create-webhook-endpoint.interface'; + +export const serializeCreateWebhookEndpoint = ( + model: CreateWebhookEndpoint, +): CreateWebhookEndpointResponse => ({ + endpoint_url: model.endpointUrl, + events: model.events, +}); diff --git a/src/webhooks/serializers/index.ts b/src/webhooks/serializers/index.ts new file mode 100644 index 000000000..a9c11ea91 --- /dev/null +++ b/src/webhooks/serializers/index.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by oagen. Do not edit. + +export * from './create-webhook-endpoint.serializer'; +export * from './update-webhook-endpoint.serializer'; +export * from './webhook-endpoint.serializer'; diff --git a/src/webhooks/serializers/update-webhook-endpoint.serializer.ts b/src/webhooks/serializers/update-webhook-endpoint.serializer.ts new file mode 100644 index 000000000..1feaa9448 --- /dev/null +++ b/src/webhooks/serializers/update-webhook-endpoint.serializer.ts @@ -0,0 +1,14 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UpdateWebhookEndpoint, + UpdateWebhookEndpointResponse, +} from '../interfaces/update-webhook-endpoint.interface'; + +export const serializeUpdateWebhookEndpoint = ( + model: UpdateWebhookEndpoint, +): UpdateWebhookEndpointResponse => ({ + endpoint_url: model.endpointUrl, + status: model.status, + events: model.events, +}); diff --git a/src/webhooks/serializers/webhook-endpoint.serializer.ts b/src/webhooks/serializers/webhook-endpoint.serializer.ts new file mode 100644 index 000000000..d3d834b9b --- /dev/null +++ b/src/webhooks/serializers/webhook-endpoint.serializer.ts @@ -0,0 +1,32 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + WebhookEndpoint, + WebhookEndpointResponse, +} from '../interfaces/webhook-endpoint.interface'; + +export const deserializeWebhookEndpoint = ( + response: WebhookEndpointResponse, +): WebhookEndpoint => ({ + object: response.object, + id: response.id, + endpointUrl: response.endpoint_url, + secret: response.secret, + status: response.status, + events: response.events, + createdAt: new Date(response.created_at), + updatedAt: new Date(response.updated_at), +}); + +export const serializeWebhookEndpoint = ( + model: WebhookEndpoint, +): WebhookEndpointResponse => ({ + object: model.object, + id: model.id, + endpoint_url: model.endpointUrl, + secret: model.secret, + status: model.status, + events: model.events, + created_at: model.createdAt.toISOString(), + updated_at: model.updatedAt.toISOString(), +}); diff --git a/src/webhooks/webhooks.spec.ts b/src/webhooks/webhooks.spec.ts index 651762199..6182f876a 100644 --- a/src/webhooks/webhooks.spec.ts +++ b/src/webhooks/webhooks.spec.ts @@ -1,10 +1,114 @@ -import crypto from 'crypto'; +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchSearchParams, + fetchBody, +} from '../common/utils/test-utils'; import { WorkOS } from '../workos'; + +import listWebhookEndpointFixture from './fixtures/list-webhook-endpoint.json'; +import webhookEndpointFixture from './fixtures/webhook-endpoint.json'; +import crypto from 'crypto'; import mockWebhook from './fixtures/webhook.json'; -const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); import { SignatureVerificationException } from '../common/exceptions'; +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +function expectWebhookEndpoint(result: any) { + expect(result.object).toBe('webhook_endpoint'); + expect(result.id).toBe('we_0123456789'); + expect(result.endpointUrl).toBe('https://example.com/webhooks'); + expect(result.secret).toBe('whsec_0FWAiVGkEfGBqqsJH4aNAGBJ4'); + expect(result.status).toBe('enabled'); + expect(result.events).toEqual(['user.created', 'dsync.user.created']); + expect(result.createdAt.toISOString()).toBe('2026-01-15T12:00:00.000Z'); + expect(result.updatedAt.toISOString()).toBe('2026-01-15T12:00:00.000Z'); +} + describe('Webhooks', () => { + beforeEach(() => fetch.resetMocks()); + + describe('listWebhookEndpoints', () => { + it('returns paginated results', async () => { + fetchOnce(listWebhookEndpointFixture); + + const { data, listMetadata } = + await workos.webhooks.listWebhookEndpoints(); + + expect(fetchMethod()).toBe('GET'); + expect(new URL(String(fetchURL())).pathname).toBe('/webhook_endpoints'); + expect(fetchSearchParams()).toHaveProperty('order'); + expect(Array.isArray(data)).toBe(true); + expect(listMetadata).toBeDefined(); + expect(data.length).toBeGreaterThan(0); + expectWebhookEndpoint(data[0]); + }); + }); + + describe('createWebhookEndpoint', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(webhookEndpointFixture); + + const result = await workos.webhooks.createWebhookEndpoint({ + endpointUrl: 'https://example.com', + events: ['authentication.email_verification_succeeded'], + }); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe('/webhook_endpoints'); + expect(fetchBody()).toEqual( + expect.objectContaining({ + endpoint_url: 'https://example.com', + events: ['authentication.email_verification_succeeded'], + }), + ); + expectWebhookEndpoint(result); + }); + }); + + describe('updateWebhookEndpoint', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(webhookEndpointFixture); + + const result = await workos.webhooks.updateWebhookEndpoint('test_id', { + endpointUrl: 'https://example.com', + status: 'enabled', + }); + + expect(fetchMethod()).toBe('PATCH'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/webhook_endpoints/test_id', + ); + expect(fetchBody()).toEqual( + expect.objectContaining({ + endpoint_url: 'https://example.com', + status: 'enabled', + }), + ); + expectWebhookEndpoint(result); + }); + }); + + describe('deleteWebhookEndpoint', () => { + it('sends a DELETE request', async () => { + fetchOnce({}, { status: 204 }); + + await workos.webhooks.deleteWebhookEndpoint('test_id'); + + expect(fetchMethod()).toBe('DELETE'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/webhook_endpoints/test_id', + ); + }); + }); +}); + +// @oagen-ignore-start +describe('Webhook signatures', () => { let payload: any; let secret: string; let timestamp: number; @@ -371,3 +475,4 @@ describe('Webhooks', () => { }); }); }); +// @oagen-ignore-end diff --git a/src/webhooks/webhooks.ts b/src/webhooks/webhooks.ts index 2fc33b77b..e530ccce1 100644 --- a/src/webhooks/webhooks.ts +++ b/src/webhooks/webhooks.ts @@ -1,28 +1,131 @@ -// @oagen-ignore-file +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { PaginationOptions } from '../common/interfaces/pagination-options.interface'; +import { AutoPaginatable } from '../common/utils/pagination'; +import { fetchAndDeserialize } from '../common/utils/fetch-and-deserialize'; +import type { + WebhookEndpoint, + WebhookEndpointResponse, +} from './interfaces/webhook-endpoint.interface'; +import type { + CreateWebhookEndpoint, + CreateWebhookEndpointResponse, +} from './interfaces/create-webhook-endpoint.interface'; +import type { + UpdateWebhookEndpoint, + UpdateWebhookEndpointResponse, +} from './interfaces/update-webhook-endpoint.interface'; +import { deserializeWebhookEndpoint } from './serializers/webhook-endpoint.serializer'; +import { serializeCreateWebhookEndpoint } from './serializers/create-webhook-endpoint.serializer'; +import { serializeUpdateWebhookEndpoint } from './serializers/update-webhook-endpoint.serializer'; import { deserializeEvent } from '../common/serializers'; import { Event, EventResponse } from '../common/interfaces'; import { SignatureProvider } from '../common/crypto/signature-provider'; -import { CryptoProvider } from '../common/crypto/crypto-provider'; import { type WebhookPayload, decodePayloadToString, isBinaryPayload, } from '../common/crypto/decode-payload'; -// Parse only after verification succeeds — a malformed body never reaches -// JSON.parse on an unauthenticated request. -function parseVerifiedPayload(payload: WebhookPayload): EventResponse { - if (typeof payload === 'object' && !isBinaryPayload(payload)) { - return payload as unknown as EventResponse; +export class Webhooks { + constructor(private readonly workos: WorkOS) {} + + /** + * List Webhook Endpoints + * + * Get a list of all of your existing webhook endpoints. + * @param options - Pagination and filter options. + * @returns {Promise>} + */ + async listWebhookEndpoints( + options?: PaginationOptions, + ): Promise> { + return new AutoPaginatable( + await fetchAndDeserialize( + this.workos, + '/webhook_endpoints', + deserializeWebhookEndpoint, + options, + ), + (params) => + fetchAndDeserialize( + this.workos, + '/webhook_endpoints', + deserializeWebhookEndpoint, + params, + ), + options, + ); } - return JSON.parse(decodePayloadToString(payload)) as EventResponse; -} -export class Webhooks { - private signatureProvider: SignatureProvider; + /** + * Create a Webhook Endpoint + * + * Create a new webhook endpoint to receive event notifications. + * @param payload - Object containing endpointUrl, events. + * @returns {Promise} + * @throws {ConflictException} 409 + * @throws {UnprocessableEntityException} 422 + */ + async createWebhookEndpoint( + payload: CreateWebhookEndpoint, + ): Promise { + const { data } = await this.workos.post< + WebhookEndpointResponse, + CreateWebhookEndpointResponse + >('/webhook_endpoints', serializeCreateWebhookEndpoint(payload)); + return deserializeWebhookEndpoint(data); + } - constructor(cryptoProvider: CryptoProvider) { - this.signatureProvider = new SignatureProvider(cryptoProvider); + /** + * Update a Webhook Endpoint + * + * Update the properties of an existing webhook endpoint. + * @param id - Unique identifier of the Webhook Endpoint. + * @example "we_0123456789" + * @param payload - The request body. + * @returns {Promise} + * @throws {NotFoundException} 404 + * @throws {ConflictException} 409 + * @throws {UnprocessableEntityException} 422 + */ + async updateWebhookEndpoint( + id: string, + payload: UpdateWebhookEndpoint, + ): Promise { + const { data } = await this.workos.patch< + WebhookEndpointResponse, + UpdateWebhookEndpointResponse + >( + `/webhook_endpoints/${encodeURIComponent(id)}`, + serializeUpdateWebhookEndpoint(payload), + ); + return deserializeWebhookEndpoint(data); + } + + /** + * Delete a Webhook Endpoint + * + * Delete an existing webhook endpoint. + * @param id - Unique identifier of the Webhook Endpoint. + * @example "we_0123456789" + * @returns {Promise} + * @throws {NotFoundException} 404 + */ + async deleteWebhookEndpoint(id: string): Promise { + await this.workos.delete(`/webhook_endpoints/${encodeURIComponent(id)}`); + } + + // @oagen-ignore-start + private _signatureProvider?: SignatureProvider; + private get signatureProvider(): SignatureProvider { + if (!this._signatureProvider) { + this._signatureProvider = new SignatureProvider( + this.workos.getCryptoProvider(), + ); + } + return this._signatureProvider; } get verifyHeader() { @@ -53,8 +156,17 @@ export class Webhooks { const options = { payload, sigHeader, secret, tolerance }; await this.verifyHeader(options); - const webhookPayload = parseVerifiedPayload(payload); + const webhookPayload = this.parseVerifiedPayload(payload); return deserializeEvent(webhookPayload); } + // Parse only after verification succeeds — a malformed body never reaches + // JSON.parse on an unauthenticated request. + private parseVerifiedPayload(payload: WebhookPayload): EventResponse { + if (typeof payload === 'object' && !isBinaryPayload(payload)) { + return payload as unknown as EventResponse; + } + return JSON.parse(decodePayloadToString(payload)) as EventResponse; + } + // @oagen-ignore-end } diff --git a/src/workos.ts b/src/workos.ts index d6506a523..4378b4f07 100644 --- a/src/workos.ts +++ b/src/workos.ts @@ -167,7 +167,7 @@ export class WorkOS { } createWebhookClient() { - return new Webhooks(this.getCryptoProvider()); + return new Webhooks(this); } createActionsClient() {