<?php
// Put your MailChimp API and List ID hehe
$api_key = 'e37762e570fb63d0cb986431bc21e1fc-us20';
$list_id = 'ddf38752d8';
// Let's start by including the MailChimp API wrapper
include('./inc/MailChimp.php');
// Then call/use the class
use \DrewM\MailChimp\MailChimp;
$MailChimp = new MailChimp($api_key);
// Submit subscriber data to MailChimp
// For parameters doc, refer to: http://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/
// For wrapper's doc, visit: https://github.com/drewm/mailchimp-api
$result = $MailChimp->post("lists/$list_id/members", [
'email_address' => $_POST["email"],
'merge_fields' => ['FNAME'=>$_POST["fname"], 'LNAME'=>$_POST["lname"]],
'status' => 'subscribed',
]);
if ($MailChimp->success()) {
// Success message
echo "<h4>Thank you, you have been added to our mailing list.</h4>";
} else {
// Display error
echo $MailChimp->getLastError();
// Alternatively you can use a generic error message like:
// echo "<h4>Please try again.</h4>";
}
?>
C:\xampp\htdocs\windows\inc\MailChimp.php
<?php
namespace DrewM\MailChimp;
/**
* Super-simple, minimum abstraction MailChimp API v3 wrapper
* MailChimp API v3: http://developer.mailchimp.com
* This wrapper: https://github.com/drewm/mailchimp-api
*
* @author Drew McLellan <drew.mclellan@gmail.com>
* @version 2.5
*/
class MailChimp
{
private $api_key;
private $api_endpoint = 'https://<dc>.api.mailchimp.com/3.0';
const TIMEOUT = 10;
/* SSL Verification
Read before disabling:
http://snippets.webaware.com.au/howto/stop-turning-off-curlopt_ssl_verifypeer-and-fix-your-php-config/
*/
public $verify_ssl = true;
private $request_successful = false;
private $last_error = '';
private $last_response = array();
private $last_request = array();
/**
* Create a new instance
*
* @param string $api_key Your MailChimp API key
* @param string $api_endpoint Optional custom API endpoint
*
* @throws \Exception
*/
public function __construct($api_key, $api_endpoint = null)
{
if (!function_exists('curl_init') || !function_exists('curl_setopt')) {
throw new \Exception("cURL support is required, but can't be found.");
}
$this->api_key = $api_key;
if ($api_endpoint === null) {
if (strpos($this->api_key, '-') === false) {
throw new \Exception("Invalid MailChimp API key supplied.");
}
list(, $data_center) = explode('-', $this->api_key);
$this->api_endpoint = str_replace('<dc>', $data_center, $this->api_endpoint);
} else {
$this->api_endpoint = $api_endpoint;
}
$this->last_response = array('headers' => null, 'body' => null);
}
/**
* Create a new instance of a Batch request. Optionally with the ID of an existing batch.
*
* @param string $batch_id Optional ID of an existing batch, if you need to check its status for example.
*
* @return Batch New Batch object.
*/
public function new_batch($batch_id = null)
{
return new Batch($this, $batch_id);
}
/**
* @return string The url to the API endpoint
*/
public function getApiEndpoint()
{
return $this->api_endpoint;
}
/**
* Convert an email address into a 'subscriber hash' for identifying the subscriber in a method URL
*
* @param string $email The subscriber's email address
*
* @return string Hashed version of the input
*/
public static function subscriberHash($email)
{
return md5(strtolower($email));
}
/**
* Was the last request successful?
*
* @return bool True for success, false for failure
*/
public function success()
{
return $this->request_successful;
}
/**
* Get the last error returned by either the network transport, or by the API.
* If something didn't work, this should contain the string describing the problem.
*
* @return string|false describing the error
*/
public function getLastError()
{
return $this->last_error ?: false;
}
/**
* Get an array containing the HTTP headers and the body of the API response.
*
* @return array Assoc array with keys 'headers' and 'body'
*/
public function getLastResponse()
{
return $this->last_response;
}
/**
* Get an array containing the HTTP headers and the body of the API request.
*
* @return array Assoc array
*/
public function getLastRequest()
{
return $this->last_request;
}
/**
* Make an HTTP DELETE request - for deleting data
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (if any)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function delete($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('delete', $method, $args, $timeout);
}
/**
* Make an HTTP GET request - for retrieving data
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function get($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('get', $method, $args, $timeout);
}
/**
* Make an HTTP PATCH request - for performing partial updates
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function patch($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('patch', $method, $args, $timeout);
}
/**
* Make an HTTP POST request - for creating and updating items
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function post($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('post', $method, $args, $timeout);
}
/**
* Make an HTTP PUT request - for creating new items
*
* @param string $method URL of the API request method
* @param array $args Assoc array of arguments (usually your data)
* @param int $timeout Timeout limit for request in seconds
*
* @return array|false Assoc array of API response, decoded from JSON
*/
public function put($method, $args = array(), $timeout = self::TIMEOUT)
{
return $this->makeRequest('put', $method, $args, $timeout);
}
/**
* Performs the underlying HTTP request. Not very exciting.
*
* @param string $http_verb The HTTP verb to use: get, post, put, patch, delete
* @param string $method The API method to be called
* @param array $args Assoc array of parameters to be passed
* @param int $timeout
*
* @return array|false Assoc array of decoded result
*/
private function makeRequest($http_verb, $method, $args = array(), $timeout = self::TIMEOUT)
{
$url = $this->api_endpoint . '/' . $method;
$response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
$httpHeader = array(
'Accept: application/vnd.api+json',
'Content-Type: application/vnd.api+json',
'Authorization: apikey ' . $this->api_key
);
if (isset($args["language"])) {
$httpHeader[] = "Accept-Language: " . $args["language"];
}
if ($http_verb === 'put') {
$httpHeader[] = 'Allow: PUT, PATCH, POST';
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
curl_setopt($ch, CURLOPT_USERAGENT, 'DrewM/MailChimp-API/3.0 (github.com/drewm/mailchimp-api)');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
switch ($http_verb) {
case 'post':
curl_setopt($ch, CURLOPT_POST, true);
$this->attachRequestPayload($ch, $args);
break;
case 'get':
$query = http_build_query($args, '', '&');
curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
break;
case 'delete':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
case 'patch':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
$this->attachRequestPayload($ch, $args);
break;
case 'put':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
$this->attachRequestPayload($ch, $args);
break;
}
$responseContent = curl_exec($ch);
$response['headers'] = curl_getinfo($ch);
$response = $this->setResponseState($response, $responseContent, $ch);
$formattedResponse = $this->formatResponse($response);
curl_close($ch);
$isSuccess = $this->determineSuccess($response, $formattedResponse, $timeout);
return is_array($formattedResponse) ? $formattedResponse : $isSuccess;
}
/**
* @param string $http_verb
* @param string $method
* @param string $url
* @param integer $timeout
*
* @return array
*/
private function prepareStateForRequest($http_verb, $method, $url, $timeout)
{
$this->last_error = '';
$this->request_successful = false;
$this->last_response = array(
'headers' => null, // array of details from curl_getinfo()
'httpHeaders' => null, // array of HTTP headers
'body' => null // content of the response
);
$this->last_request = array(
'method' => $http_verb,
'path' => $method,
'url' => $url,
'body' => '',
'timeout' => $timeout,
);
return $this->last_response;
}
/**
* Get the HTTP headers as an array of header-name => header-value pairs.
*
* The "Link" header is parsed into an associative array based on the
* rel names it contains. The original value is available under
* the "_raw" key.
*
* @param string $headersAsString
*
* @return array
*/
private function getHeadersAsArray($headersAsString)
{
$headers = array();
foreach (explode("\r\n", $headersAsString) as $i => $line) {
if (preg_match('/HTTP\/[1-2]/', substr($line, 0, 7)) === 1) { // http code
continue;
}
$line = trim($line);
if (empty($line)) {
continue;
}
list($key, $value) = explode(':', $line);
$value = ltrim($value);
if ($key == 'Link') {
$value = array_merge(
array('_raw' => $value),
$this->getLinkHeaderAsArray($value)
);
}
$headers[$key] = $value;
}
return $headers;
}
/**
* Extract all rel => URL pairs from the provided Link header value
*
* Mailchimp only implements the URI reference and relation type from
* RFC 5988, so the value of the header is something like this:
*
* 'https://us13.api.mailchimp.com/schema/3.0/Lists/Instance.json; rel="describedBy",
* <https://us13.admin.mailchimp.com/lists/members/?id=XXXX>; rel="dashboard"'
*
* @param string $linkHeaderAsString
*
* @return array
*/
private function getLinkHeaderAsArray($linkHeaderAsString)
{
$urls = array();
if (preg_match_all('/<(.*?)>\s*;\s*rel="(.*?)"\s*/', $linkHeaderAsString, $matches)) {
foreach ($matches[2] as $i => $relName) {
$urls[$relName] = $matches[1][$i];
}
}
return $urls;
}
/**
* Encode the data and attach it to the request
*
* @param resource $ch cURL session handle, used by reference
* @param array $data Assoc array of data to attach
*/
private function attachRequestPayload(&$ch, $data)
{
$encoded = json_encode($data);
$this->last_request['body'] = $encoded;
curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
}
/**
* Decode the response and format any error messages for debugging
*
* @param array $response The response from the curl request
*
* @return array|false The JSON decoded into an array
*/
private function formatResponse($response)
{
$this->last_response = $response;
if (!empty($response['body'])) {
return json_decode($response['body'], true);
}
return false;
}
/**
* Do post-request formatting and setting state from the response
*
* @param array $response The response from the curl request
* @param string $responseContent The body of the response from the curl request
* @param resource $ch The curl resource
*
* @return array The modified response
*/
private function setResponseState($response, $responseContent, $ch)
{
if ($responseContent === false) {
$this->last_error = curl_error($ch);
} else {
$headerSize = $response['headers']['header_size'];
$response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
$response['body'] = substr($responseContent, $headerSize);
if (isset($response['headers']['request_header'])) {
$this->last_request['headers'] = $response['headers']['request_header'];
}
}
return $response;
}
/**
* Check if the response was successful or a failure. If it failed, store the error.
*
* @param array $response The response from the curl request
* @param array|false $formattedResponse The response body payload from the curl request
* @param int $timeout The timeout supplied to the curl request.
*
* @return bool If the request was successful
*/
private function determineSuccess($response, $formattedResponse, $timeout)
{
$status = $this->findHTTPStatus($response, $formattedResponse);
if ($status >= 200 && $status <= 299) {
$this->request_successful = true;
return true;
}
if (isset($formattedResponse['detail'])) {
$this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
return false;
}
if ($timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout) {
$this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time']);
return false;
}
$this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
return false;
}
/**
* Find the HTTP status code from the headers or API response body
*
* @param array $response The response from the curl request
* @param array|false $formattedResponse The response body payload from the curl request
*
* @return int HTTP status code
*/
private function findHTTPStatus($response, $formattedResponse)
{
if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
return (int)$response['headers']['http_code'];
}
if (!empty($response['body']) && isset($formattedResponse['status'])) {
return (int)$formattedResponse['status'];
}
return 418;
}
}
C:\xampp\htdocs\windows\js\jquery.validate.min.js
By popular demand, I updated my code and posted it as new GitHub repository that utilizes newer MailChimp API 3.0. Please read the tutorial below to create AJAX based MailChimp subscribe form on your PHP website with a little jQuery knowledge.
Original post using (older and no longer supported) MailChimp API 1.0 can be found here.
What this code does
Validate field contents
Pass the form submission to MailChimp and add the record to your list
Basic fallback method for very, very old browser or does not have JavaScript activated
Very customizable and easily expandable, depending on your requirements
jQuery and jQuery Validation plugin called from CDNJS to keep number of files to maintain at minimal
Below we start, we need to generate public API key from your MailChimp account and also identify your list ID. If you are not sure how to retrieve those, follow these links from MailChimp Knowledge Base to retrieve your account’s API key and find your list ID.
The Form Page (index.php)
jQuery will handle the AJAX using $.post method and jQuery Validation plugin will validate the field values including email address format.
For this example, I am including first name and last name as part of the form fields. Feel free to make adjustments as required. Although keep in mind after changing the form fields, you may also need to adjust the subscribe.php file too.<html><head> <title>MailChimp (API v3) Sign-Up Form</title> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.16.0/jquery.validate.min.js"></script> <script type="text/javascript"> $(document).ready(function() { // jQuery Validation $("#signup").validate({ // if valid, post data via AJAX submitHandler: function(form) { $.post("subscribe.php", { fname: $("#fname").val(), lname: $("#lname").val(), email: $("#email").val() }, function(data) { $('#response').html(data); }); }, // all fields are required rules: { fname: { required: true }, lname: { required: true }, email: { required: true, email: true } } }); }); </script></head><body> <div id="wrapper"> <form id="signup" class="formee" action="subscribe.php" method="post"> <fieldset> <legend>Sign Up</legend> <div> <label for="fname">First Name *</label> <input name="fname" id="fname" type="text" /> </div> <div> <label for="lname">Last Name *</label> <input name="lname" id="lname" type="text" /> </div> <div> <label for="email">Email Address *</label> <input name="email" id="email" type="text" /> </div> <div> <input class="right inputnew" type="submit" title="Send" value="Send" /> </div> </fieldset> </form> <div id="response"></div> </div></body></html>
Form Processor (subscribe.php)
I try to include as much comment as possible to make the code self explanatory.<?php // Put your MailChimp API and List ID hehe $api_key = 'ENTER_YOUR_API_KEY_HERE'; $list_id = 'ENTER_YOUR_LIST_ID_HERE'; // Let's start by including the MailChimp API wrapper include('./inc/MailChimp.php'); // Then call/use the class use \DrewM\MailChimp\MailChimp; $MailChimp = new MailChimp($api_key); // Submit subscriber data to MailChimp // For parameters doc, refer to: http://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/ // For wrapper's doc, visit: https://github.com/drewm/mailchimp-api $result = $MailChimp->post("lists/$list_id/members", [ 'email_address' => $_POST["email"], 'merge_fields' => ['FNAME'=>$_POST["fname"], 'LNAME'=>$_POST["lname"]], 'status' => 'subscribed', ]); if ($MailChimp->success()) { // Success message echo "<h4>Thank you, you have been added to our mailing list.</h4>"; } else { // Display error echo $MailChimp->getLastError(); // Alternatively you can use a generic error message like: // echo "<h4>Please try again.</h4>"; }?>
To my knowledge, MailChimp is still using version 3 for its API. So it should still work.REPLY ↓
Hi Micheal,
Thanks for this. I’m testing it on a local server and getting no response from the form except a console error:
POST [mydomain]/mailchimptest/subscribe.php 500 (Internal Server Error)
Finding that a bit difficult to debug. Everything seems to be in the right place and I think I’ve put in valid API and list IDs.
Any tips?
Hi Pierce – It is a bit difficult to provide insights as the code is currently installed on your local server. But based on the information you provided, shouldn’t the POST URL be on localhost instead of your domain?REPLY ↓
Hi Micheal, thanks for getting back.
Sorry, it’s not actually on localhost, rather webhosting with a domain. Sorry for the badly worded question.
Mailchimp-api is partly working, as I wrote a simple api call in the subscribe.php to print out my lists using the api-key. Where it fails is when I try to do the Mailchimp->post of the “email_address” and “status”. All the post values (I’m only passing email address) seem to be there in subscribe, available.
Guess I was hoping you’d tell me the API had changed in the last few months or something. But looking at their docs it looks like this should work…
Might try it in a different server in case it’s local settings.REPLY ↓
To be honest, I have not tested this code for quite a while now. However based on your further confirmation, if you were able to call the API for different commands, then it should be working. Have you tried enabling double opt-in? Not really sure if it is related or not though.
Thanks Micheal, I got diverted from this in the last few days but will have another go shortly. Will let you know if I discover anything that pertains to your example.
When someone tries to signup with an email that already exists in the list you get the error of please use a valid email… is there a way to ignore the fact that someone already sign up or display a custom message for this case?
The email format validation should not relate to whether the email already exists on the mailing list or not. Do you have a preview link so me to check?REPLY ↓
When I run the script, I get the following error:
Parse error: syntax error, unexpected ‘[‘ in /home/xxx/public_html/xxx/mailchimp/subscribe.php on line 16
This sounds like a simple syntax error. Please check that all previous lines have been correctly closed.REPLY ↓
Its probably due to your php version. Try with PHP 5.4.REPLY ↓
How can I make the form submit without refresh?REPLY ↓
It should be working without refresh. You may want to double check the JS part of the index.php to make sure all has been set correctly. Also check the browser console to see if it shows any error or warning.REPLY ↓
Ok thanks for the reply. I’ve scoured the Internet, and couldn’t find a solution anywhere. The refresh works in your example, but I have added radio buttons to my form that allows you to choose one of three interest groups/categories that I have created in my Mailchimp List. By pure luck, I have the form working and it submits with the correct interests category. BUT, I cant get the darn thing to submit without refresh.
This isn’t my forte, but my arrogance (or stupidity depending on perspective), is determined to figure this out. browser console gives me this error:
Uncaught TypeError: (o.dataType || “*”).toLowerCase is not a function
I know you are probably a busy man, but I need help from a Jedi. Obi Wan you’re my only hope. Honestly, any help or direction you can give me, would be much appreciative.REPLY ↓
Hi Marco – Sorry for the late reply. I missed the notification for your last comment somehow. I think the problem you are having is related to the HTML and jQuery syntax. You cannot have more than one element with the same IDs. So for each radio button you need to assign a unique idea. Then you create a condition logic to identify which radio that is selected and pass the value to subscribe.php.
I absolutely love this. Is there a way of applying the same script to work with WordPress comment subscriptions too, based on a checkbox value? So if I check a box underneath the comment form, it will subscribe me to the Mailchimp list? Also, is there a way where I can feed the API key and list with an externally set variable, like say a WordPress option key?REPLY ↓
I believe there is a number of WP plugins that already offers this form of functionality, like MailChimp for WordPress. If you are familiar and comfortable in creating custom WP function/plugin, then you can certainly create your own based on this approach.REPLY ↓
Thanks for that, Michael. I’ll dig around in those plugins and see what functions I can pull out.REPLY ↓
Hello, thanks for sharing this. It works great! Is there a way to load the ‘#response’ back onto the parent page (index.php) without a refresh? Your help will be ecstatically appreciated!REPLY ↓
Hi Rebecca – If you set the AJAX correctly, then the response message should be loaded on the parent page without a refresh. Unless you are referring to a different behavior.REPLY ↓
Hi. I get error “Unknown error, call getLastResponse() to find out what happened.”
Where error?REPLY ↓
It’s quite hard to determine the problem without having access to the code. The only advice I can give for now is to try looking at your MailChimp API calls history/log. It is under Account > Extras > API Keys.REPLY ↓
I got this error too. All I had changed from the code provided is the API Key and List ID, which I confirmed were right. When I called getLastResponse() it returned ‘Array’… do you have any suggestions based on that information?REPLY ↓
Have you checked on the MailChimp API calls log?
Yes, sorry I forgot to mention, no call made it through to the API.
Most likely this is a syntax issue. Could you paste your subscribe.php here? You can screen out the last few digits of the API key and list ID.
Hi, really like your explainations, Thank you. I would like to add a double Opt-in. Could you guide me to add it? It would be great! Thanks again.REPLY ↓
Hi, it’s actually quite simple to enable double opt-in. Just change the `status` array when posting the email address to `pending` instead of `subscribed`.
'status' => 'pending'REPLY ↓
I get the error message
Parse error: syntax error, unexpected ‘[‘ in /home/xxx/public_html/xxx/mailchimp/subscribe.php on line 16
How can this be solved?REPLY ↓
It was a PHP-version issue. It doesn’t work with PHP 5.3, but it works fine with PHP 5.4REPLY ↓
Thanks for sharing the insight. I’ve moved away from PHP5 a long time ago.REPLY ↓
Hi, very great work you have done here, However I would like to know how can we Clear all input fields after submission of data? and how can we display error message inside every inputs ?
Hi Marco,
i am facing a similiar issue. U have a list of checkboxes and i am trying to pass the interests to Mailchimp. Did you manage your script to work? Would appreciate it to have a look on your result. Thank you!