WWaSphere Docs
Guides

WHMCS Integration

Send WhatsApp notifications from WHMCS order hooks using WaSphere.

WHMCS Integration

This guide shows you how to send WhatsApp notifications from WHMCS using WaSphere hooks. Common use cases include:

  • Order confirmation messages when a client places a new order
  • Invoice payment reminders
  • Support ticket updates
  • Service activation notifications
  • Domain expiry reminders

Prerequisites

  • WaSphere running with a connected session and an API key with messages:send and sessions:read permissions
  • WHMCS 8.x with admin access
  • PHP 8.0+ (standard with WHMCS 8)

Creating the Hook File

WHMCS hooks live in /path/to/whmcs/includes/hooks/. Create a new file called wasphere_notifications.php.

<?php
// includes/hooks/wasphere_notifications.php

/**
 * WaSphere WhatsApp Notifications for WHMCS
 * Sends WhatsApp messages via WaSphere API on key WHMCS events.
 */

defined('WHMCS') or die('This file cannot be accessed directly');

// ─── Configuration ───────────────────────────────────────────────────────────
// Store these in WHMCS custom configuration or use PHP constants.
// DO NOT hardcode secrets in production — use getenv() or WHMCS config storage.

define('WASPHERE_API_URL', getenv('WASPHERE_API_URL') ?: 'https://wa.yourdomain.com/api');
define('WASPHERE_API_KEY', getenv('WASPHERE_API_KEY') ?: '');
define('WASPHERE_SESSION_ID', getenv('WASPHERE_SESSION_ID') ?: '');

// ─── Helper Functions ─────────────────────────────────────────────────────────

/**
 * Send a WhatsApp text message via WaSphere.
 *
 * @param string $phoneNumber International format without +, e.g. "447700900123"
 * @param string $message     Message text (WhatsApp formatting supported)
 * @return bool               True on success, false on failure
 */
function waSphereSendMessage(string $phoneNumber, string $message): bool
{
    if (empty(WASPHERE_API_KEY) || empty(WASPHERE_SESSION_ID)) {
        logActivity('WaSphere: API key or session ID not configured');
        return false;
    }

    // Normalise phone number — strip spaces, dashes, parentheses, leading +
    $to = preg_replace('/[^0-9]/', '', $phoneNumber);
    if (empty($to)) {
        logActivity("WaSphere: Invalid phone number: {$phoneNumber}");
        return false;
    }

    $payload = json_encode([
        'sessionId' => WASPHERE_SESSION_ID,
        'to'        => $to,
        'text'      => $message,
    ]);

    $ch = curl_init(WASPHERE_API_URL . '/messages/send/text');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_HTTPHEADER     => [
            'X-API-Key: ' . WASPHERE_API_KEY,
            'Content-Type: application/json',
            'Content-Length: ' . strlen($payload),
        ],
    ]);

    $response = curl_exec($ch);
    $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($curlError) {
        logActivity("WaSphere: cURL error: {$curlError}");
        return false;
    }

    if ($statusCode < 200 || $statusCode >= 300) {
        logActivity("WaSphere: API error {$statusCode}: {$response}");
        return false;
    }

    return true;
}

/**
 * Retrieve client phone number from WHMCS.
 */
function waSphereGetClientPhone(int $clientId): string
{
    $client = Capsule::table('tblclients')
        ->where('id', $clientId)
        ->select('phonenumber', 'phonecc')
        ->first();

    if (!$client || empty($client->phonenumber)) {
        return '';
    }

    // Combine country code + number, strip non-digits
    $phone = $client->phonecc . $client->phonenumber;
    return preg_replace('/[^0-9]/', '', $phone);
}

/**
 * Format currency amounts for display.
 */
function waSphereFormatCurrency(float $amount, string $currency = 'USD'): string
{
    return number_format($amount, 2) . ' ' . $currency;
}

Hook: New Order Notification

<?php
// Add this to wasphere_notifications.php

