How can I emulate permalink behaviour on static pages

You need to add rewrite rules and the query_vars to fit your needs.

For example. In your functions.php or plugin file:

// Register a new vars to be used in the rewrite rules
add_filter('query_vars', 'properties_add_query_vars');
function properties_add_query_vars( $vars) {
    $vars[] = "my_action"; // name of the var as seen in the URL
    return $vars;
}

// Add the new rewrite rule to existings ones
add_action('init','properties_add_rewrite_rules');
function properties_add_rewrite_rules() {
    //Change your_page_id with the ID of the whats-on page
    add_rewrite_rule( 'whats-on/(.+)/?$' , 'index.php?page_id=your_page_id&my_action='.$matches[1] , 'top' );
}

And then in the template for whats-on page (create a page-whats-on.php template file, see this):

global $wp_query;
if(isset($wp_query->query_vars('my_action')) && $wp_query->query_vars('my_action') == 'some_action'){
    //Do what you want
}

Don’t foget to flush the rewrite rules before you try to test your new rewrite rules. You can do this by going to settings->permalinks and click on Save button.

EDIT

In your updated question you have some issues in your code.

  1. There was a error in my code, where the query var for the page is page_id and I wrote just p. Sorry for that.
  2. The rewrite rules and custom query vars should be added on init action hook, only the flush_rewrite_rules(); should run only on activation hook due performance reasons (I asked a related question myself).

I’ve tested this code and it is working:

class SpektrixPlugin {

    public function __construct(){
        add_filter('query_vars', array($this, 'add_query_vars'));
        add_action('init', array($this, 'add_rewrite_rules'));
    }

    public function activate() {
        flush_rewrite_rules();
    }

    public function deactivate() {
        flush_rewrite_rules();
    }

    public function add_query_vars($vars) {
        $vars[] = "event"; // name of the var as seen in the URL
        return $vars;
    }

    public function add_rewrite_rules() {
        //Change your_page_id with the ID of the whats-on page
        add_rewrite_rule( 'whats-on/(.+)/?$' , 'index.php?page_id=8&event=$matches[1]' , 'top' );
    }

    public function spektrix_list_events() {
       //$api = new SpektrixApiClient();
       //return $api->getAllEvents();
       return 'Test';
    }
}

$SpektrixEvents = new SpektrixPlugin();

register_activation_hook( __file__, array($SpektrixEvents, 'activate') );
register_deactivation_hook( __file__, array($SpektrixEvents, 'deactivate') );

Now you need to check when the query var event is present. In your case, I think that, as I said before, the best way to make this check is in the page tempalte. You can create a specific template for you page with ID 8. Just create a page-8.php file and put it in you theme folder (you can also put it in a plugin folder but this require extra work). In page-8.php You can make something like this:

<?php
  get_header();

  global $wp_query;
  if($wp_query->get('event') == 'event'){
       echo $SpektrixEvents->spektrix_list_events();
  }

  get_footer();
 ?>

Now If you go to yoursite.com/whats-on/event/ your get “Test” printed.