Skip to content

Events

The portal package emits domain events whenever important portal actions happen.

Events are one of the main extension points in NinjaPortal. They let your application react to portal changes without editing the package internals. You can use them to build approval workflows, send notifications, write audit logs, trigger provisioning jobs, sync external systems, or customize the developer onboarding experience.

How Events Are Emitted

Most CRUD services in the portal package follow the same flow:

text
Controller or job
  -> Portal service contract
  -> Repository operation
  -> Service hook
  -> Portal domain event
  -> Built-in or custom listener

For example, when a developer is created through UserServiceInterface::create(), the service persists the user, fires UserCreatedEvent, and any registered listener can react to that event.

This is also how the built-in Apigee sync works. The package listens for user changes and syncs the developer with Apigee through SyncUserWithApigeeListener.

Built In Listeners

ListenerPurpose
SyncUserWithApigeeListenerSyncs created or updated portal users with the configured Apigee platform.
SendUserPasswordResetNotificationListenerSends the password reset notification when a reset token is requested.

You can keep these listeners, replace them, or add your own listeners alongside them.

Event Catalog

Users

EventPayloadWhen it is useful
UserCreatedEvent$userWelcome emails, CRM sync, onboarding tasks, Apigee sync.
UserUpdatedEvent$userProfile sync, compliance review, external account updates.
UserDeletedEvent$userCleanup jobs, offboarding, external deactivation.
UserAudiencesSyncedEvent$user, $audienceIdsRecalculate access, notify downstream systems, refresh caches.
UserPasswordResetRequestedEvent$user, $tokenCustom password reset delivery.
UserResetPasswordEvent$userSecurity notifications, session revocation, audit logging.

Admins And RBAC

EventPayloadWhen it is useful
AdminCreatedEvent$adminNotify platform owners, assign onboarding tasks.
AdminUpdatedEvent$adminAudit admin profile changes.
AdminDeletedEvent$adminRevoke external admin access.
AdminRolesSyncedEvent$admin, $roleIdsSecurity audit, permission cache refresh.
RoleCreatedEvent$roleAudit RBAC changes.
RoleUpdatedEvent$roleRefresh policy caches.
RoleDeletedEvent$roleCleanup external access mappings.
RolePermissionsSyncedEvent$role, $permissionIdsRebuild authorization snapshots.
PermissionCreatedEvent$permissionAudit permission creation.
PermissionUpdatedEvent$permissionAudit permission changes.
PermissionDeletedEvent$permissionCleanup dependent workflows.

Catalog

EventPayloadWhen it is useful
ApiProductCreatedEvent$apiProductPublish catalog updates, trigger review workflows.
ApiProductUpdatedEvent$apiProductRefresh search indexes, invalidate product caches.
ApiProductDeletedEvent$apiProductRemove product references from external systems.
ApiProductCategoriesSyncedEvent$apiProduct, $categoryIdsRefresh catalog navigation.
ApiProductAudiencesSyncedEvent$apiProduct, $audienceIdsRecalculate product visibility.
CategoryCreatedEvent$categoryUpdate navigation or search indexes.
CategoryUpdatedEvent$categoryRefresh translated catalog pages.
CategoryDeletedEvent$categoryCleanup catalog filters.
AudienceCreatedEvent$audienceCreate external segment mappings.
AudienceUpdatedEvent$audienceUpdate segment metadata.
AudienceDeletedEvent$audienceRemove external segment mappings.
AudienceUsersSyncedEvent$audience, $userIdsUpdate developer access groups.
AudienceProductsSyncedEvent$audience, $apiProductIdsRecalculate product access.

Developer Apps

EventPayloadWhen it is useful
UserAppCreatedEvent$app, $userEmailStart app approval workflow, notify reviewers.
UserAppUpdatedEvent$userEmail, $appAudit app profile changes.
UserAppDeletedEvent$app, $userEmailCleanup app-specific records.
UserAppApprovedEvent$userEmail, $appNotify developer, provision access.
UserAppRevokedEvent$userEmail, $appNotify developer, suspend related workflows.

Developer App Credentials

