• 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 Salesforce CRM endpoints into WordPress custom plugins using Shortcode API

How to securely integrate Salesforce CRM endpoints into WordPress custom plugins using Shortcode API

Establishing Secure Salesforce API Connections in WordPress

Integrating Salesforce CRM data into a WordPress site via custom plugins offers immense value, enabling dynamic content display and streamlined workflows. However, the security of these integrations is paramount. This guide details a robust approach to securely connecting to Salesforce API endpoints using WordPress’s Shortcode API, focusing on OAuth 2.0 for authentication and best practices for credential management.

Prerequisites and Setup

Before diving into the code, ensure you have the following:

  • A Salesforce Developer Edition account or a sandbox environment.
  • A connected app configured in Salesforce to enable OAuth 2.0. Note down the Consumer Key (Client ID) and Consumer Secret (Client Secret).
  • A WordPress installation with administrative access.
  • Basic understanding of PHP and WordPress plugin development.

Salesforce Connected App Configuration

In Salesforce, navigate to Setup > Apps > App Manager. Create a new Connected App. Key configurations include:

  • Enable OAuth Settings: Check this box.
  • Callback URL: This is crucial. For local development, you might use something like http://localhost/wp-admin/admin-ajax.php?action=salesforce_oauth_callback. For production, use your WordPress site’s URL followed by the same action.
  • Selected OAuth Scopes: Choose scopes appropriate for your integration, e.g., api, refresh_token, offline_access.

Once saved, you will see the Consumer Key and Consumer Secret. Treat the Consumer Secret with the same confidentiality as a password.

WordPress Plugin Structure and Credential Storage

We’ll create a simple WordPress plugin. For storing sensitive credentials like the Consumer Secret and the Salesforce access/refresh tokens, avoid hardcoding them directly in the plugin files. Instead, leverage WordPress’s options API and consider using environment variables or a secure configuration file outside the webroot for production environments. For this example, we’ll use the options API for simplicity, but remember to sanitize and validate all inputs.

Plugin Initialization and Settings Page

The plugin will need a way to store and retrieve the Salesforce API credentials. A simple settings page is a good starting point.

salesforce-integration.php (Main Plugin File)

