• 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 Twilio SMS Gateway endpoints into WordPress custom plugins using WordPress Database Class ($wpdb)

How to securely integrate Twilio SMS Gateway endpoints into WordPress custom plugins using WordPress Database Class ($wpdb)

Setting Up Your Twilio Credentials Securely

Before integrating Twilio SMS into your WordPress plugin, it’s paramount to handle your API credentials with the utmost security. Hardcoding these sensitive values directly into your plugin files is a critical security vulnerability. Instead, we’ll leverage WordPress’s built-in options API to store these credentials. This allows for easy management via the WordPress admin interface and keeps them out of your version control system.

You will need your Twilio Account SID and Auth Token. These can be found on your Twilio Console dashboard. For this example, we’ll assume you’ve created two options in the WordPress database:

  • twilio_account_sid: Stores your Twilio Account SID.
  • twilio_auth_token: Stores your Twilio Auth Token.

Storing Credentials Using WordPress Options API

The add_option(), update_option(), and get_option() functions are your primary tools here. For initial setup or updates, you might use something like this within an activation hook or a dedicated settings page:

// Example: Storing credentials (ideally done via a settings page, not directly in code)
function my_twilio_plugin_activate() {
    // Replace with your actual Twilio credentials (DO NOT hardcode in production)
    $account_sid = 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
    $auth_token  = 'your_auth_token_here';

    // Add options if they don't exist
    if ( false === get_option( 'twilio_account_sid' ) ) {
        add_option( 'twilio_account_sid', $account_sid );
    } else {
        update_option( 'twilio_account_sid', $account_sid );
    }

    if ( false === get_option( 'twilio_auth_token' ) ) {
        add_option( 'twilio_auth_token', $auth_token );
    } else {
        update_option( 'twilio_auth_token', $auth_token );
    }
}
register_activation_hook( __FILE__, 'my_twilio_plugin_activate' );

// To retrieve them later:
$sid = get_option( 'twilio_account_sid' );
$token = get_option( 'twilio_auth_token' );

For a production-ready plugin, you would create a WordPress settings page to allow users to input and manage these credentials securely. This involves using the Settings API (register_setting(), add_settings_section(), add_settings_field()).

Interacting with the Twilio API Using $wpdb

While WordPress provides the Options API for storing settings, for more complex data related to SMS messages (e.g., logs, delivery statuses, message queues), you’ll want to interact directly with the database using the global $wpdb object. This object is a robust wrapper around the WordPress database connection, providing methods for safe and efficient database operations.

Creating a Custom Table for SMS Logs

Let’s create a custom table to log outgoing SMS messages. This table will store details like the recipient, message body, status, and timestamp. We’ll use a plugin activation hook to create this table if it doesn’t exist.

global $wpdb;
$table_name = $wpdb->prefix . 'twilio_sms_logs'; // e.g., wp_twilio_sms_logs

// Check if the table already exists
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) !== $table_name ) {
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        recipient varchar(20) NOT NULL,
        message text NOT NULL,
        twilio_message_sid varchar(255) NULL,
        status varchar(50) NOT NULL DEFAULT 'queued',
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY  (id),
        KEY idx_status (status)
    ) $charset_collate;";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );
}

This code snippet should be placed within your plugin’s main file and registered with register_activation_hook(). The dbDelta() function is crucial here as it handles table creation and updates intelligently.

Logging an Outgoing SMS Message

When you send an SMS via Twilio, you’ll want to log the attempt and eventually the result. Here’s how you can insert a record into your custom log table using $wpdb->insert(), which automatically handles escaping.

function log_twilio_sms( $recipient, $message_body, $status = 'queued', $twilio_sid = null ) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'twilio_sms_logs';

    $data = array(
        'recipient' => sanitize_text_field( $recipient ),
        'message'   => sanitize_textarea_field( $message_body ),
        'status'    => sanitize_text_field( $status ),
        'twilio_message_sid' => ( $twilio_sid ) ? sanitize_text_field( $twilio_sid ) : null,
    );

    $format = array(
        '%s', // recipient
        '%s', // message
        '%s', // status
        '%s', // twilio_message_sid
    );

    $result = $wpdb->insert( $table_name, $data, $format );

    if ( false === $result ) {
        // Log an error or handle the database insertion failure
        error_log( "Failed to insert SMS log: " . $wpdb->last_error );
        return false;
    }

    return $wpdb->insert_id; // Return the ID of the newly inserted row
}

