Mahbubur Riad
Back to blog
Hosting & Server 3 min read

WHMCS DNS Automation with Cloudflare API: A Step-by-Step Guide

Jun 16, 2026 · Mahbubur Riad

Learn how to automate domain DNS provisioning and management in WHMCS using Cloudflare’s API — with real code, setup steps, and troubleshooting tips.

On this page

WHMCS DNS Automation with Cloudflare API: A Step-by-Step Guide

If you run a hosting business — especially one offering domain registration, reseller hosting, or VPS — you’ve probably spent too many hours manually adding DNS zones or records for new domains.

I’ve been there. Manual DNS setup in WHMCS is tedious, error-prone, and scales poorly. But when I integrated Cloudflare’s API to automate DNS provisioning, the time saved was massive — and my clients noticed too.

In this guide, I’ll walk you through how to automate domain DNS provisioning in WHMCS using the Cloudflare API — from setup to real-world code examples. No fluff, no theory. Just what works.

Why Automate DNS in WHMCS?

Before diving in, let’s be honest: WHMCS doesn’t natively support deep Cloudflare integration out-of-the-box. You can manually link domains to Cloudflare, but that’s not scalable.

Here’s what automation buys you:

  • Zero-touch DNS provisioning for new domain registrations or transfers
  • Instant DNS record updates (e.g., after server migration or SSL deployment)
  • Consistent DNS setup — no more “why did this break?” debugging
  • Reduced ticket volume — clients manage DNS self-service via WHMCS portal

If you’re using Cloudflare (and most of us are), this integration pays off fast.


Prerequisites

Before coding, make sure you have:

  • ✅ A WHMCS installation (v8.5+ recommended)
  • ✅ A Cloudflare API Token with permissions: Zone:DNS:Read and Zone:DNS:Edit (we’ll set this up next)
  • ✅ Basic PHP/WHMCS hook knowledge (we’ll keep it simple)
  • ✅ One or more domains added in Cloudflare as full (not partial) zones

Step 1: Create a Cloudflare API Token

This is the most common stumbling block — and it’s easy to mess up.

  1. Log in to Cloudflare Dashboard
  2. Go to My ProfileAPI Tokens
  3. Click Create Token
  4. Use the Custom Token template
  5. Set permissions:
    • Zone: Read
    • Zone: DNS: Edit
  6. Under Zone Resources, select All zones in all accounts (or restrict to specific zones if you’re cautious)
  7. Copy the token — do not lose it. You’ll never see it again.

🔒 Pro Tip: Never store this token in plain text in your WHMCS database. We’ll use WHMCS’s secure configuration file.


Step 2: Store the Token Securely in WHMCS

Open configuration.php (in /whmcsroot/includes/) and add:

PHP
// Cloudflare API configuration
$cloudflare_api_token = 'YOUR_CLOUDFLARE_API_TOKEN_HERE';
$cloudflare_api_email = '[email protected]'; // Only needed if using legacy API key

🛑 Do not hardcode secrets directly in hooks or modules. Always use configuration.php.


Step 3: Hook Into WHMCS Domain Registration

We’ll use a WHMCS hook to trigger DNS provisioning right after a domain is registered.

Create a new file: includes/hooks/cloudflare_dns_provision.php

PHP
<?php

use WHMCS\Database\Capsule;

add_hook('AfterDomainRegister', 1, function($vars) {
    $domainId = $vars['domainid'];
    $domainName = $vars['domain'];

    // Fetch domain configuration
    $domain = Capsule::table('tblhosting')
        ->where('domainid', $domainId)
        ->first();

    if (!$domain || $domain->domain != $domainName) {
        logActivity("WHMCS: Skipping DNS provisioning — domain mismatch");
        return;
    }

    // Only proceed if domain is active and managed via Cloudflare
    $service = Capsule::table('tblhosting')
        ->where('id', $domain->id)
        ->where('domainstatus', 'Active')
        ->first();

    if (!$service) return;

    // Get Cloudflare zone ID (we’ll fetch it via API)
    $zoneId = getCloudflareZoneId($domainName);
    if (!$zoneId) {
        logActivity("WHMCS: Cloudflare zone not found for {$domainName}");
        return;
    }

    // Create default DNS records
    $records = [
        ['type' => 'A', 'name' => '@', 'content' => $domain->serverip ?: '192.0.2.1', 'ttl' => 1, 'priority' => null],
        ['type' => 'A', 'name' => 'www', 'content' => $domain->serverip ?: '192.0.2.1', 'ttl' => 1, 'priority' => null],
        ['type' => 'MX', 'name' => '@', 'content' => 'mail.example.com', 'ttl' => 1, 'priority' => 10],
        ['type' => 'TXT', 'name' => '@', 'content' => 'v=spf1 include:_spf.google.com ~all', 'ttl' => 1, 'priority' => null],
    ];

    foreach ($records as $record) {
        createCloudflareDnsRecord($zoneId, $record);
    }

    logActivity("WHMCS: DNS records provisioned for {$domainName} via Cloudflare");
});

