• 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 » WordPress Development Recipe: Real-time custom event triggers using WebSockets and Cron API (wp_schedule_event)

WordPress Development Recipe: Real-time custom event triggers using WebSockets and Cron API (wp_schedule_event)

Leveraging WordPress Cron and WebSockets for Real-time Event Notifications

This recipe outlines a robust method for triggering custom events in WordPress in near real-time, pushing notifications to connected clients via WebSockets. We’ll combine the reliability of the WordPress Cron API (specifically wp_schedule_event) for scheduled checks with the immediacy of WebSockets for client-side updates. This approach is ideal for scenarios like live activity feeds, status updates on long-running processes, or any situation where immediate feedback is crucial without constant polling.

Setting Up the WebSocket Server

For this example, we’ll use Ratchet, a popular PHP WebSocket library. You’ll need to install it via Composer. Ensure your PHP environment is set up to run long-running processes.

Composer Installation

Navigate to your WordPress root directory or a dedicated plugin directory and run:

composer require cboden/ratchet

Basic WebSocket Server Implementation

Create a file, for instance, websocket_server.php, in your WordPress root or a suitable location. This script will manage WebSocket connections and broadcast messages.

<?php
// websocket_server.php
require dirname(__DIR__) . '/vendor/autoload.php'; // Adjust path as necessary

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;

class EventBroadcaster implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
        // Store the new connection to send messages to later
        $this->clients->attach($conn);
        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        // This example doesn't expect messages from clients, but you could implement logic here.
        echo sprintf('Message received from %d: %s' . "\n", $from->resourceId, $msg);
    }

    public function onClose(ConnectionInterface $conn) {
        // The connection is closed, remove it, as we'll not send it further messages
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }

    public function broadcast($msg) {
        $encodedMsg = json_encode($msg);
        foreach ($this->clients as $client) {
            // Only send if the client is not the sender (if applicable)
            // if ($from !== $client) {
                $client->send($encodedMsg);
            // }
        }
        echo "Broadcasted: " . $encodedMsg . "\n";
    }
}

// Create a WebSocket server
$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new EventBroadcaster()
        )
    ),
    8080 // Port to listen on
);

echo "WebSocket server started on port 8080...\n";
$server->run();

Running the WebSocket Server

To start the server, execute the script from your terminal:

php websocket_server.php

Keep this terminal window open. For production, you’ll want to run this as a daemon using tools like systemd or supervisor.

Implementing the WordPress Cron Job

We’ll create a custom plugin to manage our scheduled event and trigger the WebSocket broadcast. This keeps our logic organized and separate from theme files.

Plugin Structure

Create a new directory in wp-content/plugins/, e.g., realtime-events. Inside, create the main plugin file, e.g., realtime-events.php.

Plugin Activation: Scheduling the Event

On plugin activation, we’ll schedule our recurring event. We’ll use a 5-minute interval for demonstration.

<?php
/*
Plugin Name: Realtime Events
Description: Triggers custom events and pushes them via WebSockets.
Version: 1.0
Author: Your Name
*/

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

// Define constants for clarity
define( 'REALTIME_EVENTS_CRON_HOOK', 'my_custom_realtime_event' );
define( 'REALTIME_EVENTS_CRON_INTERVAL', 'five_minutes' ); // WordPress default interval or custom

/**
 * Activate plugin: schedule the event.
 */
function realtime_events_activate() {
    // Schedule the event if it's not already scheduled
    if ( ! wp_next_scheduled( REALTIME_EVENTS_CRON_HOOK ) ) {
        wp_schedule_event( time(), REALTIME_EVENTS_CRON_INTERVAL, REALTIME_EVENTS_CRON_HOOK );
    }
}
register_activation_hook( __FILE__, 'realtime_events_activate' );

/**
 * Deactivate plugin: clear the scheduled event.
 */
function realtime_events_deactivate() {
    wp_clear_scheduled_hook( REALTIME_EVENTS_CRON_HOOK );
}
register_deactivation_hook( __FILE__, 'realtime_events_deactivate' );

/**
 * Add custom cron interval if needed.
 * For 'five_minutes', WordPress already provides this.
 * If you needed 'every_minute', you'd add it here.
 */
