Skip to content

Feature: Full webhook event coverage + catch-all activity logging #31

Description

@bordoni

Summary

Replace the hardcoded webhook event allowlist in Receiver.php with a complete, documented event type set that fires do_action() for every event WorkOS emits. Add a filter for custom event types. Subscribe the activity logger to the catch-all webhook action so all events—not just known types—are logged locally. This unblocks observability and event-driven reactions throughout the plugin, with zero behavior change to existing handlers.

Background / current state

Status quo:

  • src/WorkOS/Webhook/Receiver.php lines 100–117 define a hardcoded 16-event allowlist: user.created, user.updated, user.deleted, dsync.user.created, dsync.user.updated, dsync.user.deleted, dsync.group.user_added, dsync.group.user_removed, organization.created, organization.updated, organization_membership.created, organization_membership.updated, organization_membership.deleted, connection.activated, connection.deactivated, authentication.email_verification_succeeded.
  • Events outside this list are silently dropped; no action is fired.
  • Existing subscribers live in src/WorkOS/Sync/UserSync.php (workos_webhook_user.), src/WorkOS/Sync/DirectorySync.php (workos_webhook_dsync.), src/WorkOS/Sync/RoleMapper.php (workos_webhook_organization_membership.updated), and src/WorkOS/Sync/AuditLog.php (WP actions only, no webhooks).
  • src/WorkOS/ActivityLog/Controller.php currently subscribes only to workos_user_authenticated, wp_logout, and wp_login_failed; it does not listen to webhooks.
  • Diagnostics page (src/WorkOS/Admin/DiagnosticsPage.php) has five static checks but no visibility into webhook events.

What's missing:

  • WorkOS now emits ~30+ event types (Sessions, Vault, FGA/RBAC, Magic Auth, Email Verification, Password Reset, Invitation, Organization Domain, Feature Flags, API Keys) that the plugin cannot observe.
  • F2 (Sessions UI) depends on session.created and session.revoked events; F1 (Admin Portal) depends on connection.activated, dsync.activated events for user feedback.
  • No way to add support for custom/future event types without editing the plugin.