add_hook('OrderPaid', 1, function ($vars) {
    $orderId  = $vars['orderid'];
    $clientId = $vars['userid'];
    $amount   = $vars['amount'];

    $phone = waSphereGetClientPhone($clientId);
    if (empty($phone)) {
        return;
    }

    // Fetch order details
    $order = Capsule::table('tblorders')
        ->where('id', $orderId)
        ->select('ordernum', 'currencyid')
        ->first();

    $currency = Capsule::table('tblcurrencies')
        ->where('id', $order->currencyid)
        ->value('code') ?? 'USD';

    $formattedAmount = waSphereFormatCurrency((float) $amount, $currency);

    $message = implode("\n", [
        "✅ *Payment Received — Thank You!*",
        "",
        "Hi there! We've received your payment for Order #{$order->ordernum}.",
        "",
        "💳 Amount paid: *{$formattedAmount}*",
        "🔢 Order number: *#{$order->ordernum}*",
        "",
        "Your services are being activated. You'll receive another message when everything is ready.",
        "",
        "_WaSphere — Your Hosting Team_",
    ]);

    waSphereSendMessage($phone, $message);
});

Hook: Invoice Payment Reminder

<?php
add_hook('InvoiceCreated', 1, function ($vars) {
    $invoiceId = $vars['invoiceid'];
    $clientId  = $vars['userid'];

    $phone = waSphereGetClientPhone($clientId);
    if (empty($phone)) {
        return;
    }

    $invoice = Capsule::table('tblinvoices')
        ->where('id', $invoiceId)
        ->select('invoicenum', 'total', 'duedate', 'currencyid')
        ->first();

    $currency = Capsule::table('tblcurrencies')
        ->where('id', $invoice->currencyid)
        ->value('code') ?? 'USD';

    $dueDate = date('d M Y', strtotime($invoice->duedate));
    $total   = waSphereFormatCurrency((float) $invoice->total, $currency);

    $message = implode("\n", [
        "🧾 *New Invoice #{$invoice->invoicenum}*",
        "",
        "Amount due: *{$total}*",
        "Due date: *{$dueDate}*",
        "",
        "Pay online at: https://yourwhmcs.com/viewinvoice.php?id={$invoiceId}",
        "",
        "_Reply STOP to unsubscribe from WhatsApp notifications._",
    ]);

    waSphereSendMessage($phone, $message);
});

Hook: Overdue Invoice Reminder

<?php
add_hook('InvoiceOverdue', 1, function ($vars) {
    $invoiceId = $vars['invoiceid'];
    $clientId  = $vars['userid'];

    $phone = waSphereGetClientPhone($clientId);
    if (empty($phone)) {
        return;
    }

    $invoice = Capsule::table('tblinvoices')
        ->where('id', $invoiceId)
        ->select('invoicenum', 'total', 'duedate', 'currencyid')
        ->first();

    $currency = Capsule::table('tblcurrencies')
        ->where('id', $invoice->currencyid)
        ->value('code') ?? 'USD';

    $total = waSphereFormatCurrency((float) $invoice->total, $currency);

    $message = implode("\n", [
        "⚠️ *Overdue Invoice #{$invoice->invoicenum}*",
        "",
        "Your invoice for *{$total}* is now overdue.",
        "",
        "To avoid service interruption, please pay at:",
        "https://yourwhmcs.com/viewinvoice.php?id={$invoiceId}",
        "",
        "If you've already paid, please ignore this message.",
    ]);

    waSphereSendMessage($phone, $message);
});

Hook: Support Ticket Reply

<?php
add_hook('TicketAdminReply', 1, function ($vars) {
    $ticketId = $vars['ticketid'];
    $clientId = $vars['userid'];

    $phone = waSphereGetClientPhone($clientId);
    if (empty($phone)) {
        return;
    }

    $ticket = Capsule::table('tbltickets')
        ->where('id', $ticketId)
        ->select('tid', 'title', 'c')
        ->first();

    // Only notify if client has opted in — check a custom field
    // (Omit this block if you notify all clients)
    // $optIn = Capsule::table('tblclientsfields')
    //     ->where('clientid', $clientId)
    //     ->where('fieldid', WHATSAPP_OPTIN_FIELD_ID)
    //     ->value('value');
    // if ($optIn !== 'Yes') return;

    $message = implode("\n", [
        "💬 *Support Ticket Update*",
        "",
        "Ticket #{$ticket->tid}: _{$ticket->title}_",
        "",
        "Our team has replied to your ticket. View and respond at:",
        "https://yourwhmcs.com/viewticket.php?tid={$ticket->c}",
        "",
        "_Our typical response time is under 4 hours._",
    ]);

    waSphereSendMessage($phone, $message);
});

Hook: Service Activation

<?php
add_hook('AfterModuleCreate', 1, function ($vars) {
    $serviceId = $vars['params']['serviceid'];
    $clientId  = $vars['params']['userid'];

    $phone = waSphereGetClientPhone($clientId);
    if (empty($phone)) {
        return;
    }

    $service = Capsule::table('tblhosting')
        ->where('id', $serviceId)
        ->select('domain', 'username', 'dedicatedip')
        ->first();

    $message = implode("\n", [
        "🚀 *Service Activated!*",
        "",
        "Your hosting service is now live.",
        "",
        "🌐 Domain: *{$service->domain}*",
        "👤 Username: *{$service->username}*",
        "",
        "Log in to your control panel at:",
        "https://yourwhmcs.com/clientarea.php",
        "",
        "Need help getting started? Reply to this message.",
    ]);

    waSphereSendMessage($phone, $message);
});

Setting Environment Variables

Add to your WHMCS server's environment (not in the PHP file):

# /path/to/whmcs/.htaccess
SetEnv WASPHERE_API_URL "https://wa.yourdomain.com/api"
SetEnv WASPHERE_API_KEY "wsp_live_..."
SetEnv WASPHERE_SESSION_ID "sess_abc123"
# In your Nginx server block
fastcgi_param WASPHERE_API_URL "https://wa.yourdomain.com/api";
fastcgi_param WASPHERE_API_KEY "wsp_live_...";
fastcgi_param WASPHERE_SESSION_ID "sess_abc123";
# /etc/php/8.2/fpm/pool.d/whmcs.conf
[whmcs]
env[WASPHERE_API_URL] = https://wa.yourdomain.com/api
env[WASPHERE_API_KEY] = wsp_live_...
env[WASPHERE_SESSION_ID] = sess_abc123

Never store your API key or webhook secret in the WHMCS hook PHP file. If the file is exposed (a misconfigured web server or a future WHMCS vulnerability), your key would be compromised. Use environment variables or WHMCS's encrypted configuration storage.

Opt-In / Opt-Out Handling

For compliance (GDPR, TCPA), clients should explicitly opt in to WhatsApp notifications. Add a custom client field in WHMCS:

  1. Go to Setup → Custom Client Fields
  2. Create a field: WhatsApp Notifications / Type: Yes/No
  3. Show it on the client registration form and client area

Then check it before sending:

function waSphereClientOptedIn(int $clientId): bool
{
    $optInFieldId = 5; // Replace with your actual custom field ID
    
    $value = Capsule::table('tblclientsfields')
        ->where('relid', $clientId)
        ->where('fieldid', $optInFieldId)
        ->value('value');
    
    return strtolower(trim($value ?? '')) === 'yes';
}

// In your hook:
add_hook('OrderPaid', 1, function ($vars) {
    $clientId = $vars['userid'];
    
    if (!waSphereClientOptedIn($clientId)) {
        return; // Client hasn't opted in
    }
    
    // ... rest of the hook
});

Testing Hooks

WHMCS has a built-in hook tester at Utilities → Hook Tester (WHMCS 8.5+). Alternatively, trigger the hook manually:

// Temporary test script — remove after testing
// Place in /includes/hooks/wasphere_test.php
add_hook('AdminAreaPage', 1, function ($vars) {
    if (isset($_GET['test_wasphere']) && $_SESSION['adminid']) {
        $result = waSphereSendMessage('447700900123', 'WaSphere WHMCS hook test — ' . date('H:i:s'));
        die($result ? 'Message sent successfully' : 'Failed — check WHMCS activity log');
    }
});

Visit https://yourwhmcs.com/admin/index.php?test_wasphere=1 to trigger the test.

Delete the test hook file after testing. Leaving admin-only test hooks in production is a security risk — anyone who discovers the URL parameter can trigger WhatsApp messages.

Troubleshooting

Messages not sending

Check the WHMCS Activity Log (Utilities → Logs → Activity Log) — WaSphere hook errors are logged there via logActivity().

Phone numbers formatting incorrectly

WHMCS stores phone numbers in various formats depending on the client's input. The waSphereGetClientPhone() helper strips all non-numeric characters. If numbers still fail, log the raw value:

logActivity("WaSphere debug: raw phone for client {$clientId}: {$client->phonenumber} / cc: {$client->phonecc}");

Hook fires but message doesn't arrive

  1. Verify the WaSphere session is connected in the dashboard
  2. Verify the API key has messages:send permission
  3. Check WaSphere's message queue in the dashboard — the message may be queued due to rate limiting

On this page