WordPress devuelve 400 desde admin-ajax.php cuando no encuentra un callback válido para action o cuando la validación de la aplicación o seguridad rechaza la petición. El endpoint puede estar sano aunque falle una función de un plugin.
Conserva la petición exacta. Abrir directamente admin-ajax.php sin una acción suele devolver 0 o 400 y no constituye una prueba útil.
Registra la petición con seguridad
Activa la función una vez en la pestaña Red y revisa método, URL, estado, campos y respuesta.
Conserva:
nombre de action
usuario conectado o anónimo
método HTTP y tipo de contenido
nombre y presencia del campo nonce
texto de respuesta
hora y script iniciador
Oculta valores de nonce, cookies y contenido personal.
Confirma que se envía action
WordPress distribuye mediante $_REQUEST['action']. Una acción ausente, vacía, con nombre incorrecto o un cuerpo JSON no interpretado puede fallar antes de entrar en el plugin.
Inspecciona la petición final, no solo el código JavaScript. Otro script puede modificar o codificar el formulario.
Usa FormData o codificación de URL según el callback y configura correctamente el tipo.
Relaciona hooks privados y públicos
Los callbacks autenticados usan wp_ajax_{action}; los anónimos necesitan wp_ajax_nopriv_{action}.
add_action( 'wp_ajax_example_action', 'handle_example_action' );
add_action( 'wp_ajax_nopriv_example_action', 'handle_public_example_action' );
Registra acceso público solo cuando sea intencionado, con validación y límites. No expongas operaciones administrativas mediante nopriv.
Valida nonces y capacidades
Un nonce caducado o cookies perdidas pueden provocar rechazo. Recarga o inicia sesión y compara.
check_ajax_referer( 'example_action', 'nonce' );
current_user_can( 'edit_posts' );
El nonce no sustituye la autorización. Devuelve un error estructurado y un estado correcto sin revelar detalles de seguridad.
Revisa los registros y la respuesta PHP
El callback puede provocar un fatal o llamar a wp_die() con un resultado genérico. Relaciona la hora con los registros.
En código propio, usa wp_send_json_success() o wp_send_json_error():
wp_send_json_error( array( 'message' => 'Request could not be completed.' ), 400 );
No muestres secretos ni excepciones completas.
Comprueba el cortafuegos y los límites
ModSecurity o Cloudflare pueden rechazar determinados contenidos, tamaños o frecuencias. Busca el evento por URI y hora.
Crea una excepción estrecha para la acción validada, no para todo admin-ajax.php. El AJAX público es un objetivo frecuente de abuso.
Las subidas grandes deben usar mecanismos de medios o REST, no cuerpos AJAX sobredimensionados.
Revisa caché y versiones del script
Las respuestas AJAX no deben almacenarse como página pública. Un JavaScript antiguo puede enviar otro campo action o nonce después de actualizar.
Regenera recursos y versiona los scripts. Confirma que el navegador anónimo recibe el archivo actual.
No fijes ajaxurl a mano en el frontal; proporciona la URL administrativa correcta.
Versiona el contrato navegador-servidor
Tras actualizar, un script en caché puede enviar campos del callback antiguo mientras PHP espera el nuevo contrato. Trata el nombre de acción, campos, tipos y forma de respuesta como una interfaz versionada.
Usa wp_localize_script() o wp_add_inline_script() solo para configuración no secreta y nonces de la página actual. Encola con versión de archivo. Nunca expongas claves API.
Gestiona concurrencia y reintentos
Autocompletado, autoguardado y filtros producen peticiones solapadas. Una respuesta antigua lenta puede sobrescribir otra nueva y un doble clic repetir mutaciones.
Cancela solicitudes obsoletas cuando proceda y haz idempotentes las escrituras mediante referencias estables. Usa transacciones o bloqueos solo en el tramo necesario. No serialices globalmente todo AJAX.
Verifica usuarios y errores previstos
Prueba rutas autenticadas y anónimas según el diseño, nonce válido e inválido, error de validación y límites de frecuencia. Confirma que solo se devuelve información autorizada.
Vigila 400 y 403 por acción tras desplegar. El mantenimiento recurrente debe conservar versionados los contratos AJAX; que el endpoint responda no demuestra que cada acción siga siendo compatible.