What’s the latest I can hook into wp_ajax_%?

There seems to be a big misunderstanding here about how hooks work, and how AJAX works.

The reason that you can’t hook into AJAX in footer.php is because footer.php is not loaded during an AJAX request. No templates are.

When you run add_action() you are scheduling a given function to fire when that action is fired during the current request. When you load a page, WordPress loads functions.php and runs all the add_action() functions immediately so that all the functions are queued up to fire whenever the actions are triggered by anything that comes later. It does this for every single page load. At no stage are any hooks ‘stored’ so that they can be run on a separate request.

This brings us to AJAX. An AJAX request, made by jQuery or whatever, is a separate request. It’s the equivalent of opening a link in a new tab, it just happens in the background. This means that nothing that happened during the original page load is scheduled to run during that AJAX request. The only way to schedule something to run when the AJAX request is made is to schedule it in functions.php or a plugin, so that the action is scheduled when WordPress loads again for that request.

Because hooked functions aren’t stored between requests, any function that’s hooked in a template can only run if the action it’s hooked to occurs in a later template. With AJAX requests, though, no templates are loaded, because it’s a background request, not a request for a specific page.