• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » How to securely integrate HubSpot Contacts endpoints into WordPress custom plugins using Rewrite API custom endpoints

How to securely integrate HubSpot Contacts endpoints into WordPress custom plugins using Rewrite API custom endpoints

Leveraging WordPress Rewrite API for Secure HubSpot Contact Endpoint Integration

Integrating external APIs into WordPress, especially for sensitive data like customer contacts, demands a robust and secure approach. While direct AJAX calls are common, they often expose API keys and endpoints directly in client-side JavaScript, posing a significant security risk. This guide demonstrates how to securely expose HubSpot Contacts API endpoints within your WordPress custom plugin by leveraging the WordPress Rewrite API and custom endpoints. This method acts as a secure proxy, shielding your API credentials and simplifying endpoint management.

Setting Up the Custom Endpoint

The WordPress Rewrite API allows us to create custom URL structures that map to specific PHP callback functions. This is crucial for creating a clean, RESTful interface for our HubSpot integration without exposing the underlying API calls directly.

Registering the Rewrite Rule and Endpoint

We’ll hook into the init action to register our custom rewrite rule and the corresponding query variable. This rule will define a URL pattern that WordPress will recognize and route to our custom handler.

/**
 * Register custom endpoint for HubSpot contacts.
 */
function my_hubspot_register_api_endpoint() {
    // Add a custom query variable to recognize our endpoint.
    add_rewrite_tag( '%hubspot_contact_action%', '([^/]+)' );

    // Define the rewrite rule. This will match URLs like:
    // yoursite.com/api/v1/hubspot-contacts/sync/
    // yoursite.com/api/v1/hubspot-contacts/get/123/
    add_rewrite_rule(
        '^api/v1/hubspot-contacts/([^/]+)/?$',
        'index.php?hubspot_contact_action=$matches[1]',
        'top' // 'top' ensures this rule is checked before others.
    );

    // Flush rewrite rules on plugin activation/deactivation or when this function runs.
    // In a production environment, it's better to flush manually via Settings -> Permalinks.
    // flush_rewrite_rules();
}
add_action( 'init', 'my_hubspot_register_api_endpoint' );

In this code:

  • add_rewrite_tag( '%hubspot_contact_action%', '([^/]+)' ); registers a new placeholder, %hubspot_contact_action%, which will capture a segment of the URL.
  • add_rewrite_rule( '^api/v1/hubspot-contacts/([^/]+)/?$', 'index.php?hubspot_contact_action=$matches[1]', 'top' ); defines the actual URL pattern. It looks for URLs starting with /api/v1/hubspot-contacts/ followed by one or more characters (captured in $matches[1]) and a trailing slash (optional). This captured segment will be assigned to our hubspot_contact_action query variable.
  • The 'top' priority ensures this rule is evaluated early.
  • flush_rewrite_rules(); is commented out because it’s resource-intensive and should ideally be handled by WordPress’s permalink settings. For development, you might uncomment it temporarily or manually flush permalinks.

Handling the Custom Endpoint Request

Now, we need a function to process requests that match our custom endpoint. We’ll hook into the template_redirect action, which fires before WordPress determines which template to load. This is a good place to intercept requests and handle them directly.

/**
 * Handle the custom HubSpot API endpoint requests.
 */
function my_hubspot_handle_api_request() {
    global $wp_query;

    // Check if our custom query variable is set.
    $hubspot_action = $wp_query->get( 'hubspot_contact_action' );

    if ( $hubspot_action ) {
        // Prevent WordPress from loading a template.
        $wp_query->is_404 = true; // Mark as not found to prevent template loading.
        status_header( 200 ); // Set HTTP status to 200 OK.

        // Set the content type to JSON.
        header( 'Content-Type: application/json' );

        // Determine the action and call the appropriate handler.
        switch ( $hubspot_action ) {
            case 'sync':
                // Call a function to sync contacts.
                my_hubspot_sync_contacts();
                break;
            case 'get':
                // Get a specific contact (requires an ID, which we'll handle next).
                $contact_id = isset( $_GET['id'] ) ? sanitize_text_field( $_GET['id'] ) : null;
                my_hubspot_get_contact( $contact_id );
                break;
            default:
                wp_send_json_error( array( 'message' => 'Invalid action specified.' ), 400 );
                break;
        }

        // Crucially, exit to prevent further WordPress execution.
        exit;
    }
}
add_action( 'template_redirect', 'my_hubspot_handle_api_request' );