EventPayloadWhen it is useful
UserAppCredentialCreatedEvent$email, $appID, $credentialKeyNotify developer or security team.
UserAppCredentialApprovedEvent$email, $appID, $credentialKeyStart credential activation workflow.
UserAppCredentialRevokedEvent$email, $appID, $credentialKeyNotify owner, revoke dependent access.
UserAppCredentialDeletedEvent$email, $appID, $credentialKeyCleanup secrets or external logs.
UserAppCredentialProductAddedEvent$email, $appID, $api_products, $credentialKeyReview new product access.
UserAppCredentialProductRemovedEvent$email, $appID, $api_product, $credentialKeyCleanup product-specific access.
UserAppCredentialProductApprovedEvent$email, $appID, $api_product, $credentialKeyNotify developer that access is approved.
UserAppCredentialProductRevokedEvent$email, $appID, $api_product, $credentialKeyNotify developer that access is revoked.
EventPayloadWhen it is useful
MenuCreatedEvent$menuRebuild navigation cache.
MenuUpdatedEvent$menuRefresh frontend navigation.
MenuDeletedEvent$menuCleanup menu references.
MenuItemCreatedEvent$menuItemRebuild navigation cache.
MenuItemUpdatedEvent$menuItemRefresh localized navigation.
MenuItemDeletedEvent$menuItemCleanup menu item references.
SettingGroupCreatedEvent$settingGroupRefresh admin settings navigation.
SettingGroupUpdatedEvent$settingGroupRefresh settings metadata.
SettingGroupDeletedEvent$settingGroupCleanup settings UI references.
SettingCreatedEvent$settingRefresh runtime configuration.
SettingUpdatedEvent$settingRebuild cached settings.
SettingDeletedEvent$settingRemove cached settings.

Register A Custom Listener

Create a listener in your application:

php
<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use NinjaPortal\Portal\Events\User\UserCreatedEvent;

class SendDeveloperWelcomeEmail implements ShouldQueue
{
    public function handle(UserCreatedEvent $event): void
    {
        $developer = $event->user;

        // Send your notification, create onboarding tasks,
        // or call another internal service.
    }
}

Register it in your application event provider:

php
<?php

namespace App\Providers;

use App\Listeners\SendDeveloperWelcomeEmail;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use NinjaPortal\Portal\Events\User\UserCreatedEvent;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        UserCreatedEvent::class => [
            SendDeveloperWelcomeEmail::class,
        ],
    ];
}

Build A Custom Approval Workflow

The app lifecycle events are useful for approval workflows.

For example, when a developer creates an app, you can create an internal review task:

php
<?php

namespace App\Listeners;

use App\Models\ApprovalRequest;
use NinjaPortal\Portal\Events\UserApp\UserAppCreatedEvent;

class CreateAppApprovalRequest
{
    public function handle(UserAppCreatedEvent $event): void
    {
        ApprovalRequest::query()->create([
            'type' => 'developer_app',
            'subject_email' => $event->userEmail,
            'subject_name' => $event->app->getName(),
            'status' => 'pending',
        ]);
    }
}

Then listen for UserAppApprovedEvent or UserAppRevokedEvent to close the workflow and notify the developer.

Build A Catalog Publishing Workflow

Catalog events are useful when your project has a public catalog, search index, or static cache.

php
<?php

namespace App\Listeners;

use App\Jobs\ReindexApiProduct;
use NinjaPortal\Portal\Events\ApiProduct\ApiProductUpdatedEvent;

class QueueApiProductReindex
{
    public function handle(ApiProductUpdatedEvent $event): void
    {
        ReindexApiProduct::dispatch($event->apiProduct->getKey());
    }
}

You can use the same idea with ApiProductCategoriesSyncedEvent and ApiProductAudiencesSyncedEvent to refresh catalog filters and visibility.

Best Practices

  • Keep listeners small and focused.
  • Queue listeners that call external systems.
  • Prefer listening to events over overriding package services when you only need to react to something.
  • Use services when you need to change the operation itself.
  • Use events when you need to add side effects after the operation succeeds.
  • Avoid putting request-specific assumptions in listeners because the same event can be fired from APIs, jobs, seeders, or admin actions.