Updating SMS Message Status

After Twilio processes the message, you might receive a webhook or need to query the status. You can update the log entry using $wpdb->update().

function update_twilio_sms_status( $log_id, $new_status, $twilio_sid = null ) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'twilio_sms_logs';

    $where = array( 'id' => absint( $log_id ) );
    $where_format = array( '%d' );

    $data_to_update = array(
        'status' => sanitize_text_field( $new_status ),
    );
    $data_format = array( '%s' );

    if ( $twilio_sid ) {
        $data_to_update['twilio_message_sid'] = sanitize_text_field( $twilio_sid );
        $data_format[] = '%s'; // Add format for twilio_message_sid
    }

    $result = $wpdb->update( $table_name, $data_to_update, $where, $data_format, $where_format );

    if ( false === $result ) {
        // Log an error or handle the database update failure
        error_log( "Failed to update SMS log status for ID {$log_id}: " . $wpdb->last_error );
        return false;
    }

    return true;
}

Retrieving SMS Logs

You can fetch logs for display in the admin area or for analysis using $wpdb->get_results().

function get_sms_logs( $status = null, $limit = 10 ) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'twilio_sms_logs';

    $query = "SELECT * FROM $table_name";
    $args = array();

    if ( $status ) {
        $query .= " WHERE status = %s";
        $args[] = sanitize_text_field( $status );
    }

    $query .= " ORDER BY created_at DESC LIMIT %d";
    $args[] = absint( $limit );

    // Prepare and execute the query
    $sql = $wpdb->prepare( $query, $args );
    $results = $wpdb->get_results( $sql );

    if ( $wpdb->last_error ) {
        error_log( "Error retrieving SMS logs: " . $wpdb->last_error );
        return array(); // Return empty array on error
    }

    return $results;
}

// Example usage:
// $recent_logs = get_sms_logs( null, 20 ); // Get last 20 logs of any status
// $failed_logs = get_sms_logs( 'failed', 5 ); // Get last 5 failed logs

Integrating with the Twilio PHP SDK

While you can interact with the Twilio API directly via cURL, using the official Twilio PHP SDK is highly recommended for robustness and ease of use. You’ll need to include the SDK in your plugin.

Including the Twilio PHP SDK

The best practice is to manage dependencies using Composer. If your plugin uses Composer, you can install the SDK with:

composer require twilio/sdk

Then, include the autoloader in your plugin:

// In your plugin's main file or an included file
require_once __DIR__ . '/vendor/autoload.php';

use Twilio\Rest\Client;

// ... rest of your plugin code

Sending an SMS Message

Here’s a function that uses the Twilio SDK to send an SMS, incorporating the secure retrieval of credentials and logging the attempt.

function send_twilio_sms( $to_number, $message_body ) {
    // Retrieve credentials securely
    $account_sid = get_option( 'twilio_account_sid' );
    $auth_token  = get_option( 'twilio_auth_token' );
    $from_number = get_option( 'twilio_from_number' ); // Assume you have this option set

    if ( empty( $account_sid ) || empty( $auth_token ) || empty( $from_number ) ) {
        error_log( 'Twilio credentials or from number are not set in WordPress options.' );
        return false;
    }

    try {
        // Initialize the Twilio client
        $client = new Client( $account_sid, $auth_token );

        // Log the outgoing message attempt BEFORE sending
        $log_id = log_twilio_sms( $to_number, $message_body, 'queued' );
        if ( ! $log_id ) {
            error_log( 'Failed to log SMS before sending.' );
            // Decide if you still want to attempt sending or fail here
        }

        // Send the message
        $message = $client->messages->create(
            $to_number, // To number
            array(
                'from' => $from_number,
                'body' => $message_body
            )
        );

        // Update the log with Twilio's message SID and status
        if ( $log_id ) {
            update_twilio_sms_status( $log_id, $message->status, $message->sid );
        }

        // Log success or failure based on Twilio's response
        if ( $message->status === 'sent' || $message->status === 'delivered' ) {
            // Success
            return $message->sid;
        } else {
            // Handle other statuses like 'failed', 'undelivered'
            error_log( "Twilio SMS sending failed. SID: {$message->sid}, Status: {$message->status}" );
            if ( $log_id ) {
                 update_twilio_sms_status( $log_id, $message->status ); // Update with final status
            }
            return false;
        }

    } catch ( \Exception $e ) {
        error_log( "Twilio API Error: " . $e->getMessage() );
        // Update log with error status if log_id exists
        if ( isset( $log_id ) && $log_id ) {
            update_twilio_sms_status( $log_id, 'error' );
        }
        return false;
    }
}