⚠️ This is a simplified example. In production, you’d want better error handling, retries, and logging.


Step 4: Helper Functions — Zone Detection & Record Creation

Add these to the same hook file (or a helper file you include):

PHP
function getCloudflareZoneId($domainName) {
    $apiUrl = 'https://api.cloudflare.com/client/v4/zones';
    $params = [
        'name' => $domainName,
        'status' => 'active',
        'page' => 1,
        'per_page' => 1
    ];

    $query = http_build_query($params);
    $ch = curl_init("{$apiUrl}?{$query}");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $GLOBALS['cloudflare_api_token'],
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);

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

    if ($httpCode !== 200) {
        logActivity("WHMCS: Cloudflare API error (zone lookup): {$httpCode}");
        return false;
    }

    $data = json_decode($response, true);
    if (!empty($data['result'][0]['id'])) {
        return $data['result'][0]['id'];
    }

    return false;
}

function createCloudflareDnsRecord($zoneId, $record) {
    $apiUrl = "https://api.cloudflare.com/client/v4/zones/{$zoneId}/dns_records";

    $payload = json_encode([
        'type' => $record['type'],
        'name' => $record['name'],
        'content' => $record['content'],
        'ttl' => $record['ttl'],
        'priority' => $record['priority'] ?? null,
        'proxied' => false // Set to true if you want Cloudflare proxying
    ]);

    $ch = curl_init($apiUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $GLOBALS['cloudflare_api_token'],
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);

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

    if ($httpCode !== 200) {
        logActivity("WHMCS: Failed to create DNS record ({$record['type']} {$record['name']}): {$httpCode} - " . curl_error($ch));
        return false;
    }

    return true;
}

How It Works:

  • The hook fires on AfterDomainRegister
  • It fetches the Cloudflare zone ID by querying /zones with the domain name
  • Then it creates basic DNS records: A, AAAA (optional), MX, TXT
  • You can customize records per client or product — more on that below

Step 5: Handle Domain Transfers & Re-registrations

Domains aren’t always new — sometimes they’re transferred in. Add this hook:

PHP
add_hook('AfterDomainTransfer', 1, function($vars) {
    // Reuse getCloudflareZoneId() and createCloudflareDnsRecord() from above
    $domainName = $vars['domainname'];
    $zoneId = getCloudflareZoneId($domainName);

    if (!$zoneId) {
        logActivity("WHMCS: Zone not found for transferred domain {$domainName}");
        return;
    }

    // Optional: delete existing records first if re-syncing
    deleteExistingDnsRecords($zoneId);

    // Then provision again
    $records = [
        ['type' => 'A', 'name' => '@', 'content' => '192.0.2.1', 'ttl' => 1],
        ['type' => 'MX', 'name' => '@', 'content' => 'mail.example.com', 'ttl' => 1, 'priority' => 10],
    ];

    foreach ($records as $record) {
        createCloudflareDnsRecord($zoneId, $record);
    }

    logActivity("WHMCS: DNS records synced for transferred domain {$domainName}");
});

💡 For full control, you might want to add a “DNS Sync” button in the client area — but that’s a future upgrade.


Step 6: Optional — Client Portal Integration

To let users manage DNS themselves, add a simple tab in the client area:

  1. Create modules/domains/dns.php
  2. Use WHMCS’s ClientArea::setTemplateFilename() to inject content
  3. Fetch existing records with:
