• 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 REST API Controllers

WordPress Development Recipe: Real-time custom event triggers using WebSockets and REST API Controllers

Leveraging WebSockets for Real-time WordPress Event Notifications

For e-commerce platforms built on WordPress, delivering instant feedback to users and administrators is paramount. Imagine a customer completing a purchase, and the order status updating in real-time on an admin dashboard without a page refresh. Or a new support ticket appearing instantly for a customer service agent. This level of interactivity is typically achieved through WebSockets. This recipe outlines how to integrate a WebSocket server with WordPress, triggered by custom REST API events, to push real-time notifications.

Setting Up a Node.js WebSocket Server

We’ll use Node.js with the popular `ws` library for our WebSocket server. This server will act as a central hub, listening for events from WordPress and broadcasting them to connected clients.

First, initialize a Node.js project and install the necessary package:

mkdir wordpress-ws-server
cd wordpress-ws-server
npm init -y
npm install ws express

Next, create the main server file, e.g., server.js:

const WebSocket = require('ws');
const express = require('express');
const http = require('http');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

const PORT = process.env.PORT || 8080;

// Store connected clients
const clients = new Map();
let clientIdCounter = 0;

wss.on('connection', (ws) => {
    const id = clientIdCounter++;
    clients.set(id, ws);
    console.log(`Client connected: ${id}`);

    ws.on('message', (message) => {
        console.log(`Received message from client ${id}: ${message}`);
        // Optionally, broadcast messages back to all clients or specific ones
        // wss.clients.forEach((client) => {
        //     if (client !== ws && client.readyState === WebSocket.OPEN) {
        //         client.send(`Broadcast from ${id}: ${message}`);
        //     }
        // });
    });

    ws.on('close', () => {
        clients.delete(id);
        console.log(`Client disconnected: ${id}`);
    });

    ws.on('error', (error) => {
        console.error(`WebSocket error for client ${id}:`, error);
        clients.delete(id);
    });

    // Send a welcome message to the newly connected client
    ws.send(JSON.stringify({ type: 'welcome', message: 'Connected to WebSocket server!' }));
});

// Endpoint to receive events from WordPress
app.post('/event', express.json(), (req, res) => {
    const eventData = req.body;
    console.log('Received event from WordPress:', eventData);

    // Broadcast the event to all connected clients
    wss.clients.forEach((client) => {
        if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify(eventData));
        }
    });

    res.status(200).json({ message: 'Event received and broadcasted' });
});

server.listen(PORT, () => {
    console.log(`WebSocket server started on port ${PORT}`);
});

// Function to broadcast to specific client (example, not used by WP directly)
function broadcastToClient(clientId, message) {
    if (clients.has(clientId)) {
        const client = clients.get(clientId);
        if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify(message));
        }
    }
}

// Export for potential modularity or testing
module.exports = { server, wss, broadcastToClient };

To run this server, execute:

node server.js

This server listens on port 8080 and exposes a POST /event endpoint. When WordPress sends data to this endpoint, the server will broadcast it to all connected WebSocket clients.

WordPress REST API Controller for Event Publishing

We need a mechanism within WordPress to trigger events and send them to our WebSocket server. This can be achieved by creating a custom REST API endpoint that acts as a publisher. When this endpoint is called, it will format the data and send an HTTP POST request to our Node.js WebSocket server.

Create a new plugin or add this code to your theme’s functions.php (though a plugin is highly recommended for maintainability).

<?php
/**
 * Plugin Name: Real-time Event Publisher
 * Description: Publishes custom events to a WebSocket server.
 * Version: 1.0
 * Author: Your Name
 */

// Define WebSocket server URL
define('MY_WS_SERVER_URL', 'http://localhost:8080/event'); // Replace with your server's actual URL

/**
 * Register a custom REST API endpoint to trigger events.
 */
add_action('rest_api_init', function () {
    register_rest_route('myevents/v1', '/publish', array(
        'methods' => 'POST',
        'callback' => 'my_publish_event_callback',
        'permission_callback' => function () {
            // Implement proper authentication/authorization here.
            // For simplicity, allowing any authenticated user.
            return current_user_can('edit_posts');
        }
    ));
});

/**
 * Callback function for the REST API endpoint.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 */
function my_publish_event_callback(WP_REST_Request $request) {
    $event_data = $request->get_json_params();

    if (empty($event_data)) {
        return new WP_Error('invalid_data', 'No event data provided.', array('status' => 400));
    }

    // Ensure a 'type' is present for clarity
    if (!isset($event_data['type'])) {
        $event_data['type'] = 'generic_event'; // Default type
    }

    // Add a timestamp
    $event_data['timestamp'] = current_time('mysql');

    // Send the event data to the WebSocket server
    $response = wp_remote_post(MY_WS_SERVER_URL, array(
        'method'    => 'POST',
        'timeout'   => 45,
        'redirection' => 5,
        'httpversion' => '1.0',
        'body'      => json_encode($event_data),
        'headers'   => array(
            'Content-Type' => 'application/json',
        ),
    ));

    if (is_wp_error($response)) {
        $error_message = $response->get_error_message();
        error_log("Error sending event to WS server: " . $error_message);
        return new WP_Error('ws_server_error', 'Failed to send event to WebSocket server: ' . $error_message, array('status' => 500));
    }

    $response_code = wp_remote_retrieve_response_code($response);
    $response_body = wp_remote_retrieve_body($response);

    if ($response_code !== 200) {
        error_log("WS server returned error: " . $response_code . " - " . $response_body);
        return new WP_Error('ws_server_error', 'WebSocket server responded with an error.', array('status' => $response_code));
    }

    // Success
    return new WP_REST_Response(array(
        'message' => 'Event published successfully',
        'data_sent' => $event_data
    ), 200);
}

