Skip to main content

Overview

The Ember Client Server API uses Bukkit’s event-driven architecture to notify your plugin when important actions occur. All events are fired asynchronously when the server receives responses from Ember Client.

Bukkit Event Architecture

The API leverages Bukkit’s standard event system, which means:
  • Events extend PlayerEvent (they’re associated with a specific player)
  • You listen to events using @EventHandler annotations
  • Events are called through Bukkit’s PluginManager
  • You can set event priorities and control cancellation (where applicable)

Listening to Events

To listen to Ember Client events, create a listener class and register it with Bukkit:
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import com.emberclient.serverapi.event.*;

public class EmberListener implements Listener {
    
    @EventHandler
    public void onEmberPlayerJoin(EmberPlayerJoinEvent event) {
        Player player = event.getPlayer();
        // Handle player joining with Ember Client
    }
    
    @EventHandler
    public void onAttestationRegister(EmberAttestationRegisterEvent event) {
        Player player = event.getPlayer();
        AttestationRegisterResult status = event.getStatus();
        // Handle attestation registration
    }
}

Available Events

The Server API provides three events that correspond to different client interactions:

EmberPlayerJoinEvent

Package: com.emberclient.serverapi.event.EmberPlayerJoinEvent Fired when a player joins the server using Ember Client.
EmberPlayerJoinEvent.java
public class EmberPlayerJoinEvent extends PlayerEvent {
    public EmberPlayerJoinEvent(Player player) {
        super(player);
    }
}

Available Methods

getPlayer()
Player
Returns the player who joined with Ember Client.

Example Usage

@EventHandler
public void onEmberPlayerJoin(EmberPlayerJoinEvent event) {
    Player player = event.getPlayer();
    
    player.sendMessage("Welcome! You're using Ember Client.");
    
    // Grant special permissions for Ember Client users
    player.addAttachment(plugin, "ember.user", true);
    
    // Log the join
    getLogger().info(player.getName() + " joined with Ember Client");
}
Use this event to detect Ember Client users and provide enhanced features, special permissions, or customized experiences.

EmberAttestationRegisterEvent

Package: com.emberclient.serverapi.event.EmberAttestationRegisterEvent Fired when a player completes (or fails) an attestation key registration request.
EmberAttestationRegisterEvent.java
public class EmberAttestationRegisterEvent extends PlayerEvent {
    @Getter
    private AttestationRegisterResult status;
    
    @Getter
    private X509EncodedKeySpec publicKey;
    
    public EmberAttestationRegisterEvent(
        Player player, 
        AttestationRegisterResult status, 
        X509EncodedKeySpec publicKey
    ) {
        super(player);
        this.status = status;
        this.publicKey = publicKey;
    }
}

Available Methods

getPlayer()
Player
Returns the player attempting to register an attestation key.
getStatus()
AttestationRegisterResult
Returns the registration result status. Possible values:
  • SUCCESS - Key registered successfully
  • SIGNING_NOT_ALLOWED - Device doesn’t support attestation
  • USER_CANCELLED - User cancelled the registration
  • UNKNOWN_ERROR - An unexpected error occurred
getPublicKey()
X509EncodedKeySpec
Returns the public key if registration was successful, null otherwise. Store this key to verify future signatures.

Example Usage

@EventHandler
public void onAttestationRegister(EmberAttestationRegisterEvent event) {
    Player player = event.getPlayer();
    AttestationRegisterResult status = event.getStatus();
    
    if (status == AttestationRegisterResult.SUCCESS) {
        X509EncodedKeySpec publicKey = event.getPublicKey();
        
        // Store the public key in your database
        database.storePublicKey(player.getUniqueId(), publicKey.getEncoded());
        
        player.sendMessage("§aAttestation key registered successfully!");
        player.sendMessage("§aYou can now use 2FA to protect your account.");
        
    } else if (status == AttestationRegisterResult.USER_CANCELLED) {
        player.sendMessage("§cAttestation registration cancelled.");
        
    } else if (status == AttestationRegisterResult.SIGNING_NOT_ALLOWED) {
        player.sendMessage("§cYour device doesn't support attestation.");
        player.sendMessage("§cPlease use TOTP-based 2FA instead.");
        
    } else {
        player.sendMessage("§cAn error occurred during registration.");
        getLogger().warning("Attestation registration failed for " + 
            player.getName() + ": " + status);
    }
}

EmberAttestationSignEvent

Package: com.emberclient.serverapi.event.EmberAttestationSignEvent Fired when a player completes (or fails) an attestation signing request.
EmberAttestationSignEvent.java
public class EmberAttestationSignEvent extends PlayerEvent {
    @Getter
    private AttestationSignResult status;
    
