Skip to content

Event SDK

The MetaOne event system is workspace-scoped and supports both in-process (async) and external (webhook-style) delivery.

Core Types

PlatformEvent

The event record passed to all handlers.

java
public record PlatformEvent(
    String eventKey,              // e.g. "crm.deal.closed"
    String schemaVersion,         // Payload schema version, e.g. "1.0"
    String tenantId,              // Workspace ID (same as workspaceId)
    String requestId,             // Request that triggered this event
    String actor,                 // User or system that published
    Instant occurredAt,           // When the event occurred
    Map<String, Object> payload   // Arbitrary key-value event data
)

EventPayload

A marker interface for typed payload DTOs. Implement it in your payload record and the platform will deserialize the raw Map<String, Object> payload into your type automatically.

java
public interface EventPayload { }

Example typed payload:

java
public record DealClosedPayload(
    String dealId,
    String amount,
    String currency
) implements EventPayload {}

EventBusGateway

Injected into the plugin Spring context by the host. Use it to publish events from within a plugin.

java
public interface EventBusGateway {
    void publish(PlatformEvent event);
}

Annotations

@EventHandler

Marks a method as the handler for a specific event key.

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventHandler {
    String eventKey();        // Full event key, e.g. "crm.deal.closed"
    boolean async() default true;  // Run on dedicated thread pool?
}

Method signature rules:

  • First parameter: PlatformEvent OR any EventPayload subtype
  • Second parameter: CapabilityContext

@EventGroup

Optional class-level grouping annotation for a class containing @EventHandler methods.

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventGroup {
    String description() default "";
}

PlatformEventHandler

Marker interface. Extend it in your event listener class to make it discoverable by the platform (via PF4J).

java
public interface PlatformEventHandler extends ExtensionPoint { }

Writing an Event Handler

java
import org.pf4j.Extension;
import vn.metaone.sdk.event.*;
import vn.metaone.sdk.capability.CapabilityContext;

@Extension
@EventGroup(description = "Handles CRM deal events")
public class DealEventListener implements PlatformEventHandler {

    private final NotificationService notificationService;

    public DealEventListener(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    // Typed payload — platform deserializes Map → DealClosedPayload
    @EventHandler(eventKey = "crm.deal.closed", async = true)
    public void onDealClosed(DealClosedPayload payload, CapabilityContext ctx) {
        notificationService.notify(
            ctx.workspaceId(),
            "Deal " + payload.dealId() + " closed for " + payload.amount()
        );
    }

    // Raw event — access the full PlatformEvent
    @EventHandler(eventKey = "platform.notification", async = false)
    public void onNotification(PlatformEvent event, CapabilityContext ctx) {
        String channel = (String) event.payload().get("channel");
        String message = (String) event.payload().get("message");
        // route to the right conversation...
    }
}

Publishing Events from a Plugin

Inject EventBusGateway via Spring and call publish():

java
@Service
public class DealService {

    private final EventBusGateway eventBus;

    public DealService(EventBusGateway eventBus) {
        this.eventBus = eventBus;
    }

    public void closeDeal(String workspaceId, String dealId, BigDecimal amount) {
        // ... business logic ...

        eventBus.publish(new PlatformEvent(
            "crm.deal.closed",
            "1.0",
            workspaceId,
            UUID.randomUUID().toString(),
            "crm-bot",
            Instant.now(),
            Map.of(
                "dealId",   dealId,
                "amount",   amount.toPlainString(),
                "currency", "USD"
            )
        ));
    }
}

Cross-Extension Notifications

Any extension can post a message to a chat channel — without depending on the chat extension — by publishing a platform.notification event:

java
eventBus.publish(new PlatformEvent(
    "platform.notification",
    "1.0",
    workspaceId,
    requestId,
    "crm-bot",
    Instant.now(),
    Map.of(
        "channel", "deals",                         // Well-known: "general", "deals", or a conversationId
        "message", "Deal #" + dealId + " is closed!",
        "sender",  "crm-bot"
    )
));

Well-known channels:

ChannelDescription
generalGeneral workspace notifications
dealsCRM deal activity feed
{conversationId}A specific conversation

Registering Event Subscriptions

Declare consumed events in the manifest. The platform creates ext_event_subscription rows during install:

java
new CapabilityManifest(
    List.of(...),                           // provides
    List.of("crm.deal.closed",             // consumesEvents — platform will route these here
             "platform.notification"),
    List.of("crm.deal.closed")             // publishesEvents — informational
)

Async vs Sync Handlers

asyncBehavior
true (default)Runs on a dedicated eventExecutor thread pool. Does not block the event publisher. Workspace context is set on the thread before dispatch.
falseRuns inline on the publisher's thread. Blocks until handler completes. Use for lightweight, critical handlers only.

External Service Event Delivery

For EXTERNAL_SERVICE extensions, the platform delivers events as HTTP POST:

POST {serviceBaseUrl}/events
Content-Type: application/json

{
  "eventKey":      "crm.deal.closed",
  "schemaVersion": "1.0",
  "tenantId":      "ws-uuid",
  "requestId":     "req-uuid",
  "actor":         "user-uuid",
  "occurredAt":    "2026-04-13T01:00:00Z",
  "payload": {
    "dealId": "deal-123",
    "amount": "5000.00"
  }
}

Return 200 OK to acknowledge. Failed deliveries are stored in dead_letter_event for replay.

MetaOne Platform Documentation