function realtime_events_add_intervals( $intervals ) {
    // Example: Add an interval for every minute
    // $intervals['every_minute'] = array(
    //     'interval' => 60, // seconds
    //     'display'  => __( 'Every Minute' ),
    // );
    return $intervals;
}
// add_filter( 'cron_schedules', 'realtime_events_add_intervals' );

// ... rest of the plugin code

Hooking into the Cron Event

Now, we’ll define the action that runs when our scheduled event fires. This function will check for new data or conditions and then trigger the WebSocket broadcast.

<?php
// ... (previous plugin code)

/**
 * The function that will run on our scheduled cron hook.
 */
function realtime_events_trigger_check() {
    // --- Logic to determine if an event needs to be triggered ---
    // This is where you'd query your database, check external APIs,
    // or perform any checks to see if something "new" has happened.

    // Example: Let's simulate an event based on a transient value.
    $event_data = get_transient( 'my_realtime_event_data' );

    if ( $event_data ) {
        // An event is pending. Prepare the message.
        $message = array(
            'type'    => 'new_activity',
            'payload' => $event_data,
            'timestamp' => current_time( 'mysql' ),
        );

        // --- Broadcast the event via WebSocket ---
        // We need to access the running WebSocket server.
        // This is a common challenge: cron runs server-side PHP,
        // but the WebSocket server is a separate long-running process.
        // The most reliable way is to have the cron job make an HTTP request
        // to a dedicated endpoint on the WebSocket server, or use a message queue.
        // For simplicity in this example, we'll simulate the broadcast by
        // assuming a mechanism exists to send data to the WebSocket server.

        // In a real-world scenario, you might:
        // 1. Use a library like Guzzle to POST to a local API endpoint exposed by your WebSocket server.
        // 2. Use a message queue (like Redis Pub/Sub, RabbitMQ) that both cron and the WebSocket server listen to.

        // --- SIMULATED BROADCAST ---
        // This part requires actual integration with your WebSocket server.
        // For demonstration, imagine this function call succeeds:
        // broadcast_to_websocket_server( $message );

        // For a practical implementation using HTTP POST to a local endpoint:
        $ws_endpoint = 'http://localhost:8081/broadcast'; // Assuming WS server has an HTTP endpoint
        $response = wp_remote_post( $ws_endpoint, array(
            'body'    => json_encode( $message ),
            'headers' => array( 'Content-Type' => 'application/json' ),
            'timeout' => 2, // Short timeout
        ) );

        if ( is_wp_error( $response ) ) {
            error_log( 'Realtime Events: Failed to send message to WebSocket server: ' . $response->get_error_message() );
        } else {
            // Optionally check response code: wp_remote_retrieve_response_code($response)
            // Clear the transient after successful broadcast
            delete_transient( 'my_realtime_event_data' );
            error_log( 'Realtime Events: Message broadcasted successfully.' );
        }
        // --- END SIMULATED BROADCAST ---

    } else {
        // No event data found, do nothing.
        error_log( 'Realtime Events: Cron job ran, no event data found.' );
    }
}
add_action( REALTIME_EVENTS_CRON_HOOK, 'realtime_events_trigger_check' );

// --- Helper function to trigger an event manually (for testing) ---
function trigger_custom_event_manually( $data ) {
    set_transient( 'my_realtime_event_data', $data, 60 * 5 ); // Store for 5 minutes
    error_log( 'Realtime Events: Manual event triggered with data: ' . print_r( $data, true ) );
}

// Example of how you might call this from another part of your site:
// add_action('save_post', 'my_post_save_handler');
// function my_post_save_handler($post_id) {
//     $post_data = get_post($post_id);
//     if ($post_data && $post_data->post_status === 'publish') {
//         trigger_custom_event_manually( array(
//             'post_id' => $post_id,
//             'title'   => $post_data->post_title,
//             'type'    => 'post_published'
//         ) );
//     }
// }

// --- END Helper function ---

// ... rest of the plugin code

Integrating WebSocket Server with HTTP Endpoint

To enable the cron job to communicate with the WebSocket server, we need to add an HTTP endpoint to our Ratchet server. This endpoint will receive messages from WordPress and broadcast them to connected WebSocket clients.