    @Getter
    private byte[] signedData;
    
    public EmberAttestationSignEvent(
        Player player, 
        AttestationSignResult status, 
        byte[] signedData
    ) {
        super(player);
        this.status = status;
        this.signedData = signedData;
    }
}

Available Methods

getPlayer()
Player
Returns the player attempting to sign data.
getStatus()
AttestationSignResult
Returns the signing result status. Possible values:
  • SUCCESS - Data signed successfully
  • SIGNING_NOT_ALLOWED - Device doesn’t support attestation
  • SIGN_DATA_INVALID - The verification data was invalid
  • USER_CANCELLED - User cancelled the signing
  • KEY_DOES_NOT_EXIST - No attestation key registered
  • UNKNOWN_ERROR - An unexpected error occurred
getSignedData()
byte[]
Returns the signed data if signing was successful, null otherwise. Verify this signature against the stored public key.

Example Usage

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;

@EventHandler
public void onAttestationSign(EmberAttestationSignEvent event) {
    Player player = event.getPlayer();
    AttestationSignResult status = event.getStatus();
    
    if (status == AttestationSignResult.SUCCESS) {
        byte[] signedData = event.getSignedData();
        byte[] originalData = pendingVerifications.get(player.getUniqueId());
        
        // Retrieve stored public key
        byte[] publicKeyBytes = database.getPublicKey(player.getUniqueId());
        
        try {
            // Verify the signature
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            PublicKey publicKey = keyFactory.generatePublic(
                new X509EncodedKeySpec(publicKeyBytes)
            );
            
            Signature signature = Signature.getInstance("SHA256withECDSA");
            signature.initVerify(publicKey);
            signature.update(originalData);
            
            if (signature.verify(signedData)) {
                player.sendMessage("§aAuthentication successful!");
                authenticatedPlayers.add(player.getUniqueId());
                // Grant access to protected features
            } else {
                player.sendMessage("§cSignature verification failed!");
                player.kickPlayer("Invalid attestation signature");
            }
            
        } catch (Exception e) {
            getLogger().severe("Failed to verify signature: " + e.getMessage());
            player.sendMessage("§cVerification error occurred.");
        }
        
    } else if (status == AttestationSignResult.KEY_DOES_NOT_EXIST) {
        player.sendMessage("§cYou haven't registered an attestation key yet.");
        player.sendMessage("§cUse /2fa register to set up 2FA.");
        
    } else if (status == AttestationSignResult.USER_CANCELLED) {
        player.sendMessage("§cAuthentication cancelled.");
        
    } else {
        player.sendMessage("§cAuthentication failed: " + status);
    }
    
    // Clean up pending verification
    pendingVerifications.remove(player.getUniqueId());
}

Event Data Summary

EmberPlayerJoinEvent

Data:
  • Player
Use Case: Detect Ember Client users

EmberAttestationRegisterEvent

Data:
  • Player
  • AttestationRegisterResult
  • X509EncodedKeySpec (public key)
Use Case: Store public key for 2FA

EmberAttestationSignEvent

Data:
  • Player
  • AttestationSignResult
  • byte[] (signed data)
Use Case: Verify authentication signatures

Best Practices

The getStatus() method tells you whether the operation succeeded. Only access getPublicKey() or getSignedData() when status is SUCCESS:
if (event.getStatus() == AttestationRegisterResult.SUCCESS) {
    X509EncodedKeySpec publicKey = event.getPublicKey();
    // Safe to use publicKey
}
Don’t just check for SUCCESS - handle failure cases to provide helpful feedback:
switch (event.getStatus()) {
    case SUCCESS:
        // Handle success
        break;
    case USER_CANCELLED:
        // User cancelled
        break;
    case SIGNING_NOT_ALLOWED:
        // Device doesn't support it
        break;
    default:
        // Unknown error
        break;
}
Public keys should be stored in a secure database, associated with the player’s UUID. Use proper encryption for your database:
// Store the encoded public key bytes
byte[] encodedKey = event.getPublicKey().getEncoded();
database.storePublicKey(player.getUniqueId(), encodedKey);
If you track pending sign requests, always clean them up after receiving the event:
try {
    // Process the signature
} finally {
    pendingVerifications.remove(player.getUniqueId());
}

Next Steps

Attestation Concept

Learn more about how attestation authentication works

Attestation Guide

Step-by-step guide to implementing attestation in your plugin

Build docs developers (and LLMs) love