diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
index f43630a..7c4a631 100644
--- src/wp-admin/js/customize-controls.js
+++ src/wp-admin/js/customize-controls.js
@@ -1945,7 +1945,6 @@
 			save: function() {
 				var self  = this,
 					query = $.extend( this.query(), {
-						action: 'customize_save',
 						nonce:  this.nonce.save
 					} ),
 					processing = api.state( 'processing' ),
@@ -1955,7 +1954,7 @@
 				body.addClass( 'saving' );
 
 				submit = function () {
-					var request = $.post( api.settings.url.ajax, query );
+					var request = wp.ajax.post( 'customize_save', query );
 
 					api.trigger( 'save', request );
 
@@ -1963,28 +1962,33 @@
 						body.removeClass( 'saving' );
 					} );
 
-					request.done( function( response ) {
-						// Check if the user is logged out.
+					request.fail( function ( response ) {
 						if ( '0' === response ) {
+							response = 'not_logged_in';
+						} else if ( '-1' === response ) {
+							// Back-compat in case any other check_ajax_referer() call is dying
+							response = 'invalid_nonce';
+						}
+
+						if ( 'invalid_nonce' === response ) {
+							self.cheatin();
+						} else if ( 'not_logged_in' === response ) {
 							self.preview.iframe.hide();
 							self.login().done( function() {
 								self.save();
 								self.preview.iframe.show();
 							} );
-							return;
-						}
-
-						// Check for cheaters.
-						if ( '-1' === response ) {
-							self.cheatin();
-							return;
 						}
+						api.trigger( 'error', response );
+					} );
 
+					request.done( function( response ) {
 						// Clear setting dirty states
 						api.each( function ( value ) {
 							value._dirty = false;
 						} );
-						api.trigger( 'saved' );
+
+						api.trigger( 'saved', response );
 					} );
 				};
 
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
index 60303e6..58e8c0f 100644
--- src/wp-includes/class-wp-customize-manager.php
+++ src/wp-includes/class-wp-customize-manager.php
@@ -628,10 +628,14 @@ final class WP_Customize_Manager {
 	 * @since 3.4.0
 	 */
 	public function save() {
-		if ( ! $this->is_preview() )
-			die;
+		if ( ! $this->is_preview() ) {
+			wp_send_json_error( 'not_preview' );
+		}
 
-		check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce' );
+		$action = 'save-customize_' . $this->get_stylesheet();
+		if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
+			wp_send_json_error( 'invalid_nonce' );
+		}
 
 		// Do we have to switch themes?
 		if ( ! $this->is_theme_active() ) {
@@ -666,7 +670,16 @@ final class WP_Customize_Manager {
 		 */
 		do_action( 'customize_save_after', $this );
 
-		die;
+		/**
+		 * Filter response data for customize_save Ajax request.
+		 *
+		 * @since 4.2.0
+		 *
+		 * @param array $data
+		 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+		 */
+		$response = apply_filters( 'wp_customize_save_response', array(), $this );
+		wp_send_json_success( $response );
 	}
 
 	/**