Goals & acceptance criteria

  • Webhook receiver fires do_action() for all WorkOS event types in a canonical, alphabetized list.
  • A workos_webhook_event_types filter allows site code to register custom event types.
  • Activity logger (EventLogger) subscribes to the catch-all workos_webhook action and logs every event, regardless of domain.
  • Documentation (docs/webhook-events.md) lists all supported event types and shows three example subscriber stubs.
  • Existing subscribers in Sync/* continue to work unchanged; no event-naming or dispatch regressions.
  • WPUnit test (tests/wpunit/WebhookCoverageTest.php) confirms the filter is applied and custom types fire both specific and generic actions.
  • Optional: Diagnostics page shows "Last 5 webhook events received" with timestamps for support visibility.

Technical design

Files to add

  • docs/webhook-events.md — Canonical event type list, grouped by domain, with example handler stubs.
  • tests/wpunit/WebhookCoverageTest.php — Test filter application, custom type dispatch, and EventLogger subscription to the catch-all.

Files to change

src/WorkOS/Webhook/Receiver.php:

  • Replace the $handlers array (lines 100–117) with a method that defines the default event types as a documented, alphabetized list. Group by domain (Authentication, Connection, DirectorySync, Organization, Session, etc.) for clarity.
  • Apply apply_filters( 'workos_webhook_event_types', $default_event_types ) so site code can extend the list.
  • Check the filtered list to determine whether to fire the specific action.
  • The generic do_action( 'workos_webhook', $event, $event_type ) continues to fire for all events, known or not.

src/WorkOS/ActivityLog/Controller.php:

  • In doRegister(), add add_action( 'workos_webhook', [ $this, 'log_webhook_event' ], 10, 2 ) to subscribe to the catch-all.
  • Implement log_webhook_event( array $event, string $event_type ): void that calls EventLogger::log() with event_type matching the webhook type and metadata capturing the event data.

src/WorkOS/Admin/DiagnosticsPage.php (optional):

  • Add check_recent_webhook_events() to run_all_checks() around line 176.
  • Query the activity log for the last 5 webhook-sourced entries and report "Last 5 webhook events received: [timestamps]" or "No webhook events logged" if the log is empty.

REST endpoints

None. This feature does not add new endpoints; it refines routing of existing webhook POST to /wp-json/workos/v1/webhook.

Data model

No schema changes. Webhook events are logged to the existing wp_workos_activity_log table via EventLogger::log().

Activity log row for a webhook event:

  • event_type: the WorkOS event type (e.g., session.created, authentication.password_reset_succeeded)
  • user_id: the WordPress user ID if present in event data, else 0
  • user_email: the email from the event, else empty
  • workos_user_id: the WorkOS user ID from the event, else empty
  • metadata: JSON blob of the full event data field
  • created_at: timestamp

Hooks & filters

New filter:

  • workos_webhook_event_types — Allows site code or plugins to register custom event types. Receives the default array, returns the filtered array. Example:
    add_filter( 'workos_webhook_event_types', function( $types ) {
        return array_merge( $types, [ 'custom.event_type', 'legacy.deprecated_event' ] );
    } );

Existing actions remain:

  • do_action( "workos_webhook_{$event_type}", $event ) — Fires for each specific event type, known or filtered-custom.
  • do_action( 'workos_webhook', $event, $event_type ) — Catch-all; fires for all events, always.
  • workos_activity_logged — Already fires after EventLogger logs an entry.

WorkOS API

No new API methods. The webhook receiver is driven by inbound POST; it does not initiate requests (except future Diagnostics check, which calls an existing API method if added).

Default event types

Expand from 16 to ~30 types. Group by domain for clarity:

Authentication:

  • authentication.email_verification_succeeded
  • authentication.magic_auth_completed
  • authentication.password_reset_link_invalid
  • authentication.password_reset_succeeded

Connection:

  • connection.activated
  • connection.deactivated

DirectorySync:

  • dsync.activated
  • dsync.deactivated
  • dsync.group.created
  • dsync.group.deleted
  • dsync.group.updated
  • dsync.group.user_added
  • dsync.group.user_removed
  • dsync.user.created
  • dsync.user.deleted
  • dsync.user.updated

Organization:

  • organization.created
  • organization.updated
  • organization_domain.verified
  • organization_domain.verification_failed
  • organization_membership.created
  • organization_membership.deleted
  • organization_membership.updated

Sessions:

  • session.created
  • session.revoked

User:

  • user.created
  • user.deleted
  • user.updated

(Future WorkOS features—Vault, FGA/RBAC, Feature Flags, API Keys, Invitation—can be added here as they ship without a code change; the filter allows custom subscribers to register them immediately.)

Edge cases & security

  • Unknown event types: Events not in the default or filtered list still fire the generic workos_webhook action and are logged. This ensures forward compatibility: new WorkOS event types are captured automatically, even if not yet handled.
  • Custom types via filter: Site code adding custom types is responsible for namespacing them (e.g., my_integration.custom_event) to avoid collisions. No validation; the filter is trusted.
  • Activity log privacy: Webhook events are logged with full event data. If a future event includes sensitive data (e.g., passwords, secrets), site code must use the workos_activity_logged action or a custom EventLogger subscriber to filter/redact metadata before it persists.
  • Rate limiting: No change; webhook ingress is already rate-limited by WordPress REST API nonce/signature checks.
  • Capabilities: The webhook receiver does not check current_user_can(); it is a public endpoint. All auth is via signature verification (existing).
  • No regression for existing handlers: Handlers in Sync/* continue subscribing to specific events they already know. The allowlist removal means they will also receive new event types, but only if they explicitly subscribe. This is safe and additive.

Testing

WPUnit (/slic)

Create tests/wpunit/WebhookCoverageTest.php:

  • Test that the default event types are complete and alphabetized by domain.
  • Test that a known event (e.g., user.created) fires both the specific workos_webhook_user.created and generic workos_webhook actions.
  • Test that the workos_webhook_event_types filter is applied: register a custom type via the filter, fire the event, confirm both actions fire.
  • Test that an unknown event (not in default or filtered list) still fires the generic action (forward compatibility).
  • Test that ActivityLog\Controller::log_webhook_event() logs the event with correct event_type, user_id, user_email, workos_user_id, and metadata.
  • Test that the log entry is retrievable via EventLogger::get_events() with a filter on event_type.
  • Re-run existing WebhookReceiverTest.php and WebhookSignatureTest.php without modification; all pass.

Browser smoke

In a Staging install with webhooks enabled:

  1. Trigger each new event type from the WorkOS dashboard webhook tester (e.g., session.created, organization_domain.verified, etc.).
  2. For each, check that the Activity Log (/wp-admin/admin.php?page=workos-activity-log) shows a row with event_type matching the webhook and metadata intact.
  3. Confirm that existing handlers for user.*, dsync.*, organization_membership.*, connection.* still work (e.g., a dsync.user.created still provisions the WP user).

Diagnostics & activity log

Activity log event types added:

  • All ~30 event types from the default list and any custom types registered via the filter.
  • Existing types (login, logout, login_failed) are unaffected.

Diagnostics check (optional):

  • Add "Last 5 webhook events received" to the checks table in DiagnosticsPage::run_all_checks(). Display timestamps and event types, or "No webhook events logged yet" if the activity log is empty. Helps support confirm webhook delivery without log diving.

Documentation

  • New docs/webhook-events.md with:
    • Table: Event type, description, sample data structure, and associated sync class (if any).
    • Grouped by domain for readability.
    • Example 1: Subscribing to a specific event (e.g., session.created).
    • Example 2: Subscribing to all events via the catch-all.
    • Example 3: Registering a custom event type via the filter.
    • Note: No "breaking changes"; this is purely additive observability.

Out of scope

  • F2 (Sessions UI) — Depends on this feature but lands separately. No session-specific changes here.
  • F1 (Admin Portal) — Depends on this feature but lands separately. No portal-specific changes here.
  • Activity log CSV export, dashboard widgets, or advanced filtering — Future polish.
  • Rate limiting or throttling webhooks — Not needed; WorkOS controls delivery.

Dependencies & sequencing

This feature is the sequencing keystone for 1.1.0. It unblocks:

  • F2 (Sessions UI) — cannot log session.created / session.revoked or display webhook-driven activity without this.
  • F1 (Admin Portal) — cannot react to connection.activated / dsync.activated completion signals without this.

Landing order within 1.1.0:

  1. F3 (this feature) first — smallest scope, no dependencies. Merges in ~1 commit. Unblocks both F2 and F1.
  2. F2 (Sessions UI) second — depends on F3 for event routing. Merges independently.
  3. F1 (Admin Portal) last — smaller scope, no new event dependencies (uses F3 for observability).

No cross-feature blockers within the plugin. F3 is purely additive and backward compatible.


1.1.0 milestone navigation

Recommended landing order — #31#30#29; #28 is independent and may land any time. Each lands as soon as it is green (no monolithic 1.1.0 PR).

Milestone: https://github.com/bordoni/integration-workos/milestone/4

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: webhooksWorkOS webhook receiver & eventsdocumentationImprovements or additions to documentationenhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions