WWaSphere
GitHub
WaSphere Teamwhmcswhatsapp-apibillingintegration

Send WhatsApp Order & Invoice Alerts from WHMCS (Self-Hosted)

Wire a self-hosted WhatsApp API into WHMCS order and invoice hooks. Send order confirmations, invoice reminders, and service-activation alerts over WhatsApp.

Send WhatsApp Order & Invoice Alerts from WHMCS (Self-Hosted)

Email open rates for billing notices are dismal. WhatsApp messages get read in minutes. If you run a hosting or reseller business on WHMCS, moving order confirmations and invoice reminders to WhatsApp is one of the highest-leverage changes you can make — and with a self-hosted WhatsApp API, you do it without paying per message or handing customer phone numbers to a third party.

This post shows how to connect WHMCS to WaSphere, a self-hosted WhatsApp platform you run with Docker, and fire WhatsApp messages straight from WHMCS hooks.

How the pieces fit

WHMCS fires PHP hooks on events like OrderPaid, InvoiceCreated, and InvoiceOverdue. You write a hook handler that calls the WaSphere API over HTTPS. WaSphere holds the WhatsApp session and delivers the message.

You'll need:

  • WaSphere running with a connected session (5-minute Docker install)
  • A WaSphere API key with the messages:send and sessions:read permissions
  • WHMCS 8.x with admin access

Create the key under API Keys in the dashboard. Scope it to the single session you use for billing so a leaked key can't touch anything else. Keys look like wsk_live_... and are shown once.

The send helper

WHMCS hooks live in includes/hooks/. Create wasphere_notifications.php with a small helper that posts to the API. The session ID lives in the URL path; the body carries the recipient and text:

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

function waSphereSendText(string $phone, string $text): bool
{
    $workspaceId = getenv('WASPHERE_WORKSPACE_ID');
    $sessionId   = getenv('WASPHERE_SESSION_ID');
    $apiKey      = getenv('WASPHERE_API_KEY');
    $baseUrl     = rtrim(getenv('WASPHERE_API_URL'), '/');

    $to = preg_replace('/[^0-9]/', '', $phone); // strip +, spaces, dashes
    if ($to === '' || $apiKey === '') {
        logActivity('WaSphere: missing phone or API key');
        return false;
    }

    $url = "{$baseUrl}/workspaces/{$workspaceId}/proxy/api/sessions/{$sessionId}/messages/text";
    $payload = json_encode(['to' => $to, 'text' => $text]);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . $apiKey,
            'Content-Type: application/json',
        ],
    ]);

    $response = curl_exec($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

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

Keep secrets out of the PHP file — read them from environment variables set in your web server or PHP-FPM pool config. A leaked hook file should never leak your API key.

Order paid → confirmation

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

    $client = Capsule::table('tblclients')->where('id', $clientId)
        ->select('phonenumber', 'phonecc')->first();
    if (!$client || empty($client->phonenumber)) return;

    $phone = $client->phonecc . $client->phonenumber;

    $message = implode("\n", [
        "✅ *Payment received — thank you!*",
        "",
        "Your order is confirmed and your services are being activated.",
        "You'll get another message the moment everything is live.",
    ]);

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

Invoice created → payment notice

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

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

    $client = Capsule::table('tblclients')->where('id', $invoice->userid)
        ->select('phonenumber', 'phonecc')->first();
    if (!$client || empty($client->phonenumber)) return;

    $due = date('d M Y', strtotime($invoice->duedate));
    $message = implode("\n", [
        "🧾 *New invoice #{$invoice->invoicenum}*",
        "",
        "Amount due: *{$invoice->total}*",
        "Due date: *{$due}*",
        "",
        "Pay online: https://yourwhmcs.com/viewinvoice.php?id={$invoiceId}",
        "",
        "_Reply STOP to unsubscribe._",
    ]);

    waSphereSendText($client->phonecc . $client->phonenumber, $message);
});

Add InvoiceOverdue for nudges and AfterModuleCreate for service-activation alerts using the same pattern — fetch the client phone, build a message, call waSphereSendText().

Two-way: react to replies with webhooks

Outbound alerts are half the story. When a customer replies, WaSphere can POST the inbound message to your endpoint. Register a webhook for message.received and you can route replies into a ticket, a bot, or a support queue.

Every webhook delivery is signed with HMAC-SHA256 (X-WaSphere-Signature: v1,sha256=...) over {timestamp}.{rawBody}. Always verify it before trusting the payload — it's the difference between a real event and a spoofed one. See the Webhooks guide for verification snippets in PHP, Node, and Python.

Stay compliant

Add a WhatsApp Notifications Yes/No custom client field in WHMCS and check it before sending. Include a Reply STOP to unsubscribe line on marketing-adjacent messages. Use a dedicated number for automation, not a personal account.

Why self-hosted beats a SaaS gateway here

Your customers' phone numbers and message history stay in your own PostgreSQL. There are no per-message fees eating your margin on every invoice reminder. API keys are scoped to exactly the permissions and the single session they need, and the whole platform is MIT-licensed.

Spin it up and connect your billing session in a few minutes with the Quick Start guide, then drop these hooks into WHMCS.

Get Started

Ship a self-hosted WhatsApp API today

Clone the repo, set your secrets, and send your first message in minutes — Docker-based, MIT licensed, zero config.

Read the Quick Start