/**
 * Example: Triggering an event on order completion (requires WooCommerce).
 * This is a conceptual example. You'd hook into WooCommerce's specific actions.
 */
add_action('woocommerce_order_status_changed', function ($order_id, $old_status, $new_status) {
    if ('completed' === $new_status) {
        $order = wc_get_order($order_id);
        if ($order) {
            $event_payload = array(
                'type' => 'order_completed',
                'order_id' => $order_id,
                'customer_email' => $order->get_billing_email(),
                'total' => $order->get_total(),
                'currency' => $order->get_currency(),
                'status' => $new_status,
            );

            // Use wp_remote_post to send to our custom REST endpoint,
            // which then forwards to the WS server.
            // Alternatively, you could directly call the WS server here if preferred,
            // but using the REST endpoint provides a unified publishing mechanism.
            $publish_endpoint = rest_url('myevents/v1/publish');
            $response = wp_remote_post($publish_endpoint, array(
                'method'    => 'POST',
                'timeout'   => 45,
                'body'      => json_encode($event_payload),
                'headers'   => array(
                    'Content-Type' => 'application/json',
                    'Authorization' => 'Bearer YOUR_API_KEY_OR_NONCE' // If your publish endpoint requires auth
                ),
            ));

            if (is_wp_error($response)) {
                error_log("Error triggering order_completed event via REST: " . $response->get_error_message());
            } else {
                $response_code = wp_remote_retrieve_response_code($response);
                if ($response_code !== 200) {
                    error_log("REST endpoint for order_completed event returned error: " . $response_code);
                } else {
                    // Event successfully queued for publishing
                    error_log("Order completed event queued for publishing: " . $order_id);
                }
            }
        }
    }
}, 10, 3);

// Basic authentication for the publish endpoint (example)
// In a real-world scenario, use nonces, JWT, or application passwords.
add_filter('rest_authentication_errors', function ($result) {
    // If a previous authentication check has failed, return that.
    if (true !== $result && is_wp_error($result)) {
        return $result;
    }

    // If the user is logged in, we don't need to do anything.
    if (get_current_user_id()) {
        return $result;
    }

    // If no user is logged in, and the request is for our specific endpoint,
    // we might want to allow it if it has a valid token/key.
    // For this example, we'll just check if the request is for our publish endpoint.
    // A more robust solution would involve checking headers for an API key.
    $request = \WP_REST_Server::get_instance()->get_request();
    if (isset($request->get_route()[0]) && $request->get_route()[0] === '/myevents/v1/publish') {
        // Here you would implement your API key validation.
        // Example:
        // $headers = $request->get_headers();
        // if (!isset($headers['authorization']) || $headers['authorization'] !== 'Bearer YOUR_SECURE_KEY') {
        //     return new WP_Error('rest_forbidden', __('Invalid API key.'), array('status' => 401));
        // }
        // For now, let's assume it's allowed if it matches the route, but this is INSECURE for production.
        // return $result; // Allow if authenticated or if we implement key check
    }

    // Otherwise, return the result of the default authentication.
    return $result;
});

Important Security Note: The permission_callback and the example rest_authentication_errors filter are placeholders. In a production environment, you MUST implement robust authentication and authorization for your /publish endpoint. This could involve API keys, OAuth tokens, or ensuring the user has specific capabilities.

Client-Side JavaScript for WebSocket Connection

On the front-end (e.g., in a theme’s JavaScript file or a dedicated plugin), you’ll need JavaScript to establish a WebSocket connection to your Node.js server and listen for incoming messages.

