Identica
API

Events

How to listen to, react to, and reason about Identica events.

Identica has its own event system for addon plugins, integrations, and internal components.

Basic listener pattern

To consume Identica events from an addon plugin:

  • implement EventListener
  • annotate handler methods with @IdenticEvent
  • register the listener through identica.getEventManager()

Listener example

import me.whereareiam.identica.IdenticaAPI;
import me.whereareiam.identica.event.EventListener;
import me.whereareiam.identica.event.base.IdenticEvent;
import me.whereareiam.identica.event.scenario.registration.RegistrationRequiredEvent;

import java.time.Instant;

public final class ExampleRegistrationListener implements EventListener {
    @IdenticEvent
    public void onRegistrationRequired(RegistrationRequiredEvent event) {
        System.out.println(
            "Registration is now required until " + Instant.ofEpochMilli(event.getExpiresAt())
        );
    }

    public void register() {
        IdenticaAPI.whenReady(identica -> identica.getEventManager().register(this));
    }
}

Event order

If you need a specific execution order, pass it to @IdenticEvent.

import me.whereareiam.identica.event.EventListener;
import me.whereareiam.identica.event.base.IdenticEvent;
import me.whereareiam.identica.event.session.SessionOpenedEvent;
import me.whereareiam.identica.type.event.EventOrder;

public final class ExampleSessionListener implements EventListener {
    @IdenticEvent(EventOrder.LOW)
    public void onSessionOpened(SessionOpenedEvent event) {
        System.out.println("Opened session for: " + event.getSession().getEffectiveUsername());
    }
}

Startup and shutdown

Identica starts in phases, each one after the previous has finished: the configuration, the log, the stores (the database and replication), the core subsystems, the features, the providers, the integrations such as usage statistics, and finally player connections. Three lifecycle events mark that sequence:

  • IdenticaBootstrappedEvent is called once the features have started, before the providers are loaded.
  • IdenticaReadyEvent is called at the end of startup. The providers are loaded with their commands and documents, Identica handles player connections, and the actions given to IdenticaAPI.whenReady(...) have run.
  • IdenticaShutdownEvent is called when Identica stops. At EventOrder.LOW Identica stops the phases in reverse, so a listener with EventOrder.LOWEST still runs while the providers and the database are open, and later listeners run after they are closed.

Messages to a joining player

A player's client shows chat, titles and dialogs only once it is in the world of a server. While it still configures or switches servers, it drops chat and closes dialogs. ServerJoinedEvent (in me.whereareiam.identica.event.connection) is called at that moment: on the first join and again after every server switch, with the player's Identity and the server's name. Identica delivers its own queued messages, such as the first prompt and the completion message, from this event.

Show your own messages or dialogs to a joining player from it rather than from a platform's join or connect event:

import me.whereareiam.identica.event.EventListener;
import me.whereareiam.identica.event.base.IdenticEvent;
import me.whereareiam.identica.event.connection.ServerJoinedEvent;
import net.kyori.adventure.text.Component;

public final class WelcomeListener implements EventListener {
    @IdenticEvent
    public void onServerJoined(ServerJoinedEvent event) {
        event.getIdentity().sendMessage(Component.text("Welcome to " + event.getServer()));
    }
}

On BungeeCord the event is called from Identica's scheduler shortly after the join, because BungeeCord has no event once the client is in the world.

Common event categories

The current API exposes event groups such as:

  • lifecycle events
  • scenario and pipeline events
  • routing events
  • session events
  • provider events
  • feature-owned events such as verification enrollment (when that feature is selected)
  • account and identity events

That means addon plugins can react at different levels, from high-level scenario start events down to specific verification or routing decisions.

Cancellable and mutable events

Some events are only informational. Others can be modified or cancelled.

For example, VerificationEnrollEvent is cancellable and lets listeners rewrite the provider or method before enrollment begins. It belongs to the verification feature API. Depend on that API at compile time and register the listener after Identica initializes. Verification behavior follows the effective settings of the provider involved.

import me.whereareiam.identica.event.EventListener;
import me.whereareiam.identica.event.base.IdenticEvent;
import me.whereareiam.identica.feature.verification.event.enroll.VerificationEnrollEvent;

public final class ExampleVerificationListener implements EventListener {
    @IdenticEvent
    public void onEnroll(VerificationEnrollEvent event) {
        if ("totp".equalsIgnoreCase(event.getMethodId())) {
            event.setProviderId("credential");
        }
    }
}

Another example is AccountPrepareEvent, where listeners can influence the final preparation decision.

import me.whereareiam.identica.event.EventListener;
import me.whereareiam.identica.event.account.AccountPrepareEvent;
import me.whereareiam.identica.event.base.IdenticEvent;
import me.whereareiam.identica.model.identity.AccountDecision;

public final class ExampleAccountPrepareListener implements EventListener {
    @IdenticEvent
    public void onPrepare(AccountPrepareEvent event) {
        if (event.getDecision() == null) {
            event.setDecision(AccountDecision.allow());
        }
    }
}

Always check the specific event type before assuming it is cancellable or mutable.

Synchronous vs asynchronous

Identica distinguishes between synchronous events and non-synchronous events through the event type hierarchy. If an event implements SynchronousEvent, it is processed synchronously on the current thread.

For addon authors, that means you should treat event handlers as part of the active runtime flow and avoid unnecessary blocking work inside them.

Replicated events

Some Identica events also implement ReplicatedEvent. Those are intended to cross the replication channel and be replayed on other runtimes.

Important properties of replicated events:

  • they carry a replication event id for deduplication
  • they carry an origin server id
  • only registered replicated event types are allowed onto the wire
  • they should represent facts that are safe for every instance to handle locally

That last point is important: not every event should become a replicated event. One-time global operations are usually better expressed as commands or service calls rather than replayed events.

Built-in replicated events

The current replicated event bridge registers these built-in replicated event types:

  • SessionClosedEvent
  • AccountClearEvent
  • AccountDeleteEvent

Those are propagated because they represent state changes that other runtimes can safely observe and apply locally.

When to care about replicated events

As an addon author, you care about replicated events when:

  • your integration runs in a replicated multi-instance environment
  • you need to react consistently to account or session changes across runtimes
  • you are considering defining an event that may need cross-instance replay semantics

If your addon only reacts locally and does not need cross-instance replay, normal Identica events are usually enough.

On this page