PHP
function getCloudflareDnsRecords($zoneId) {
    $apiUrl = "https://api.cloudflare.com/client/v4/zones/{$zoneId}/dns_records";
    $ch = curl_init($apiUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $GLOBALS['cloudflare_api_token'],
    ]);
    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    return $data['result'] ?? [];
}

Then render a table with edit/delete buttons. (Full implementation is out of scope here, but it’s just CRUD over the API.)


Comparison: Manual vs. Automated DNS Setup

Task Manual Setup Automated (Cloudflare API)
Time per domain 5–10 minutes <1 second
Error risk High (typos, missing MX) Very low (validated payload)
Scaling Fails at 100+ domains Scales to 10k+
Client self-service Not possible Yes (with extra UI)
Audit trail None Full log via logActivity()
Maintenance Ongoing admin effort One-time setup + monitoring

I’ve seen hosting providers go from 2 hours/day of DNS work to near-zero — just by automating this.


Real-World Gotchas (And How to Avoid Them)

Here’s what I learned the hard way:

❌ 1. Using the Wrong API Endpoint

Cloudflare has two APIs:

  • Legacy API (key + email) → deprecated
  • API Tokens (recommended) → token-only, scoped permissions

Always use API Tokens, not Global API Keys.

❌ 2. Not Handling Rate Limits

Cloudflare allows ~1,200 requests/5 minutes per token. If you provision 500 domains at once, you’ll hit the limit.

Fix: Add exponential backoff in createCloudflareDnsRecord():

PHP
$retry = 0;
$maxRetries = 3;
while ($retry < $maxRetries) {
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    if ($httpCode == 429) {
        $retry++;
        sleep(2 ** $retry); // exponential backoff
        continue;
    }

    break;
}

❌ 3. Hardcoding Default Records

One-size-fits-all DNS records rarely work. I recommend:

  • Grouping by product (e.g., “WordPress Hosting” → preloads A, MX, TXT with wp-config hints)
  • Letting clients override via custom fields in WHMCS

❌ 4. Ignoring TTL and Proxied Status

Cloudflare’s default ttl: 1 (auto) is great for speed, but some services (like Google Workspace) require higher TTLs.

Always double-check:

PHP
'ttl' => ($record['type'] == 'MX') ? 3600 : 1,
'proxied' => ($record['type'] == 'A' && $domain->uses_cloudflare_proxy) ? true : false,

Testing Your Integration

Before going live:

  1. Use WHMCS’s Test Domain Registration feature
  2. Check WHMCS → Utilities → Logs → Activity Log for WHMCS: entries
  3. Manually verify in Cloudflare dashboard that records appear
  4. Try a failed scenario: remove zone permission → see if logs catch it

If the hook fires but records don’t appear, enable CURLOPT_VERBOSE temporarily in your cURL calls.


FAQ: WHMCS + Cloudflare DNS Automation

Q: Can I use this with Cloudflare Partner accounts?
A: Yes — just create a separate API token for each partner account and store them dynamically (e.g., by tblclients.customfield).

Q: Does this work with domain-only orders (no hosting)?
A: Yes — but you’ll need to hook into AfterDomainRegister and AfterDomainTransfer, and fetch the domain name from $vars['domainname'].

Q: What if Cloudflare is down?
A: Add a fallback: if API fails, log it and skip DNS creation — don’t block domain registration.

Q: Can I sync DNS changes from Cloudflare back to WHMCS?
A: Not easily — WHMCS doesn’t track DNS state. You’d need a separate cron job to poll Cloudflare and sync.

Q: Is this secure?
A: Yes — if you follow the guide: store tokens in configuration.php, never expose them in hooks or templates, and use API tokens (not global keys).


Final Thoughts

Automating DNS with Cloudflare in WHMCS isn’t just about saving time — it’s about reducing human risk. One typo in an MX record can break email for a whole business. One missing A record can make a site unreachable.

When I first built this, I worried about complexity. But after a few iterations, it became one of the most reliable parts of my hosting stack.

Start small: just provision A and MX for new domains. Then expand.

If you’re building a scalable hosting business, this is one automation you can’t afford to skip.

— Mahbubur
Sysadmin and WHMCS integrator
mahbuburriad.com

Related

Related posts