In this handler:

  • We access the captured URL segment using $wp_query->get( 'hubspot_contact_action' );.
  • If the variable is set, we set $wp_query->is_404 = true; and status_header( 200 ); to indicate a successful response and prevent WordPress from trying to load a theme template.
  • We set the Content-Type header to application/json as we’ll be returning JSON data.
  • A switch statement dispatches the request to specific handler functions (e.g., my_hubspot_sync_contacts, my_hubspot_get_contact) based on the captured action.
  • We handle potential additional parameters like id for fetching specific contacts.
  • exit; is vital to stop WordPress from continuing its normal execution flow after our custom handler has responded.

Implementing HubSpot API Interaction Functions

These functions will contain the actual logic for interacting with the HubSpot API. For security, all API keys and sensitive information should be stored securely, ideally in environment variables or a secure configuration file outside the webroot, and accessed via PHP. Never embed API keys directly in your plugin’s code.

Securely Fetching HubSpot API Credentials

A common and secure practice is to use environment variables. If your hosting environment supports it, you can define these variables. Alternatively, you can store them in a file outside your WordPress installation’s public directory.

/**
 * Get HubSpot API key from environment variable.
 *
 * @return string|false HubSpot API key or false if not set.
 */
function get_hubspot_api_key() {
    // Example: Assuming your API key is stored in an environment variable named 'HUBSPOT_API_KEY'.
    // For local development, you might use a .env file and a library like phpdotenv.
    if ( getenv( 'HUBSPOT_API_KEY' ) ) {
        return getenv( 'HUBSPOT_API_KEY' );
    }
    // Fallback for environments where getenv might not be ideal or for simpler setups.
    // Consider a secure configuration file outside the webroot.
    // For demonstration, we'll use a placeholder. In production, this MUST be secure.
    // $config_path = ABSPATH . '../config/hubspot.php'; // Example path outside webroot
    // if ( file_exists( $config_path ) ) {
    //     require $config_path;
    //     if ( defined('HUBSPOT_API_KEY') ) {
    //         return HUBSPOT_API_KEY;
    //     }
    // }
    return false; // API key not found.
}

Example: Syncing Contacts

This function would make a request to HubSpot’s API to fetch contacts and potentially process them within WordPress (e.g., store them in a custom database table, update existing records).

/**
 * Sync contacts from HubSpot.
 */
function my_hubspot_sync_contacts() {
    $api_key = get_hubspot_api_key();
    if ( ! $api_key ) {
        wp_send_json_error( array( 'message' => 'HubSpot API key not configured.' ), 500 );
        return;
    }

    // HubSpot API endpoint for contacts. Adjust URL as needed for specific HubSpot features.
    // This example uses the basic contacts endpoint.
    $hubspot_api_url = 'https://api.hubapi.com/contacts/v1/lists/all/contacts/all';
    $args = array(
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
        ),
        'timeout' => 30, // Set a reasonable timeout.
    );

    $response = wp_remote_get( $hubspot_api_url, $args );

    if ( is_wp_error( $response ) ) {
        wp_send_json_error( array( 'message' => 'HubSpot API request failed.', 'error' => $response->get_error_message() ), 500 );
        return;
    }

    $body = wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );
    $status_code = wp_remote_retrieve_response_code( $response );

    if ( $status_code !== 200 || json_last_error() !== JSON_ERROR_NONE ) {
        wp_send_json_error( array( 'message' => 'Failed to retrieve contacts from HubSpot.', 'status' => $status_code, 'response' => $data ), $status_code );
        return;
    }

    // Process the $data array here.
    // For example, loop through contacts and save them to your WordPress database.
    $processed_contacts = array();
    if ( isset( $data['contacts'] ) && is_array( $data['contacts'] ) ) {
        foreach ( $data['contacts'] as $contact ) {
            // Example: Extracting email and properties.
            $email = isset( $contact['properties']['email']['value'] ) ? $contact['properties']['email']['value'] : 'N/A';
            $firstname = isset( $contact['properties']['firstname']['value'] ) ? $contact['properties']['firstname']['value'] : '';
            $lastname = isset( $contact['properties']['lastname']['value'] ) ? $contact['properties']['lastname']['value'] : '';

            $processed_contacts[] = array(
                'email' => $email,
                'firstname' => $firstname,
                'lastname' => $lastname,
                // Add other properties as needed.
            );

            // In a real scenario, you'd likely use update_user_meta,
            // or a custom table to store/update these contacts.
        }
    }

    wp_send_json_success( array( 'message' => 'Contacts synced successfully.', 'count' => count( $processed_contacts ), 'contacts' => $processed_contacts ), 200 );
}