document.addEventListener('DOMContentLoaded', () => {
    const wsUrl = 'ws://localhost:8080'; // Replace with your WebSocket server URL
    let socket;

    function connectWebSocket() {
        socket = new WebSocket(wsUrl);

        socket.onopen = () => {
            console.log('WebSocket connection established.');
            // Send a message to the server upon connection if needed
            // socket.send(JSON.stringify({ type: 'identify', userId: 'some_user_id' }));
        };

        socket.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                console.log('Message from server:', data);

                // Handle different event types
                switch (data.type) {
                    case 'welcome':
                        console.log('Server welcome message:', data.message);
                        break;
                    case 'order_completed':
                        console.log(`New order completed: #${data.order_id}`);
                        // Update UI, show notification, etc.
                        displayNotification(`Order #${data.order_id} completed! Total: ${data.total}`);
                        break;
                    case 'new_support_ticket':
                        console.log('New support ticket received:', data.ticket_id);
                        // Update support dashboard
                        break;
                    // Add more cases for other event types
                    default:
                        console.log('Received unknown event type:', data.type);
                }
            } catch (e) {
                console.error('Failed to parse message or handle event:', e);
            }
        };

        socket.onclose = (event) => {
            console.log('WebSocket connection closed:', event.code, event.reason);
            // Attempt to reconnect after a delay
            setTimeout(connectWebSocket, 5000); // Reconnect every 5 seconds
        };

        socket.onerror = (error) => {
            console.error('WebSocket error:', error);
            // The 'onclose' event will typically follow an error
        };
    }

    function displayNotification(message) {
        // Example: Append to a notification area on the page
        const notificationArea = document.getElementById('ws-notifications');
        if (!notificationArea) {
            const div = document.createElement('div');
            div.id = 'ws-notifications';
            div.style.position = 'fixed';
            div.style.bottom = '20px';
            div.style.right = '20px';
            div.style.backgroundColor = '#4CAF50';
            div.style.color = 'white';
            div.style.padding = '15px';
            div.style.borderRadius = '5px';
            div.style.zIndex = '1000';
            document.body.appendChild(div);
            notificationArea = div;
        }
        const p = document.createElement('p');
        p.textContent = message;
        notificationArea.appendChild(p);

        // Remove notification after some time
        setTimeout(() => {
            notificationArea.removeChild(p);
            if (notificationArea.children.length === 0) {
                notificationArea.remove();
            }
        }, 10000); // 10 seconds
    }

    // Initial connection
    connectWebSocket();
});

To integrate this JavaScript, enqueue it properly in WordPress. For example, in your plugin file:

function enqueue_ws_client_script() {
    // Ensure this path is correct relative to your plugin's root directory
    wp_enqueue_script('ws-client', plugin_dir_url(__FILE__) . 'js/ws-client.js', array(), '1.0', true);
}
add_action('wp_enqueue_scripts', 'enqueue_ws_client_script');
// Use 'admin_enqueue_scripts' for admin-only functionality

Triggering Events Programmatically

Beyond specific hooks like WooCommerce order status changes, you can trigger events from anywhere in your WordPress code. The most straightforward way is to directly call the custom REST API endpoint you created.

function trigger_custom_event($event_type, $event_data = array()) {
    $publish_endpoint = rest_url('myevents/v1/publish');

    $payload = array_merge(array(
        'type' => $event_type,
        'timestamp' => current_time('mysql'),
    ), $event_data);

    $response = wp_remote_post($publish_endpoint, array(
        'method'    => 'POST',
        'timeout'   => 45,
        'body'      => json_encode($payload),
        'headers'   => array(
            'Content-Type' => 'application/json',
            // Add authentication headers if your publish endpoint requires them
            // 'Authorization' => 'Bearer YOUR_API_KEY'
        ),
    ));

    if (is_wp_error($response)) {
        error_log("Error triggering custom event '{$event_type}': " . $response->get_error_message());
        return false;
    }

    $response_code = wp_remote_retrieve_response_code($response);
    if ($response_code !== 200) {
        error_log("Custom event '{$event_type}' endpoint returned error: " . $response_code);
        return false;
    }

    return true;
}

// Example usage:
// trigger_custom_event('user_registered', array('user_id' => 123, 'username' => 'john_doe'));
// trigger_custom_event('product_low_stock', array('product_id' => 456, 'stock_level' => 5));

Production Considerations

For a production environment, several aspects require careful attention:

  • Scalability: The Node.js WebSocket server might need to be scaled horizontally. Consider using a message broker like Redis Pub/Sub or RabbitMQ to decouple the Node.js server instances and allow them to broadcast messages reliably across all instances. Your WordPress plugin would publish to Redis, and each Node.js instance would subscribe to Redis and broadcast to its connected clients.
  • Security: Implement robust authentication for both the WordPress REST API endpoint publishing events and potentially for WebSocket connections themselves (e.g., using JWTs passed during connection). Ensure TLS/SSL is used for both HTTP and WebSocket (WSS) connections.
  • Error Handling & Resilience: Implement comprehensive error logging on both the Node.js server and the WordPress plugin. The client-side JavaScript should have robust reconnection logic.
  • Deployment: Use a process manager like PM2 for the Node.js server to ensure it stays running and restarts automatically on crashes.
  • Configuration Management: Externalize sensitive information like server URLs and API keys using environment variables.
  • Client Identification: For more advanced scenarios, you might want to identify specific connected clients (e.g., by user ID) and send targeted messages rather than broadcasting to everyone. This requires passing user identifiers during the WebSocket connection and storing them alongside the connection object in the `clients` map.

By combining WordPress’s REST API capabilities with a dedicated WebSocket server, you can build highly interactive and real-time features for your e-commerce site, enhancing user experience and operational efficiency.

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