<?php
// ... (previous websocket_server.php code)

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ServerRequestInterface;
use GuzzleHttp\Psr7\Response;

class HttpBroadcastHandler implements RequestHandlerInterface {
    protected $broadcaster;

    public function __construct(EventBroadcaster $broadcaster) {
        $this->broadcaster = $broadcaster;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface {
        $method = $request->getMethod();
        $path = $request->getUri()->getPath();

        if ($method === 'POST' && $path === '/broadcast') {
            $body = $request->getBody()->getContents();
            $data = json_decode($body, true);

            if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
                $this->broadcaster->broadcast($data);
                return new Response(200, ['Content-Type' => 'application/json'], json_encode(['status' => 'success', 'message' => 'Message broadcasted']));
            } else {
                return new Response(400, ['Content-Type' => 'application/json'], json_encode(['status' => 'error', 'message' => 'Invalid JSON payload']));
            }
        }

        // Handle other routes or return 404
        return new Response(404, [], 'Not Found');
    }
}

// --- Modified Server Setup ---
$broadcaster = new EventBroadcaster();

// Create a WebSocket server
$webSockServer = new WsServer($broadcaster);

// Create an HTTP server that also handles WebSocket upgrades
$httpServer = new HttpServer($webSockServer);

// Add the HTTP handler for broadcasting
$httpServer->route('/broadcast', new HttpBroadcastHandler($broadcaster), ['POST']);

// Create the main IoServer
$server = IoServer::factory(
    $httpServer,
    8080 // Port for WebSocket and HTTP endpoint
);

echo "WebSocket server started on port 8080...\n";
$server->run();

With this modification, the WebSocket server now listens on port 8080 for both WebSocket connections and HTTP POST requests to /broadcast. The WordPress cron job can now reliably send event data to this endpoint.

Client-Side JavaScript for WebSocket Connection

On the frontend, you’ll need JavaScript to establish a WebSocket connection and listen for incoming messages. This code should be enqueued on the frontend of your WordPress site.

Enqueueing the JavaScript File

Add the following to your plugin file (realtime-events.php) to enqueue a JavaScript file named realtime-events.js.

<?php
// ... (previous plugin code)

/**
 * Enqueue frontend scripts.
 */
function realtime_events_enqueue_scripts() {
    // Only enqueue on the frontend
    if ( ! is_admin() ) {
        wp_enqueue_script(
            'realtime-events-js',
            plugin_dir_url( __FILE__ ) . 'realtime-events.js',
            array(), // Dependencies
            '1.0',
            true // Load in footer
        );

        // Pass WebSocket server URL to the script
        wp_localize_script( 'realtime-events-js', 'realtimeEventsConfig', array(
            'websocket_url' => 'ws://' . $_SERVER['HTTP_HOST'] . ':8080', // Adjust protocol and host as needed
        ) );
    }
}
add_action( 'wp_enqueue_scripts', 'realtime_events_enqueue_scripts' );

// ... rest of the plugin code

JavaScript WebSocket Client

Create the realtime-events.js file in your plugin directory.

// realtime-events.js