Example: Getting a Specific Contact

This function demonstrates fetching a single contact by ID. Note how we retrieve the ID from the query parameters.

/**
 * Get a specific contact from HubSpot by ID.
 *
 * @param string|null $contact_id The HubSpot contact ID.
 */
function my_hubspot_get_contact( $contact_id = null ) {
    $api_key = get_hubspot_api_key();
    if ( ! $api_key ) {
        wp_send_json_error( array( 'message' => 'HubSpot API key not configured.' ), 500 );
        return;
    }

    if ( empty( $contact_id ) ) {
        wp_send_json_error( array( 'message' => 'Contact ID is required.' ), 400 );
        return;
    }

    // HubSpot API endpoint for a single contact.
    $hubspot_api_url = sprintf( 'https://api.hubapi.com/contacts/v1/contact/vid/%s', intval( $contact_id ) );
    $args = array(
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
        ),
        'timeout' => 15,
    );

    $response = wp_remote_get( $hubspot_api_url, $args );

    if ( is_wp_error( $response ) ) {
        wp_send_json_error( array( 'message' => 'HubSpot API request failed.', 'error' => $response->get_error_message() ), 500 );
        return;
    }

    $body = wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );
    $status_code = wp_remote_retrieve_response_code( $response );

    if ( $status_code !== 200 || json_last_error() !== JSON_ERROR_NONE ) {
        // HubSpot returns 404 if contact not found.
        wp_send_json_error( array( 'message' => 'Failed to retrieve contact from HubSpot.', 'status' => $status_code, 'response' => $data ), $status_code );
        return;
    }

    wp_send_json_success( array( 'message' => 'Contact retrieved successfully.', 'contact' => $data ), 200 );
}

Securing the Endpoint

While the Rewrite API hides the direct API calls, the endpoint itself is still accessible. For sensitive operations, you must implement authentication and authorization checks.

Authentication Methods

  • Nonce Verification: For actions initiated by logged-in WordPress users, use nonces to protect against CSRF attacks. This involves generating a nonce on the client-side (or server-side if the request originates from the backend) and verifying it within your endpoint handler.
  • API Key/Secret for External Systems: If this endpoint is intended to be called by external applications (e.g., a separate microservice), you’ll need a more robust authentication mechanism. This could involve passing a custom API key in the request headers and verifying it against a stored secret.
  • IP Whitelisting: Restrict access to known IP addresses if the endpoint is only meant to be called from specific servers.

Example: Adding Nonce Verification

Let’s modify the my_hubspot_handle_api_request function to include nonce verification for actions that should only be performed by authenticated users.

/**
 * Handle the custom HubSpot API endpoint requests with nonce verification.
 */
function my_hubspot_handle_api_request() {
    global $wp_query;

    $hubspot_action = $wp_query->get( 'hubspot_contact_action' );

    if ( $hubspot_action ) {
        // --- Nonce Verification ---
        // Check for nonce if the action requires it (e.g., 'sync').
        // For 'get', it might be less critical depending on data sensitivity.
        if ( in_array( $hubspot_action, array( 'sync' ) ) ) {
            // Expecting nonce in $_GET or $_POST.
            $nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( $_REQUEST['_wpnonce'] ) : '';
            $action = 'hubspot_api_nonce_action'; // A unique action name for your nonce.

            if ( ! wp_verify_nonce( $nonce, $action ) ) {
                wp_send_json_error( array( 'message' => 'Nonce verification failed.' ), 403 ); // Forbidden
                exit;
            }
        }
        // --- End Nonce Verification ---

        $wp_query->is_404 = true;
        status_header( 200 );
        header( 'Content-Type: application/json' );

        switch ( $hubspot_action ) {
            case 'sync':
                my_hubspot_sync_contacts();
                break;
            case 'get':
                $contact_id = isset( $_GET['id'] ) ? sanitize_text_field( $_GET['id'] ) : null;
                my_hubspot_get_contact( $contact_id );
                break;
            default:
                wp_send_json_error( array( 'message' => 'Invalid action specified.' ), 400 );
                break;
        }
        exit;
    }
}
add_action( 'template_redirect', 'my_hubspot_handle_api_request' );

To use this, when making a request (e.g., via JavaScript AJAX), you would include the nonce:

