It took me so long getting this to work that someone even marked it as solved… 
I ended up mixing technics from @mzur and @luxlogica and got quite confused…
Without the controller method I didn’t know how to configure the Uniform plugin to handle validation… instead of validating “manually” in my custom Uniform::action.
I’d like to not validate it via javaScript… but it seems impossible due to the way the router returns the json.
In summary, is there a way to use Uniform to handle validation? how to initialize it? Above the form in the snippet? in the router? In the custom Uniform::action?
Here’s What I did so for… it works but looks messy…
The config’s router
array(
'pattern' => 'newsletter/subscribe',
'method' => 'POST',
'action' => function() {
// check whether this is an ajax request, and respond with an error if it isn't
if(!kirby()->request()->ajax()){ return response::error("Page Not Found!","404");}
$form_data = kirby()->request()->data();
// add the user and get some feedbacks
return response::json( call_user_func(uniform::$actions['mailchimp'], $form_data) );
}
)
The Uniform action (plugins/uniform-action-mailchimp.php)
uniform::$actions['mailchimp'] = function ($form_data) {
require_once __DIR__.DS.'mailchimp'.DS.'src'.DS.'VPS'.DS.'MailChimp.php';
$mc = new \VPS\MailChimp('xxxxxxxxxxxxxxxxxx-us6');
$list_id = 'xxxxxxxxxxxx';
// get form data
$email = $form_data['_from'];
$potty = $form_data['website'];
$do_website = $form_data['do_website'];
// check if email is valid and the potty is empty
// assemble feedback array (result = [success, message, errors[] ])
$errors = array();
if(!v::email($email)) { $errors[] = 'email'; }
if(!empty($potty)){ $errors[] = 'potty'; }
if(empty($do_website) AND ($do_website !== 'sim') ) { $errors[] = 'do_website'; }
$result = array();
$result['errors'] = $errors;
// if we have validation errors, we can stop and return them:
if(!empty($errors)){
$result['success'] = false;
$result['message'] = 'Validation Failed';
return $result;
}
// if we have no errors, go ahead and insert the email in the list
$response = $mc->post('/lists/'.$list_id.'/members', array(
'email_address' => (string) $email,
'merge_fields' => array('DO_WEBSITE'=>$do_website),
'status' => 'subscribed'
));
// Email already in the list
if ($response['status'] == 400) {
$result['success'] = true;
$result['message'] = 'Thanks, your email was already in the list.';
return $result;
// sloppy way to check if the API returned a pseudo "200" status
} else if (isset($response['id'])) {
$result['success'] = true;
$result['message'] = 'Thank you, soon you\'ll hear from us .';
return $result;
} else {
$result['success'] = false;
$result['message'] = 'Sorry, something went wrong.';
$result['errors'] = 'unknown';
}
// return proper error/success as array
return $result;
};
The js below the form
<script>
// AJAX FORM PROCESSING
$('#newsletter').on('submit', function(e){
e.preventDefault();
var form = $(this);
$.ajax({
type: 'POST',
// use the same url here as the 'pattern' in your route
url: 'newsletter/subscribe',
data: form.serialize(),
success: function(result){
// form data successfully reached form processor api
if(result.success){
// message successfully sent
var msg = "<b>Thank you</b> <br>";
form.find('.form-result').parent().removeClass('hidden');
form.find('.form-result').html(msg += result.message);
} else {
form.find('.form-result').parent().removeClass('hidden');
// an issue was encountered
if(result.errors == undefined || result.errors == null || result.errors.length == 0){
// no validation errors - an email sending error was encountered
var msg = "<b>Ooops</b> <br>";
form.find('.form-result').html(msg += result.message);
} else {
// a validation error was encountered
var msg = "<b>Oops</b> <br>";
if(result.errors.indexOf('email') != -1){
msg += "Please, insert a valid email.";
}
if(result.errors.indexOf('website') != -1){
msg += "We're robots!";
}
form.find('.form-result').html(msg);
}
}
},
error: function(result){
// the form was unable to reach processor api
form.find('.form-result').text('Error '+ result.status + ' - unable to process form: ' + result.statusText);
},
dataType: 'json'
});
});
</script>