<?php
/**
 * Plugin Name: Salesforce Integration
 * Description: Securely integrates Salesforce CRM endpoints into WordPress.
 * Version: 1.0
 * Author: Your Name
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

define( 'SALESFORCE_INTEGRATION_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
define( 'SALESFORCE_INTEGRATION_PLUGIN_URL', plugin_dir_url( __FILE__ ) );

// Include necessary files
require_once SALESFORCE_INTEGRATION_PLUGIN_PATH . 'includes/class-salesforce-oauth.php';
require_once SALESFORCE_INTEGRATION_PLUGIN_PATH . 'includes/class-salesforce-shortcode.php';
require_once SALESFORCE_INTEGRATION_PLUGIN_PATH . 'admin/class-salesforce-settings.php';

// Initialize classes
function initialize_salesforce_integration() {
    new Salesforce_Settings();
    new Salesforce_OAuth();
    new Salesforce_Shortcode();
}
add_action( 'plugins_loaded', 'initialize_salesforce_integration' );

// Activation hook to set default options
register_activation_hook( __FILE__, 'salesforce_integration_activate' );
function salesforce_integration_activate() {
    if ( false === get_option( 'sf_integration_consumer_key' ) ) {
        add_option( 'sf_integration_consumer_key', '' );
    }
    if ( false === get_option( 'sf_integration_consumer_secret' ) ) {
        add_option( 'sf_integration_consumer_secret', '' );
    }
    if ( false === get_option( 'sf_integration_instance_url' ) ) {
        add_option( 'sf_integration_instance_url', '' ); // e.g., https://yourdomain.my.salesforce.com
    }
    if ( false === get_option( 'sf_integration_redirect_uri' ) ) {
        add_option( 'sf_integration_redirect_uri', admin_url( 'admin-ajax.php?action=salesforce_oauth_callback' ) );
    }
}
?>

admin/class-salesforce-settings.php


        <div class="wrap">
            <h1>Salesforce Integration Settings</h1>
            <form method="post" action="options.php">
                
            </form>
            <p><a href="" class="button button-primary">Connect to Salesforce</a></p>
            <p>After connecting, you may need to refresh your access token periodically. The system attempts to do this automatically.</p>
        </div>
        


OAuth 2.0 Authorization Code Flow Implementation

The OAuth 2.0 Authorization Code flow is the recommended method for server-side web applications like WordPress plugins. It involves redirecting the user to Salesforce to grant permission, then exchanging an authorization code for an access token.

includes/class-salesforce-oauth.php

load_settings();

        add_action( 'wp_ajax_salesforce_oauth_init', array( $this, 'initiate_oauth' ) );
        add_action( 'wp_ajax_salesforce_oauth_callback', array( $this, 'handle_oauth_callback' ) );
        add_action( 'admin_init', array( $this, 'maybe_refresh_token' ) );
    }

    private function load_settings() {
        $options = get_option( 'sf_integration_settings' );
        $this->consumer_key = isset( $options['consumer_key'] ) ? $options['consumer_key'] : '';
        $this->consumer_secret = isset( $options['consumer_secret'] ) ? $options['consumer_secret'] : '';
        $this->instance_url = isset( $options['instance_url'] ) ? rtrim( $options['instance_url'], '/' ) : '';
        $this->redirect_uri = isset( $options['redirect_uri'] ) ? $options['redirect_uri'] : '';

        if ( empty( $this->consumer_key ) || empty( $this->consumer_secret ) || empty( $this->instance_url ) ) {
            // Optionally display an admin notice if settings are incomplete
            add_action( 'admin_notices', array( $this, 'admin_notice_missing_settings' ) );
        }
    }

    public function admin_notice_missing_settings() {
        ?>
        <div class="notice notice-error is-dismissible">
            <p><?php _e( 'Salesforce Integration: Please configure your Connected App credentials in the Salesforce Integration settings page.', 'salesforce-integration' ); ?></p>
        </div>
        consumer_key ) || empty( $this->consumer_secret ) || empty( $this->instance_url ) || empty( $this->redirect_uri ) ) {
            wp_die( 'Salesforce API credentials are not fully configured. Please check your settings.' );
        }

        $auth_url = $this->instance_url . '/services/oauth2/authorize';
        $params = array(
            'response_type' => 'code',
            'client_id'     => $this->consumer_key,
            'redirect_uri'  => $this->redirect_uri,
            'scope'         => 'api refresh_token offline_access', // Adjust scopes as needed
        );

        $auth_url = add_query_arg( $params, $auth_url );
        wp_redirect( $auth_url );
        exit;
    }

    public function handle_oauth_callback() {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'You do not have permission to perform this action.' );
        }

        if ( isset( $_GET['error'] ) ) {
            // Handle errors from Salesforce
            $error_message = sanitize_text_field( $_GET['error_description'] ?? $_GET['error'] );
            wp_die( 'Salesforce OAuth Error: ' . $error_message );
        }

        if ( ! isset( $_GET['code'] ) ) {
            wp_die( 'Invalid OAuth callback. Missing authorization code.' );
        }

        $auth_code = sanitize_text_field( $_GET['code'] );
        $token_url = $this->instance_url . '/services/oauth2/token';

        $body = array(
            'grant_type'    => 'authorization_code',
            'code'          => $auth_code,
            'client_id'     => $this->consumer_key,
            'client_secret' => $this->consumer_secret,
            'redirect_uri'  => $this->redirect_uri,
        );

        $response = wp_remote_post( $token_url, array(
            'method'    => 'POST',
            'body'      => $body,
            'headers'   => array( 'Content-Type' => 'application/x-www-form-urlencoded' ),
            'timeout'   => 60,
        ) );

        if ( is_wp_error( $response ) ) {
            wp_die( 'Error obtaining access token: ' . $response->get_error_message() );
        }

        $body = wp_remote_retrieve_body( $response );
        $token_data = json_decode( $body, true );

        if ( json_last_error() !== JSON_ERROR_NONE || ! isset( $token_data['access_token'] ) ) {
            wp_die( 'Failed to parse token response or token not found. Response: ' . esc_html( $body ) );
        }

        // Store tokens securely
        update_option( $this->auth_token_option_name, $token_data['access_token'] );
        update_option( $this->refresh_token_option_name, $token_data['refresh_token'] );
        update_option( $this->token_expiry_option_name, time() + intval( $token_data['expires_in'] ) );

        // Redirect back to settings page or a success page
        wp_redirect( admin_url( 'options-general.php?page=salesforce-integration&message=oauth_success' ) );
        exit;
    }

    public function get_access_token() {
        $access_token = get_option( $this->auth_token_option_name );
        $refresh_token = get_option( $this->refresh_token_option_name );
        $expiry_time = get_option( $this->token_expiry_option_name );

        // Check if token is expired or missing
        if ( empty( $access_token ) || empty( $refresh_token ) || ( $expiry_time && time() >= $expiry_time ) ) {
            if ( ! empty( $refresh_token ) ) {
                return $this->refresh_access_token( $refresh_token );
            } else {
                // No refresh token available, prompt user to re-authenticate
                return false;
            }
        }

        return $access_token;
    }

    private function refresh_access_token( $refresh_token ) {
        if ( empty( $this->consumer_key ) || empty( $this->consumer_secret ) || empty( $this->instance_url ) ) {
            return false; // Cannot refresh without credentials
        }

        $token_url = $this->instance_url . '/services/oauth2/token';
        $body = array(
            'grant_type'    => 'refresh_token',
            'refresh_token' => $refresh_token,
            'client_id'     => $this->consumer_key,
            'client_secret' => $this->consumer_secret,
        );

        $response = wp_remote_post( $token_url, array(
            'method'    => 'POST',
            'body'      => $body,
            'headers'   => array( 'Content-Type' => 'application/x-www-form-urlencoded' ),
            'timeout'   => 60,
        ) );

        if ( is_wp_error( $response ) ) {
            error_log( 'Salesforce Token Refresh Error: ' . $response->get_error_message() );
            return false;
        }

        $body = wp_remote_retrieve_body( $response );
        $token_data = json_decode( $body, true );

        if ( json_last_error() !== JSON_ERROR_NONE || ! isset( $token_data['access_token'] ) ) {
            error_log( 'Salesforce Token Refresh Failed. Response: ' . esc_html( $body ) );
            // If refresh token is invalid, clear stored tokens
            if ( isset( $token_data['error'] ) && $token_data['error'] === 'invalid_grant' ) {
                delete_option( $this->auth_token_option_name );
                delete_option( $this->refresh_token_option_name );
                delete_option( $this->token_expiry_option_name );
            }
            return false;
        }

        // Update stored tokens
        update_option( $this->auth_token_option_name, $token_data['access_token'] );
        update_option( $this->refresh_token_option_name, $token_data['refresh_token'] ?? $refresh_token ); // Keep old refresh token if new one not provided
        update_option( $this->token_expiry_option_name, time() + intval( $token_data['expires_in'] ) );

        return $token_data['access_token'];
    }

    // This function is called on admin_init to proactively refresh tokens if needed.
    public function maybe_refresh_token() {
        if ( is_admin() && current_user_can( 'manage_options' ) ) {
            $expiry_time = get_option( $this->token_expiry_option_name );
            // Refresh if token expires within the next hour (3600 seconds)
            if ( $expiry_time && ( time() + 3600 ) >= $expiry_time ) {
                $refresh_token = get_option( $this->refresh_token_option_name );
                if ( $refresh_token ) {
                    $this->refresh_access_token( $refresh_token );
                }
            }
        }
    }

    public function get_salesforce_api_url() {
        return $this->instance_url . '/services/data/';
    }
}
?>

Creating a Shortcode to Fetch Salesforce Data

Now, let's create a shortcode that users can place in their WordPress posts or pages to display Salesforce data. This shortcode will use the authenticated Salesforce API client.

includes/class-salesforce-shortcode.php

salesforce_oauth = new Salesforce_OAuth(); // Instantiate OAuth class to access its methods
        add_shortcode( 'salesforce_account_list', array( $this, 'render_account_list_shortcode' ) );
        add_shortcode( 'salesforce_contact_search', array( $this, 'render_contact_search_shortcode' ) );
    }

    public function render_account_list_shortcode( $atts ) {
        // Shortcode attributes (e.g., [salesforce_account_list limit="10"])
        $atts = shortcode_atts( array(
            'limit' => 10,
        ), $atts, 'salesforce_account_list' );

        $limit = intval( $atts['limit'] );
        $access_token = $this->salesforce_oauth->get_access_token();

        if ( ! $access_token ) {
            return '<p>Error: Could not authenticate with Salesforce. Please connect your account.</p>';
        }

        $instance_url = $this->salesforce_oauth->get_salesforce_api_url();
        $api_endpoint = $instance_url . 'v58.0/query/?q=' . urlencode( "SELECT Id, Name, Industry, Phone FROM Account ORDER BY Name LIMIT {$limit}" ); // Use a specific API version

        $response = wp_remote_get( $api_endpoint, array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $access_token,
                'Content-Type'  => 'application/json',
            ),
            'timeout' => 30,
        ) );

        if ( is_wp_error( $response ) ) {
            return '<p>Error fetching accounts: ' . esc_html( $response->get_error_message() ) . '</p>';
        }

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

        if ( json_last_error() !== JSON_ERROR_NONE || ! isset( $data['records'] ) ) {
            return '<p>Error processing Salesforce response. Response: ' . esc_html( $body ) . '</p>';
        }

        // Start output buffering
        ob_start();
        ?>
        <div class="salesforce-accounts">
            <h3>Salesforce Accounts</h3>
            <ul>
                
                    <li>
                        <strong><?php echo esc_html( $account['Name'] ); ?></strong>
                        (Industry: <?php echo esc_html( $account['Industry'] ?? 'N/A' ); ?>, Phone: <?php echo esc_html( $account['Phone'] ?? 'N/A' ); ?>)
                    </li>
                
            </ul>
        </div>
        
        <div class="salesforce-contact-search">
            <h3>Search Salesforce Contacts</h3>
            <form method="post" action="">
                <input type="text" name="sf_contact_search_name" placeholder="Enter Contact Name" />
                <input type="submit" name="sf_contact_search_submit" value="Search" class="button" />
                <?php wp_nonce_field( 'sf_contact_search_nonce_action', 'sf_contact_search_nonce_field' ); ?>
            </form>
            <div id="sf-contact-results">
                display_contact_search_results( $search_name );
                    } else {
                        echo '<p>Please enter a name to search.</p>';
                    }
                }
                ?>
            </div>
        </div>
        salesforce_oauth->get_access_token();
        if ( ! $access_token ) {
            echo '<p>Error: Could not authenticate with Salesforce.</p>';
            return;
        }

        $instance_url = $this->salesforce_oauth->get_salesforce_api_url();
        // Using SOSL (Salesforce Object Search Language) for flexible searching
        $search_query = urlencode( "FIND {$name} IN ALL FIELDS RETURNING Contact(Id, Name, Email, Phone, Account.Name)" );
        $api_endpoint = $instance_url . 'v58.0/search/?q=' . $search_query;

        $response = wp_remote_get( $api_endpoint, array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $access_token,
                'Content-Type'  => 'application/json',
            ),
            'timeout' => 30,
        ) );

        if ( is_wp_error( $response ) ) {
            echo '<p>Error fetching contacts: ' . esc_html( $response->get_error_message() ) . '</p>';
            return;
        }

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

        if ( json_last_error() !== JSON_ERROR_NONE || ! isset( $data['searchRecords'] ) ) {
            echo '<p>Error processing Salesforce response. Response: ' . esc_html( $body ) . '</p>';
            return;
        }

        if ( empty( $data['searchRecords'] ) ) {
            echo '<p>No contacts found matching your search criteria.</p>';
            return;
        }

        ?>
        <h4>Search Results</h4>
        <table class="wp-list-table widefat fixed striped">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Phone</th>
                    <th>Account</th>
                </tr>
            </thead>
            <tbody>
                
                    <tr>
                        <td><?php echo esc_html( $contact['Name'] ); ?></td>
                        <td><?php echo esc_html( $contact['Email'] ?? 'N/A' ); ?></td>
                        <td><?php echo esc_html( $contact['Phone'] ?? 'N/A' ); ?></td>
                        <td><?php echo esc_html( $contact['Account']['Name'] ?? 'N/A' ); ?></td>
                    </tr>
                
            </tbody>
        </table>
        


Usage in WordPress

Once the plugin is activated and the Salesforce credentials are set up via the WordPress admin menu (Options > Salesforce Integration), you can use the shortcodes:

  • To display a list of Salesforce Accounts: [salesforce_account_list limit="5"]
  • To display the contact search form: [salesforce_contact_search]

When a user first encounters a shortcode that requires authentication, they will be prompted to click the "Connect to Salesforce" button on the settings page. After successful OAuth, the shortcodes will function as expected.

Security Considerations and Best Practices

  • Credential Storage: For production, avoid storing secrets directly in the WordPress database options table. Use environment variables (e.g., via a wp-config.php modification or a plugin that loads them) or a secure configuration file outside the web root.
  • HTTPS: Ensure your WordPress site and Salesforce are accessed over HTTPS to protect data in transit.
  • OAuth Scopes: Grant only the necessary OAuth scopes to your Connected App. Avoid overly broad permissions.
  • API Versioning: Pin your API calls to a specific Salesforce API version (e.g., v58.0) to prevent unexpected changes when Salesforce updates its API.
  • Error Handling: Implement robust error handling for API requests, including network issues, authentication failures, and invalid responses. Log errors for debugging.
  • Rate Limiting: Be mindful of Salesforce API rate limits. Implement caching and efficient querying to avoid hitting limits.
  • Input Sanitization: Always sanitize user inputs before using them in API queries or displaying them. Use WordPress functions like sanitize_text_field, esc_html, and esc_url.
  • Nonce Verification: For any form submissions handled via AJAX or POST requests, always use nonces to prevent CSRF attacks.
  • Token Management: Regularly review and rotate Salesforce access and refresh tokens. Implement automated refresh mechanisms as shown.

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

  • Troubleshooting namespace class loading collisions in production when using modern Sage Roots modern environments wrappers
  • Troubleshooting WooCommerce hook execution loops in production when using modern Classic Core PHP wrappers
  • Implementing automated compliance reporting for custom internal server status logs ledgers using dompdf library
  • Step-by-Step Guide to building a custom CSV bulk exporter block for Gutenberg using SolidJS high-performance reactive components
  • Troubleshooting Zend memory limit exceed in production when using modern Carbon Fields custom wrappers wrappers

Categories

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

Recent Posts

  • Troubleshooting namespace class loading collisions in production when using modern Sage Roots modern environments wrappers
  • Troubleshooting WooCommerce hook execution loops in production when using modern Classic Core PHP wrappers
  • Implementing automated compliance reporting for custom internal server status logs ledgers using dompdf library

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (824)
  • Debugging & Troubleshooting (609)
  • Security & Compliance (587)
  • 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