// Example using jQuery AJAX
var data = {
    'action': 'hubspot_api_endpoint', // This is a standard WordPress AJAX action, NOT our custom endpoint action.
    'hubspot_contact_action': 'sync', // This is our custom endpoint action.
    '_wpnonce': wpApiSettings.nonce // Assuming wpApiSettings.nonce is populated with wp_create_nonce('hubspot_api_nonce_action')
};

jQuery.post(wpApiSettings.ajax_url, data, function(response) {
    console.log('Got this from the server: ' + response);
});

Important Note on AJAX vs. Rewrite API: The JavaScript example above uses wp_ajax_hubspot_api_endpoint and wp_ajax_nopriv_hubspot_api_endpoint hooks, which is the standard WordPress AJAX way. However, our goal here is to use the Rewrite API for a RESTful URL. If you are calling this endpoint from JavaScript within WordPress, you would typically use wp_localize_script to pass the correct URL and nonce. If calling from an external application, you’d pass the nonce in headers or query parameters as appropriate.

// In your plugin's main file or an admin enqueue function:
wp_enqueue_script( 'my-hubspot-script', 'path/to/your/script.js', array('jquery'), '1.0', true );

// Localize the script to pass data to JavaScript
wp_localize_script( 'my-hubspot-script', 'myHubspotApi', array(
    'apiUrl' => home_url( '/api/v1/hubspot-contacts/' ), // The base URL for your endpoint
    'syncNonce' => wp_create_nonce( 'hubspot_api_nonce_action' ), // Nonce for sync action
    // Add other nonces if needed for different actions
) );
// In your script.js file:
jQuery(document).ready(function($) {
    $('#sync-contacts-button').on('click', function() {
        var data = {
            'hubspot_contact_action': 'sync',
            '_wpnonce': myHubspotApi.syncNonce
        };

        // Construct the full URL
        var url = myHubspotApi.apiUrl + 'sync/';

        $.ajax({
            url: url,
            type: 'POST', // Or GET, depending on your handler logic
            data: data,
            success: function(response) {
                console.log('Sync successful:', response);
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.error('Sync failed:', textStatus, errorThrown, jqXHR.responseJSON);
            }
        });
    });

    $('#get-contact-button').on('click', function() {
        var contactId = $('#contact-id-input').val();
        if (!contactId) return;

        var data = {
            'hubspot_contact_action': 'get',
            'id': contactId
            // No nonce needed for 'get' in this example, but could be added.
        };

        var url = myHubspotApi.apiUrl + 'get/';

        $.ajax({
            url: url,
            type: 'GET',
            data: data,
            success: function(response) {
                console.log('Contact retrieved:', response);
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.error('Get contact failed:', textStatus, errorThrown, jqXHR.responseJSON);
            }
        });
    });
});

Error Handling and Logging

Robust error handling is critical. Use wp_send_json_error() with appropriate HTTP status codes (e.g., 400 for bad requests, 403 for forbidden, 500 for server errors). For debugging, consider using WordPress’s built-in debugging tools or a dedicated logging plugin.

// Example of logging an error
if ( is_wp_error( $response ) ) {
    error_log( 'HubSpot API Error: ' . $response->get_error_message() );
    wp_send_json_error( array( 'message' => 'HubSpot API request failed.', 'error' => $response->get_error_message() ), 500 );
    return;
}

Conclusion

By utilizing the WordPress Rewrite API, you can create clean, RESTful endpoints for your HubSpot integrations. This approach not only provides a more organized and maintainable codebase but also offers a secure proxy layer, protecting your API credentials and centralizing access control. Remember to always prioritize security by implementing proper authentication, authorization, and error handling for any API interactions.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • WordPress Development Recipe: High-efficiency server-side rendering for Gutenberg blocks using Strongly typed objects
  • Debugging Guide: Diagnosing caching race conditions in multi-site network environments with modern tools
  • Troubleshooting SQL query deadlocks in production when using modern Elementor custom widgets wrappers
  • How to analyze and reduce CPU consumption of custom Repository and Interface Structure event mediators
  • How to build custom Elementor custom widgets extensions utilizing modern Cron API (wp_schedule_event) schemas

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (38)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (2)
  • WordPress Plugin Development (3)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • WordPress Development Recipe: High-efficiency server-side rendering for Gutenberg blocks using Strongly typed objects
  • Debugging Guide: Diagnosing caching race conditions in multi-site network environments with modern tools
  • Troubleshooting SQL query deadlocks in production when using modern Elementor custom widgets wrappers

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala