Jump to content

All Activity

This stream auto-updates

  1. Past hour
  2. Today
  3. Yeah, Ive hit this - had to license the mailbox - so it has a direct login. 😞
  4. Yesterday
  5. Here is the hook by the way. Vote for its implementation if you think it's of any use. that hook sends the order details to the admin email and saves it in a txt file under "logs" folder
  6. As you might know, whmcs has a problem with stripe: when the payment is not immediately confirmed by stripe (but requires to clic "capture" on stripe dashboard), whmcs does not record the order and the customer details and does not even send any notification to whmcs admin or to the customer. We solved with a hook that sends us an email with the order details, so that: - we know that there was a order attempt - we know that there might be a payment in stripe that requires manual "capture" - if necessary, we have all the client and order details in order to recreate the both client account and order. Here is the hook. <?php if (!defined("WHMCS")) { die("This file cannot be accessed directly"); } /** * Save all checkout data to a TXT file * AND email it to admin * Runs BEFORE order/invoice creation, and before redirect to Stripe. */ add_hook('ShoppingCartValidateCheckout', 1, function ($vars) { // --- 1. Clone and sanitise incoming data ------------------------------- $data = $vars; // Mask any possible card data (good practice even if Stripe never posts it) $sensitiveFields = [ 'ccnumber', 'cccvv', 'ccexpirymonth', 'ccexpiryyear', 'password', 'password2', ]; foreach ($sensitiveFields as $field) { if (!empty($data[$field])) { $data[$field] = '***masked***'; } } // --- 2. Attach cart contents from session (products, domains, etc.) --- $cart = isset($_SESSION['cart']) ? $_SESSION['cart'] : []; $payload = [ 'timestamp' => date('Y-m-d H:i:s'), 'ip' => $_SERVER['REMOTE_ADDR'] ?? null, 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null, 'checkoutFields' => $data, 'cart' => $cart, ]; // --- 3. Build a text representation (for file & email) ---------------- $entry = "==============================\n"; $entry .= "Checkout submitted: " . $payload['timestamp'] . "\n"; $entry .= "IP: " . ($payload['ip'] ?? 'N/A') . "\n"; $entry .= "User-Agent: " . ($payload['user_agent'] ?? 'N/A') . "\n\n"; $entry .= "DATA:\n" . print_r($payload, true) . "\n\n"; // --- 4. Save to TXT file ---------------------------------------------- $logDir = __DIR__ . '/../../logs'; $logFile = $logDir . '/pre_stripe_checkout.txt'; if (!is_dir($logDir)) { @mkdir($logDir, 0700, true); } @file_put_contents($logFile, $entry, FILE_APPEND); // --- 5. Send email to admin ------------------------------------------- // This uses WHMCS' built-in admin notification helper. // The "system" type means it goes to the system email / admin notifications. if (function_exists('sendAdminNotification')) { $subject = 'Pre-Stripe Checkout Captured'; // Use <pre> so the print_r formatting is readable in HTML email $message = nl2br(htmlspecialchars($entry)); sendAdminNotification('system', $subject, '<pre>' . $message . '</pre>'); } // IMPORTANT: Do NOT return an error. Just let checkout continue. return; }); I suggest to implement it. It would be great if it would offer some sort of customization in the admin area.
  7. Please get in touch with us here https://whmcsglobalservices.com/whmcs-custom-development/ , and we can develop the custom Telegram module as we developed for WhatsApp, so Telegram is not a big deal for us.
  8. Hey WHMCS Community So here’s the thing… I love WHMCS. It’s great. But right now, it has a little “quirk” when it comes to shared mailboxes. Imagine this: I’ve got my main mailbox doing its thing, happily bringing in emails. But then there’s the shared mailbox, the one that all the support team emails go to. WHMCS? It sees it… but then pretends it doesn’t exist. 😅 Here’s the geeky version: WHMCS currently uses /me/messages from Microsoft Graph API. This only fetches emails from the authenticated user’s mailbox. Shared mailbox? Nope, invisible. Even if Full Access is granted in Exchange and you’ve got all the delegated permissions like Mail.Read.Shared, WHMCS is like, “I don’t see anything here. Must be Monday.” The right way (according to Microsoft Graph) is: GET https://graph.microsoft.com/v1.0/users/{shared-mailbox}/mailFolders/inbox/messages Yes, WHMCS, that mailbox does exist, and yes, I do have permission to read it. What I’m asking: Can WHMCS please natively support shared mailbox imports? Guidance on exactly which delegated permissions are needed (so I don’t go on a wild goose chase for nothing). Support for the correct Graph API endpoint. Bonus points: documentation for optional PHP session settings for cross-site OAuth flows (because apparently, cookies are shy and need proper settings to stick around). Expected outcome: Fetch emails from shared mailboxes without needing a license for it. Less headache, more sanity. WHMCS behaving like the email superhero it’s meant to be. Anyone else banging their head on the “shared mailbox wall”? Let’s commiserate, share workarounds, and maybe get WHMCS to notice that shared mailboxes exist in the universe. Thanks!
  9. Last week
  10. Hey Bruxs In the first test today it looks working. He generated a username now, that looks good. Thank you for your time and helpfull answer!
  11. Email Verify Pro Automates email verification with customizable blocking overlay. What it does: Sends verification email on registration Blocks unverified users with full-screen overlay Cryptographically secure 32-byte tokens Configurable expiry and rate limiting Full overlay customization (colors, fonts, animations) Admin verification status display Features: Token expiry configuration (default: 60 minutes) Resend cooldown (default: 2 minutes) Max resend attempts (default: 7 per 24 hours) Database-backed rate limiting CSRF protection on all endpoints Automatic token cleanup via daily cron Email change re-verification Compatible with WHMCS native verification Technical Details: Addon module with hook integration Uses existing WHMCS database columns (email_verified_at, email_verification_token, email_verification_token_expiry) Creates two tables: mod_email_verify_pro (settings), mod_email_verify_pro_rate_limit (tracking) No core table modifications Verification URL: index.php?m=email_verification&verify=TOKEN Languages: English, Russian, Dutch Requirements: WHMCS 8.0+, PHP 7.4+, MySQL 5.7+ Installation: Upload modules folder to WHMCS root Activate in Setup → Addon Modules Configure token expiry and rate limits Customize overlay appearance Edit email template in Setup → Email Templates → "Email Verification" Client Features: Receives verification email on registration Clicks link to verify Re-verify required on email change Resend verification email (rate limited) Admin Features: View verification status in client profile Configure token expiry Set rate limit parameters Full overlay styling control Automatic cleanup of expired tokens Why it exists: Built because WHMCS native email verification doesn't block access. Users could skip verification and access the client area. This module enforces verification with a blocking overlay and adds rate limiting to prevent abuse. Support: support@arkhost.com License: One per WHMCS install WHMCS Marketplace: https://marketplace.whmcs.com/product/8286-email-verify-pro Documentation: https://arkhost.com/knowledgebase/5545/Email-Verify-Pro-for-WHMCS.html Store: https://arkhost.com/store/whmcs-modules/email-verify-pro
  12. Yes, you'll need some coding work to achieve this. When you use product type "Other" with a custom Hostname field, WHMCS doesn't automatically pass that value to your provisioning module. You need a hook that maps the custom field to the domain field.
  13. We've finally solved with a hook that catch all the necessary data when the "pay now" button is clicked. I wonder why whmcs doesn't want to implement something like that... @whmcs team.. any reason???
  14. Most of the users who sign up for whmcs are mostly to reduce the cost of creating a complete system, that includes the payments that whmcs supports, even if the website administrator has the ability to code, we still can't see or find the problem because the stripe module is encrypted by whmcs, most of the people here have tried to contact stripe, stripe pushes it to whmcs, in the end the people who actually pay to use the services of both sides have to find a solution to the problem of both sides.
  15. Hi there! We'd love to go over the individual requirements together and get started on this Telegram integration with you. We've just sent you an email with the info you need to get started. Looking forward to hearing from you soon!
  16. Yes, I also thought the same. But we provisioned the server automatically after payments. If I create a custom field for the hostname, how is the server going to use that hostname? I think we’ll need some coding work to achieve this
  17. HI You can find a couple at https://marketplace.whmcs.com/search?search=Telegram+Notification, but if you still need your ow,n then please contact me at https://www.whmcsservices.com/customworks.php
  18. I've not tried any alternatives - this issue aside, I do like WHMCS. Instead, I implemented Stripe directly into our website and just register the payments in WHMCS using the API. It was a bit painful because Stripe offers like a million different options, but it works now and we have 0 uncaptured payments.
  19. Can you not set as other and create a custom field for the product and label it Hostname
  20. That's a nice idea, but this way the hostname option we offer will be gone
  21. Hello everyone, We’re pleased to announce the release of our new module: Spanish Fiscal E-Invoicing (VeriFactura / TicketBAI) for WHMCS from WHMCS Global Services. ✅ What it is This module automates e-invoicing in Spain and ensures full compliance with the antifraud regulations (VeriFactura) and the regional Basque requirement (TicketBAI). It integrates directly into your WHMCS billing system to handle Spanish-client invoicing. WHMCS Global Services+1 ✳ Key features Automatically generate invoices in WHMCS with the required XML format for Spain’s tax agency. WHMCS Global Services Real-time submission of invoice data to the Spanish tax agency (AEAT) via API. WHMCS Global Services Export structured XML data and CSV reports for your Spanish clients. WHMCS Global Services Full log and traceability of invoice generation and transmission. WHMCS Global Services Automatic activation only for clients whose country is Spain (so it won’t interfere with your global invoicing flows). WHMCS Global Services 🕒 Why act now From 1 January 2026, submission via VeriFactura becomes mandatory for companies / corporate tax payers. WHMCS Global Services From 1 July 2026, it becomes mandatory for all other businesses and self-employed workers. WHMCS Global Services If you operate in Spain (or have Spanish clients) and you’re using WHMCS, this module can save you from manual processes and possible compliance risk. 💡 License & pricing highlight Compatible with WHMCS 8.13.x and PHP 8.2 / 8.3. WHMCS Global Services We’re offering a 40% off introductory discount. WHMCS Global Services 10-Day money-back guarantee. WHMCS Global Services 🔧 Implementation & support The module is designed for straightforward setup. If you need help with configuration, API credentials (via partner Fiskaly), or guidance, the team provides support via ticketing and even remote assistance. WHMCS Global Services 👥 Who should consider it Hosting providers, SaaS businesses, service companies using WHMCS and servicing Spanish clients WHMCS resellers or multi-country operators with a Spanish billing component Any WHMCS user looking to automate Spanish tax-compliant invoicing easily If you’d like a demo or more details, feel free to contact our team or visit the product page. We’re happy to answer any questions or walk you through the setup. Thanks and looking forward to your feedback! Best regards, The WHMCS Global Services Team
  22. If a client purchases a service using the credit on his account how come I cannot refund it to credit? I understand why you might not be able to refund to a credit card or PayPal since the funds did not come from that source. But I should always be able to refund to the credit balance. Now I have to add the credit manually and I cannot even mark the invoice refunded. I don't know what to do with the invoice now.
  23. I am Sharing below the Complete Code , that will run webhook and Send data to your webhook URL for both when a new ticket is created and When a ticket is replied, remember to update your webhook URL in the code, and use this code in public_html-whmcs-includes- hooks-(create a new file: ticketWebhook.php) and paste this code by updating ur webhook URL on the code <?php if (!defined("WHMCS")) { die("This file cannot be accessed directly"); } use WHMCS\Database\Capsule; /** * CONFIG */ const PRIMARY_WEBHOOK_URL = "YOUR WEBHOOK URL"; const ADMIN_REPLY_WEBHOOK_URL = "YOUR WEBHOOK URL"; /** * When false: admin replies will NOT be posted to PRIMARY_WEBHOOK_URL (only to ADMIN_REPLY_WEBHOOK_URL). * When true: admin replies are posted to BOTH URLs (legacy behavior). */ const SEND_ADMIN_REPLY_TO_PRIMARY = false; function wh_log($message) { try { logActivity("WebhookHook: " . $message); } catch (Exception $e) { // ignore } $file = '/tmp/whmcs_ticket_webhook.log'; $line = '[' . date('c') . '] ' . $message . PHP_EOL; @file_put_contents($file, $line, FILE_APPEND | LOCK_EX); } function get_ticket_reference($ticket) { $possibleFields = ['tid', 'ticketnum', 'ref', 'ticketid_public', 'ticketnumber', 'ticket_ref']; foreach ($possibleFields as $field) { if (isset($ticket->$field) && !empty($ticket->$field)) { return (string)$ticket->$field; } } if (!empty($ticket->title)) { $title = (string)$ticket->title; if (preg_match('/#?([A-Z]{2,6}-\d{3,12})/i', $title, $m)) { return '#' . strtoupper($m[1]); } if (preg_match('/#(\d{3,12})/', $title, $m2)) { return '#' . $m2[1]; } } return ''; } function fetch_ticket($ticketId) { return Capsule::table('tbltickets')->where('id', $ticketId)->first(); } function fetch_client($clientId) { return Capsule::table('tblclients')->where('id', $clientId)->first(); } function fetch_admin_name($adminId) { if (empty($adminId)) { return ''; } try { $admin = Capsule::table('tbladmins')->where('id', $adminId)->first(); if ($admin) { return trim(($admin->firstname ?? '') . ' ' . ($admin->lastname ?? '')) ?: ($admin->username ?? ''); } } catch (Exception $e) { // ignore } return ''; } function post_with_retries($url, $payload, $extraHeaders = [], $maxAttempts = 2) { $json = json_encode($payload); $attempt = 0; $lastError = null; while ($attempt < $maxAttempts) { $attempt++; $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $json); $headers = array_merge(['Content-Type: application/json'], $extraHeaders); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 6); $resp = curl_exec($ch); $errno = curl_errno($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlErr = curl_error($ch); curl_close($ch); if ($errno === 0 && ($httpCode >= 200 && $httpCode < 300)) { wh_log("POST success to {$url} (attempt {$attempt}) http={$httpCode}"); return ['ok' => true, 'code' => $httpCode, 'body' => $resp]; } $lastError = "attempt {$attempt} failed: errno={$errno} http={$httpCode} curlErr={$curlErr}"; wh_log("POST error to {$url}: {$lastError}"); sleep($attempt); } return ['ok' => false, 'error' => $lastError]; } function build_base_payload($ticket, $client, $event, $isCreation = false) { $ticketId = $ticket->id ?? ''; $ticketRef = get_ticket_reference($ticket); if (empty($ticketRef)) { $ticketRef = 'TCKT-' . (string)$ticketId; } return [ "ticket_id" => (string)$ticketId, "ticket_ref" => (string)$ticketRef, "ticket_subject" => (string)($ticket->title ?? ''), "mobile" => (string)($client->phonenumber ?? ''), "client_name" => trim((string)($client->firstname ?? '') . ' ' . (string)($client->lastname ?? '')), "event" => $event, "timestamp" => date('c'), "ticket_status" => isset($ticket->status) ? $ticket->status : '', // explicit flag so receivers can easily know if it's a creation event "is_creation_event" => $isCreation ? true : false ]; } function send_primary_webhook($payload) { if (empty(PRIMARY_WEBHOOK_URL)) { wh_log("Primary webhook not configured; skip"); return; } $res = post_with_retries(PRIMARY_WEBHOOK_URL, $payload, ['X-Ticket-Event: ' . ($payload['event'] ?? '')]); if (!$res['ok']) { wh_log("Primary webhook POST failed: " . ($res['error'] ?? 'unknown')); } } function send_admin_reply_webhook($payload) { if (empty(ADMIN_REPLY_WEBHOOK_URL)) { wh_log("Admin reply webhook not configured; skip"); return; } $res = post_with_retries(ADMIN_REPLY_WEBHOOK_URL, $payload, ['X-Ticket-Event: ' . ($payload['event'] ?? '')]); if (!$res['ok']) { wh_log("Admin reply webhook POST failed: " . ($res['error'] ?? 'unknown')); } } function send_ticket_webhook($ticketId, $event, $isCreation = false) { try { $ticket = fetch_ticket($ticketId); if (!$ticket) { wh_log("Ticket not found (send_ticket_webhook): {$ticketId}"); return; } $client = fetch_client($ticket->userid ?? 0); $payload = build_base_payload($ticket, $client, $event, $isCreation); send_primary_webhook($payload); } catch (Exception $ex) { wh_log("Exception in send_ticket_webhook: " . $ex->getMessage()); } } function handle_admin_reply($vars) { try { $ticketId = $vars['ticketid'] ?? null; if (!$ticketId) { wh_log("handle_admin_reply: ticketid not found in vars"); return; } $ticket = fetch_ticket($ticketId); if (!$ticket) { wh_log("handle_admin_reply: ticket not found {$ticketId}"); return; } $client = fetch_client($ticket->userid ?? 0); // create payload and mark isCreation = false $payload = build_base_payload($ticket, $client, "ticket_replied_by_admin", false); // reply message $replyMessage = ''; if (!empty($vars['message'])) { $replyMessage = $vars['message']; } else { try { $lastReply = Capsule::table('tblticketreplies') ->where('tid', $ticketId) ->orWhere('ticketid', $ticketId) ->orderByDesc('id') ->first(); if ($lastReply && !empty($lastReply->message)) { $replyMessage = $lastReply->message; } } catch (Exception $e) { // ignore } } $payload['reply_message'] = (string)$replyMessage; // admin info $adminId = $vars['adminid'] ?? $vars['admin'] ?? null; $payload['admin_id'] = (string)$adminId; $payload['admin_name'] = fetch_admin_name($adminId); if (!empty($vars['ticketreplyid'])) { $payload['ticket_reply_id'] = (string)$vars['ticketreplyid']; } // Decide where to send: // - always send to ADMIN_REPLY_WEBHOOK_URL send_admin_reply_webhook($payload); // - optionally send to primary webhook if configured if (SEND_ADMIN_REPLY_TO_PRIMARY) { // mark event and send $payload['event'] = 'ticket_replied_by_admin'; $payload['is_creation_event'] = false; send_primary_webhook($payload); } else { wh_log("Admin reply not forwarded to primary (SEND_ADMIN_REPLY_TO_PRIMARY=false). Sent only to admin-reply webhook."); } } catch (Exception $ex) { wh_log("Exception in handle_admin_reply: " . $ex->getMessage()); } } /** * Hooks */ add_hook('TicketOpen', 1, function ($vars) { $ticketId = $vars['ticketid']; wh_log("Hook TicketOpen triggered for ticket {$ticketId}"); // creation event: mark isCreation = true send_ticket_webhook($ticketId, "ticket_created_by_client", true); }); add_hook('TicketOpenAdmin', 1, function ($vars) { $ticketId = $vars['ticketid']; wh_log("Hook TicketOpenAdmin triggered for ticket {$ticketId}"); // admin-created ticket: creation event send_ticket_webhook($ticketId, "ticket_created_by_admin", true); }); add_hook('TicketUserReply', 1, function ($vars) { $ticketId = $vars['ticketid']; wh_log("Hook TicketUserReply triggered for ticket {$ticketId}"); send_ticket_webhook($ticketId, "ticket_replied_by_client", false); }); add_hook('TicketAdminReply', 1, function ($vars) { $ticketId = $vars['ticketid'] ?? null; wh_log("Hook TicketAdminReply triggered for ticket " . ($ticketId ?? 'unknown')); handle_admin_reply($vars); });
  24. Update v 1.0.4 based on feedback from peers using this addon. Added a Monitoring system and Queue system to prevent overloading the WHMCS database during data iteration. UI and Ajax system optimization within the queue system. Added Event Type / Hook Type to the queue system. Added several event notifications. Improved error handling for compatibility with the latest WHMCS version. Optimized integration with several WhatsApp gateway providers, resolving frequent timeout issues previously experienced with one specific provider. Tested: Successfully sent 3600 messages in 1 hour to 200+ WhatsApp numbers.
  25. Hi, to fix the issue go to Settings > General Settings, under Ordering Tab look for "Enable Random Usernames" uncheck it if checked.
  26. Hello, I am developing an application that needs to interact with the WHMCS API. When I attempt to authenticate using an identifier and secret, I receive the following error: "undefined result=error;message=Authentication Failed".
  27. Hello, I am looking for a WHMCS developer who can create a custom Telegram integration for my instance. The goal is to receive real-time Telegram notifications for key events such as: New support tickets opened New support ticket replies New invoices generated Newly paid invoices New client registrations Requirements: Must be implemented using WHMCS hooks Should send messages to a specified Telegram group via a telegram bot Ability to guide through setup and deployment is appreciated If you are experienced with WHMCS hooks and Telegram Bot API development, please contact me. Email: vpsworker@proton.me Thank you.
  1. Load more activity
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use & Guidelines and understand your posts will initially be pre-moderated