// Example usage:
// $success = send_twilio_sms( '+15551234567', 'Hello from your WordPress site!' );
// if ( $success ) {
//     echo "SMS sent successfully! SID: " . $success;
// } else {
//     echo "Failed to send SMS.";
// }

Handling Twilio Webhooks

Twilio can send status updates (e.g., delivered, failed) and incoming messages via webhooks. You’ll need to create a public-facing endpoint in your WordPress site to receive these POST requests.

Creating a Webhook Endpoint

Use WordPress’s rewrite rules and a custom query variable to route webhook requests to a specific function. This is a cleaner approach than relying on generic AJAX handlers for external services.

// Add rewrite rule and query var
add_action( 'init', 'my_twilio_add_rewrite_rule' );
function my_twilio_add_rewrite_rule() {
    add_rewrite_rule(
        '^twilio-webhook/?$', // Regex for the URL path
        'index.php?twilio_webhook=1', // Rewrite to index.php with a query var
        'top'
    );
    add_rewrite_tag( '%twilio_webhook%', '1' );
}

// Flush rewrite rules on plugin activation/deactivation
register_activation_hook( __FILE__, 'my_twilio_flush_rewrites' );
register_deactivation_hook( __FILE__, 'my_twilio_flush_rewrites' );
function my_twilio_flush_rewrites() {
    my_twilio_add_rewrite_rule();
    flush_rewrite_rules();
}