document.addEventListener('DOMContentLoaded', function() {
    // Check if the WebSocket API is supported
    if (!window.WebSocket) {
        console.error('WebSocket is not supported by your browser.');
        return;
    }

    // Get the WebSocket URL from localized script parameters
    var wsUrl = realtimeEventsConfig.websocket_url;
    var socket;

    function connectWebSocket() {
        console.log('Attempting to connect to WebSocket server at: ' + wsUrl);
        socket = new WebSocket(wsUrl);

        // Connection opened
        socket.addEventListener('open', function (event) {
            console.log('WebSocket connection opened:', event);
            // You can send a message to the server upon connection if needed
            // socket.send('Hello Server!');
        });

        // Listen for messages
        socket.addEventListener('message', function (event) {
            console.log('Message from server:', event.data);
            try {
                const message = JSON.parse(event.data);
                handleRealtimeMessage(message);
            } catch (e) {
                console.error('Failed to parse message:', e);
            }
        });

        // Connection closed
        socket.addEventListener('close', function (event) {
            console.log('WebSocket connection closed:', event);
            // Attempt to reconnect after a delay
            setTimeout(connectWebSocket, 5000); // Reconnect every 5 seconds
        });

        // Handle errors
        socket.addEventListener('error', function (event) {
            console.error('WebSocket error:', event);
            // The 'close' event will likely follow, triggering a reconnect
        });
    }

    function handleRealtimeMessage(message) {
        // --- Process incoming messages ---
        // Example: Display a notification or update a UI element
        if (message.type === 'new_activity' && message.payload) {
            console.log('New activity detected:', message.payload);

            // Example: Update a DOM element
            const notificationArea = document.getElementById('realtime-notification-area');
            if (notificationArea) {
                const notification = document.createElement('div');
                notification.className = 'realtime-notification';
                notification.innerHTML = `${message.payload.title || 'New Activity'}
${message.payload.message || 'Something happened!'}`; notificationArea.prepend(notification); // Add to the top // Optional: Remove notification after some time setTimeout(() => { notification.remove(); }, 15000); // Remove after 15 seconds } else { alert(`New Activity: ${message.payload.title || 'New Activity'}`); } } // Add more message types as needed } // Initial connection connectWebSocket(); });

Frontend HTML for Display

You’ll need a place in your theme’s template files (e.g., front-page.php, single.php, or a custom template part) to display the notifications.

<!-- In your theme template file -->
<div id="realtime-notification-area">
    <!-- Real-time notifications will appear here -->
</div>

<!-- Your existing content -->

Testing and Considerations

Manual Event Triggering (for testing)

To test the flow without waiting for the cron job, you can manually trigger the event. Add a shortcode or an admin action to call the trigger_custom_event_manually function defined in the plugin.

<?php
// ... (inside realtime-events.php)

// Example: Add a shortcode to trigger an event
function realtime_events_test_shortcode() {
    if ( isset( $_GET['trigger_test_event'] ) && $_GET['trigger_test_event'] === '1' ) {
        trigger_custom_event_manually( array(
            'title'   => 'Test Event',
            'message' => 'This is a manually triggered event.',
        ) );
        return '<p>Test event triggered! Check your WebSocket client.</p>';
    }
    return '<p>Visit <a href="?trigger_test_event=1">this link</a> to trigger a test event.</p>';
}
add_shortcode( 'test_realtime_event', 'test_realtime_event' );

Then, in a post or page, add the shortcode [test_realtime_event]. Visiting that page with ?trigger_test_event=1 appended to the URL will simulate an event.

Production Deployment

WebSocket Server: The websocket_server.php script must run continuously. Use process managers like systemd (Linux) or supervisor to ensure it restarts if it crashes. Configure it to listen on a public IP if your clients are not on the same network.

Cron Jobs: Ensure WordPress Cron is not disabled (DISABLE_WP_CRON in wp-config.php). For high-traffic sites, consider setting up a real server cron job that triggers wp-cron.php via wget or curl to bypass potential performance issues with the default WordPress cron.

Security: Implement authentication and authorization for your WebSocket connections if sensitive data is being transmitted. You could pass a nonce or JWT token during the initial WebSocket handshake and validate it within the onOpen method.

Scalability: For very high volumes of events or many concurrent WebSocket connections, consider more advanced solutions like Redis Pub/Sub for inter-process communication between WordPress and the WebSocket server, or dedicated real-time platforms.

Error Handling and Logging

Thorough logging is crucial. The provided examples include error_log calls. Ensure your server’s PHP error logs are configured correctly. Monitor both the WordPress debug logs and the WebSocket server’s output.

Conclusion

By integrating WordPress’s reliable Cron API with the real-time capabilities of WebSockets, you can build dynamic, responsive applications on top of WordPress. This recipe provides a foundational structure for triggering and broadcasting custom events, enabling richer user experiences and more efficient data synchronization.

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

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins
  • How to implement native Redis caching layers for high-volume custom taxonomy queries in Carbon Fields custom wrappers
  • Building secure B2B pricing grids with custom REST API Controllers endpoints and role overrides

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 (48)
  • 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 (182)
  • WordPress Plugin Development (197)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins

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