Alongside its REST API, Havenlytics uses WordPress’s classic admin-ajax.php transport for a specific set of interactions: frontend property search and map rendering, the contact-agent form submission, the Agent Workspace authentication flow, and several administrator-only maintenance tools. This reference lists every wp_ajax_* and wp_ajax_nopriv_* action registered in Havenlytics 3.3.1, the handler that runs, the nonce action that guards it, and whether it is available to logged-out visitors.
Note: Every AJAX action verifies a nonce. Some also require a capability. An action being registered for
nopriv(logged-out) users does not mean it is unguarded — it still checks the shared frontend nonce. If an action is not listed here, it is not part of the plugin.
How Havenlytics AJAX actions are structured
Each action follows the standard WordPress pattern: the plugin hooks a handler onto wp_ajax_{action} (logged-in) and, where public access is needed, wp_ajax_nopriv_{action} (logged-out). The handler verifies a nonce, checks a capability where required, does its work, and returns wp_send_json_success() or wp_send_json_error(). Havenlytics uses three broad nonce-and-capability tiers:
- Frontend / contact — a single shared nonce action
hvnly_ajax_request, registered for bothprivandnopriv. No capability check (these are public-facing). - Workspace auth — a distinct per-action nonce (the
SessionAuthController::NONCE_*constants), registered for bothprivandnopriv, with rate limiting on the sensitive flows. - Admin — a dedicated nonce plus a capability such as
manage_optionsoractivate_plugins, registered forprivonly.
Frontend property-search actions
All share the nonce action hvnly_ajax_request and are available to both logged-in and logged-out visitors.
| Action | Handler (class::method) | Nonce action | Access |
|---|---|---|---|
hvnly_search_properties | Frontend\AjaxHandler::search_properties | hvnly_ajax_request | priv + nopriv |
hvnly_load_more | Frontend\AjaxHandler::load_more_properties (delegates to search) | hvnly_ajax_request | priv + nopriv |
hvnly_get_filters | Frontend\AjaxHandler::get_filter_options | hvnly_ajax_request | priv + nopriv |
hvnly_get_properties_for_map | Frontend\AjaxHandler::hvnly_get_properties_for_map | hvnly_ajax_request | priv + nopriv |
hvnly_render_map_container | Frontend\AjaxHandler::hvnly_render_map_container | hvnly_ajax_request | priv + nopriv |
hvnly_sidebar_filter_search | SidebarAjaxHandler::sidebar_filter_search | hvnly_ajax_request | priv + nopriv |
hvnly_elementor_load_more_properties | Elementor\AjaxHandler::load_more_properties | hvnly_ajax_request | priv + nopriv |
Contact-agent submission
| Action | Handler (class::method) | Nonce action | Access |
|---|---|---|---|
hvnly_submit_contact_agent | ContactAgentAjaxRegistrar::dispatch → AjaxHandler::handle_submit | hvnly_ajax_request | priv + nopriv |
ContactAgentAjaxRegistrar is the primary registrar (wired from HookManager::register_ajax_hooks). The ContactAgent\AjaxHandler::register_hooks path carries an idempotent guard so the action is not double-registered.
Workspace authentication actions
These drive the Agent Workspace sign-in, registration, and email-verification flow. Each uses its own nonce action (the SessionAuthController::NONCE_* constants) verified through verify_nonce(), and each is available to logged-out users so a visitor can authenticate. Most run through guard_enabled() (Workspace-must-be-enabled) — the exceptions are logout and nonce refresh.
| Action | Handler (class::method) | Nonce action | Access |
|---|---|---|---|
hvnly_ws_login | SessionAuthController::login | hvnly_ws_auth_login | priv + nopriv |
hvnly_ws_register | SessionAuthController::register_user | hvnly_ws_auth_register | priv + nopriv |
hvnly_ws_lostpassword | SessionAuthController::lost_password | hvnly_ws_auth_lostpassword | priv + nopriv |
hvnly_ws_resetpassword | SessionAuthController::reset_password | hvnly_ws_auth_resetpassword | priv + nopriv |
hvnly_ws_logout | SessionAuthController::logout | hvnly_ws_auth_logout | priv + nopriv |
hvnly_ws_auth_nonces | SessionAuthController::refresh_nonces | none (token refresh) | priv + nopriv |
hvnly_ws_verify_email | SessionAuthController::verify_email | hvnly_ws_auth_verify_email | priv + nopriv (rate limited) |
hvnly_ws_resend_verification | SessionAuthController::resend_verification | hvnly_ws_auth_resend_verification | priv + nopriv (rate limited) |
hvnly_ws_reg_set_status | RegistrationAdminActions::ajax_set_status | hvnly_ws_reg_status + per-agent hvnly_ws_reg_status_{agent_id} | priv; caps APPROVE_LISTINGS + PORTAL_ACCESS or manage_options |
Security: The email verify/resend actions are rate limited by
VerificationRateLimiter: verify is capped at 20 per IP per 10 minutes and 10 per selector per hour; resend at 10 per IP per hour with a 1-minute cooldown, 5 per account per hour, and 10 per account per day. Responses are uniform and generic so they cannot be used to probe which emails or tokens exist. Tokens must match^[a-f0-9]{16}\.[a-f0-9]{64}$before any lookup runs.
Admin maintenance actions
These are administrator-only. Each requires a capability in addition to its nonce and is registered for priv users only.
| Action | Handler (class::method) | Nonce action | Capability |
|---|---|---|---|
hvnly_clear_cache | CacheAdmin::ajax_clear_cache | hvnly_cache_nonce | manage_options |
hvnly_get_cache_stats | CacheAdmin::ajax_get_cache_stats | hvnly_cache_nonce | manage_options |
hvnly_update_cache_settings | CacheAdmin::ajax_update_cache_settings | hvnly_cache_nonce | manage_options |
hvnly_clear_shortcode_cache | CacheAdmin::ajax_clear_shortcode_cache | hvnly_cache_nonce | manage_options |
hvnly_clear_dynamic_css | CacheAdmin::ajax_clear_dynamic_css | hvnly_cache_nonce | manage_options |
hvnly_import_properties | PropertyImportWizard::ajax_import_properties | hvnly_import_nonce + hvnly_import_csrf | manage_options |
hvnly_cancel_import | PropertyImportWizard::ajax_cancel_import | hvnly_import_nonce + hvnly_import_csrf | manage_options |
hvnly_save_google_api_key | PropertyImportWizard::ajax_save_google_api_key | hvnly_import_nonce + hvnly_import_csrf | manage_options |
hvnly_save_map_provider | PropertyImportWizard::ajax_save_map_provider | hvnly_import_nonce + hvnly_import_csrf | manage_options |
hvnly_dismiss_theme_notice | ThemeRecommendationNotice::ajax_dismiss | hvnly_dismiss_theme_notice | manage_options |
hvnly_deactivate_conflict | ConflictChecker::ajax_deactivate_plugin | hvnly_ajax_nonce + per-plugin hvnly_deactivate_{file} | activate_plugins |
hvnly_get_icon_library | Hvnly_Advanced_Icon_Manager | hvnly_advanced_icon_picker_nonce | (admin icon picker) |
hvnly_search_icons | Hvnly_Advanced_Icon_Manager | hvnly_advanced_icon_picker_nonce | (admin icon picker) |
Warning: The import wizard requires two nonces on every request —
hvnly_import_nonceandhvnly_import_csrf— as a defense-in-depth CSRF measure. A request missing either is rejected. If you build custom tooling around the importer, send both fields.
Note: The
hvnly_clear_cache,hvnly_get_cache_stats, andhvnly_update_cache_settingsactions are also registered fromHookManager, so those three are wired up twice (a duplicate registration). Behaviour is unchanged — the same guarded handler runs — but be aware of the double registration when tracing hooks or profiling.
The nonce + capability pattern
Havenlytics AJAX security rests on two independent checks that must both pass:
- Nonce verification proves the request originated from a page the site itself rendered (CSRF protection). Frontend actions verify the shared
hvnly_ajax_requestnonce; each admin and Workspace action verifies its own dedicated nonce. - Capability check proves the current user is allowed to perform the action (authorization). Frontend and contact actions are public and skip this; admin actions require
manage_optionsoractivate_plugins;hvnly_ws_reg_set_statusrequires the portal capabilitiesAPPROVE_LISTINGS+PORTAL_ACCESS(ormanage_options).
The distinct nonce actions in play across the AJAX surface are: hvnly_ajax_request, hvnly_cache_nonce, hvnly_import_nonce, hvnly_import_csrf, hvnly_dismiss_theme_notice, hvnly_ajax_nonce, hvnly_deactivate_{file}, hvnly_advanced_icon_picker_nonce, and the hvnly_ws_auth_* / hvnly_ws_reg_status family.
Calling an action from JavaScript
Post to admin-ajax.php with the action name and the matching nonce. The example below submits the contact-agent form using the shared frontend nonce.
// The nonce is localized onto the page as hvnly_ajax_request.
async function submitContactAgent( propertyId, name, email, message ) {
const form = new FormData();
form.append( 'action', 'hvnly_submit_contact_agent' );
form.append( 'nonce', hvnlyFrontend.nonce ); // wp_create_nonce('hvnly_ajax_request')
form.append( 'property_id', propertyId );
form.append( 'name', name );
form.append( 'email', email );
form.append( 'message', message );
const res = await fetch( hvnlyFrontend.ajaxUrl, { // admin-ajax.php URL
method: 'POST',
credentials: 'same-origin',
body: form
} );
const body = await res.json();
if ( ! body.success ) {
// e.g. { code: 'hvnly_contact_agent_invalid_nonce', status: 403 }
console.error( body.data.message );
return;
}
return body.data;
}
A bad nonce returns a structured error, for example:
{
"success": false,
"data": {
"message": "Security check failed. Please refresh the page and try again.",
"code": "hvnly_contact_agent_invalid_nonce",
"status": 403
}
}
Writing a properly-guarded handler (PHP)
If you extend Havenlytics with your own AJAX action, mirror the plugin’s pattern: register both hooks only if you need public access, verify a dedicated nonce, check a capability, then respond. Never skip either guard.
<?php
/**
* A logged-in-only custom AJAX action.
* Verifies its own nonce AND a capability before doing any work.
*/
add_action( 'wp_ajax_my_plugin_sync_property', function () {
// 1. CSRF: verify a dedicated nonce (die on failure).
check_ajax_referer( 'my_plugin_sync', 'nonce' );
// 2. Authorization: confirm the user may do this.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error(
array( 'message' => 'Insufficient permissions.' ),
403
);
}
// 3. Sanitize input.
$property_id = isset( $_POST['property_id'] )
? absint( $_POST['property_id'] )
: 0;
if ( ! $property_id || get_post_type( $property_id ) !== 'hvnly_property' ) {
wp_send_json_error( array( 'message' => 'Invalid property.' ), 400 );
}
// 4. Do the work, then respond.
wp_send_json_success( array( 'property_id' => $property_id ) );
} );
Common mistake: Registering a handler on
wp_ajax_nopriv_*without a nonce because “it is just a public read.” Every Havenlytics public action still verifieshvnly_ajax_request. An unguardednoprivendpoint is an open door — always verify a nonce.
Best practices
- Reuse the shared frontend nonce (
hvnly_ajax_request) only for genuinely public actions; give admin tools their own nonce. - Localize, don’t hard-code. Read the AJAX URL and nonce from the localized script object rather than embedding
/wp-admin/admin-ajax.php. - Return structured errors. Follow the
{ message, code, status }shape Havenlytics uses so clients can branch oncode. - Send both import nonces. Any interaction with the import wizard needs
hvnly_import_nonceandhvnly_import_csrf. - Prefer REST for new work. For structured CRUD, the REST API gives you schemas, discoverability, and consistent permission callbacks; reserve admin-ajax for the flows above.
Performance recommendations
- Debounce search input.
hvnly_search_propertiesandhvnly_sidebar_filter_searchfire per keystroke if you let them — debounce on the client. - Page with
hvnly_load_moreinstead of requesting large result sets in a single call. - Cache filter options.
hvnly_get_filtersresults change rarely; cache them client-side for the session. - Separate map data from map markup.
hvnly_get_properties_for_map(data) andhvnly_render_map_container(markup) are distinct on purpose — fetch data once and re-render locally.
Security notes
- Rate-limited auth actions (
hvnly_ws_verify_email,hvnly_ws_resend_verification) return uniform responses — do not build logic that depends on distinguishing “not found” from “throttled.” - Admin actions enforce a capability in addition to a nonce; never rely on the nonce alone for authorization.
- The conflict-deactivation action layers a global nonce with a per-plugin nonce (
hvnly_deactivate_{file}) and requiresactivate_plugins. - Always sanitize
$_POSTinput withabsint(),sanitize_text_field(), etc., before use.
Common mistakes
- Sending only one of the two import nonces and getting a rejected request.
- Assuming
noprivmeans “no security” — the shared nonce is still verified. - Reusing
hvnly_ajax_requestfor an admin action instead of a capability-gated dedicated nonce. - Treating a generic rate-limited verification response as a hard error rather than a throttle.
- Hard-coding the admin-ajax URL instead of reading it from the localized settings.