// Handle the webhook request
add_action( 'template_redirect', 'my_twilio_handle_webhook' );
function my_twilio_handle_webhook() {
    if ( get_query_var( 'twilio_webhook' ) ) {
        // Ensure the request is a POST request
        if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) {
            status_header( 405 ); // Method Not Allowed
            echo 'Method Not Allowed';
            exit;
        }

        // Verify Twilio Signature (CRITICAL for security)
        $twilio_signature = isset( $_SERVER['HTTP_X_TWILIO_SIGNATURE'] ) ? $_SERVER['HTTP_X_TWILIO_SIGNATURE'] : '';
        $auth_token = get_option( 'twilio_auth_token' ); // Your Twilio Auth Token
        $url = home_url( $_SERVER['REQUEST_URI'] ); // The URL Twilio posted to
        $params = $_POST; // All POST parameters

        // Rebuild the URL if it contains query parameters (though typically webhooks are to root path)
        if ( strpos($url, '?') !== false ) {
            $url = substr($url, 0, strpos($url, '?'));
        }

        // Use Twilio's helper library to validate the signature
        // You might need to include the SDK or a specific helper file here
        // For simplicity, let's assume you have Twilio\Security\RequestValidator available
        // If not using Composer, you'd need to manually include Twilio's security class.

        // Example using Twilio's helper library (requires SDK)
        // Make sure to require the autoloader if using Composer
        // require_once __DIR__ . '/vendor/autoload.php';
        // use Twilio\Security\RequestValidator;
        // $validator = new RequestValidator($auth_token);
        // $is_valid = $validator->validate($url, $params, $twilio_signature);

        // Placeholder for validation logic if not using the SDK directly here
        // In production, ALWAYS validate the signature.
        $is_valid = true; // Replace with actual validation

        if ( ! $is_valid ) {
            status_header( 403 ); // Forbidden
            echo 'Invalid signature';
            exit;
        }

        // Process the webhook data
        $message_sid = isset( $_POST['MessageSid'] ) ? sanitize_text_field( $_POST['MessageSid'] ) : '';
        $message_status = isset( $_POST['MessageStatus'] ) ? sanitize_text_field( $_POST['MessageStatus'] ) : '';
        $to_number = isset( $_POST['To'] ) ? sanitize_text_field( $_POST['To'] ) : '';
        $from_number = isset( $_POST['From'] ) ? sanitize_text_field( $_POST['From'] ) : '';
        $body = isset( $_POST['Body'] ) ? sanitize_textarea_field( $_POST['Body'] ) : ''; // For incoming messages

        if ( ! empty( $message_sid ) && ! empty( $message_status ) ) {
            // Update the status in our logs
            // We need to find the log entry by MessageSid.
            // Add a unique index on twilio_message_sid in your table for efficiency.
            global $wpdb;
            $table_name = $wpdb->prefix . 'twilio_sms_logs';

            $updated = $wpdb->update(
                $table_name,
                array( 'status' => $message_status ),
                array( 'twilio_message_sid' => $message_sid ),
                array( '%s' ), // format for status
                array( '%s' )  // format for twilio_message_sid
            );

            if ( $updated === false ) {
                error_log( "Webhook: Failed to update status for MessageSid {$message_sid}: " . $wpdb->last_error );
            } elseif ( $updated === 0 ) {
                 error_log( "Webhook: No matching log entry found for MessageSid {$message_sid} to update." );
            } else {
                // Log successful update if needed
            }

        } elseif ( ! empty( $from_number ) && ! empty( $body ) ) {
            // This is an incoming message
            // Handle incoming messages logic here (e.g., log, reply, trigger action)
            error_log( "Incoming SMS from {$from_number}: {$body}" );
            // Example: Log incoming message
            // log_incoming_sms( $from_number, $body );
        }

        // Respond to Twilio with a 200 OK status
        status_header( 200 );
        echo 'OK';
        exit;
    }
}

Security Note: The X-Twilio-Signature header validation is absolutely critical. Without it, anyone could send POST requests to your webhook URL, impersonating Twilio and potentially manipulating your database. Ensure you correctly implement this validation using Twilio’s provided libraries or by carefully following their documentation.

Best Practices and Considerations

  • Error Handling: Implement robust error logging for API calls, database operations, and webhook processing. Use error_log() or a more sophisticated logging solution.
  • Rate Limiting: Be mindful of Twilio’s rate limits and implement retry mechanisms with exponential backoff if necessary.
  • Idempotency: Design your webhook handler to be idempotent. Receiving the same webhook twice should not cause duplicate actions or data corruption.
  • Security: Always validate Twilio’s signature for webhooks. Never expose your Auth Token publicly. Use WordPress’s options API or environment variables for credentials.
  • User Experience: Provide clear feedback to users when SMS messages are sent or if there are errors. Consider a dedicated admin page for viewing SMS logs and managing settings.
  • Internationalization: Ensure your message bodies are translatable if your plugin supports multiple languages.
  • Asynchronous Processing: For high-volume SMS sending, consider using a background job queue (e.g., WP-Cron with a queue plugin, or a dedicated queue system) to avoid blocking user requests.

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

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store
  • How to refactor legacy event ticket registers queries using modern WP_Query and custom Transient caching
  • Step-by-Step Guide: Offloading high-frequency member profile directories metadata writes to a Redis KV store

Categories

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

Recent Posts

  • Debugging Guide: Diagnosing PHP-FPM child process pool exhaustion in multi-site network environments with modern tools
  • Debugging and Resolving complex namespace class loading collisions issues during heavy concurrent database traffic
  • Step-by-Step Guide: Offloading high-frequency customer support tickets metadata writes to a Redis KV store

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (726)
  • Debugging & Troubleshooting (662)
  • Security & Compliance (647)
  • SEO & Growth (492)

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