The MailChimp Webhooks working today for both subscribing and unsubscubing. I had another one of those face palm errors. I couldn't figure out why the POST from MailChimp wasn't exucting the code in my index.php. Things like this are where curl is your friend.
Using curl http://localhost/ml I was able to see the output HTML. It was not what I was expecting. Instead of a boring message with information from the MySQL database connection there was a 303 redirect from the server. After around an hour of hunting through Apache configurations and trying a few things out (also some random internet distractions), I finally executed curl http://localhost/ml/ and received the response I expected.
It was all about the trailing "/", who knew? One of those things that I feel I should have known.
With that little error squared away, and the Webhook URL updated in MailChimp things started looking up. Of course there were some additional PHP and MySQL errors. The major one was some problems in my SQL query, but not being able to see what the error actually was. I finally found this little gem that will log the errors if there are any:
$stmt = $mysqli->prepare($query) or die("Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error);
This will write the error to the log file, /var/log/apache2/error.log in my case, which is massively helpful. This uses the mysqli interface there is slightly different syntax for PHP's MySQL PDO interface.
Now I just need to build a page to download the eBook and update the downloads table.