Skip to main content

Cloudflare Hosts Service

๐Ÿ“– Overviewโ€‹

Service Path: external/Integrations/Cloudflare/services/hosts.service.js

The Cloudflare Hosts service manages custom hostnames (domains) on Cloudflare's CDN infrastructure for DashClick's Lightning Domains feature. Core responsibilities include:

  • Custom Hostname Management: Create, read, update, and delete custom hostnames
  • SSL Certificate Provisioning: Automatic SSL certificate generation via Let's Encrypt
  • Domain Verification: DNS and ownership verification for custom domains
  • OneBalance Integration: Charge accounts for domain additions (with free tier)
  • Funnel Management: Track which funnels use which domains
  • Domain Cleanup: Remove domain associations when deleted

๐Ÿ—„๏ธ Collections Usedโ€‹

  • hostname - Domain name
  • cf_id - Cloudflare custom hostname ID
  • ssl - SSL certificate status
  • status - Domain status
  • cname_validation - DNS validation records
  • included - Whether domain is in free tier
  • account_id - Owner account
  • parent_account - Main account (for white-label)

๐Ÿ”„ Data Flowโ€‹

Custom Hostname Creation Flowโ€‹

sequenceDiagram
participant Client
participant HostsService
participant OneBalance
participant LightningDomainDB
participant CloudflareAPI
participant FunnelCleanup

Client->>HostsService: createHost(domain, account_id, parent_account)

HostsService->>HostsService: Check if free tier available
Note over HostsService: First 2 domains free for main accounts

alt Free Tier Available
HostsService->>HostsService: Mark as included = true
else Must Pay
HostsService->>OneBalance: verifyBalance(lighting_domain)
OneBalance-->>HostsService: Balance verified
end

HostsService->>CloudflareAPI: POST /custom_hostnames
Note over HostsService,CloudflareAPI: SSL: DV with TXT validation

CloudflareAPI-->>HostsService: Custom hostname created

HostsService->>CloudflareAPI: GET /custom_hostnames/:id
Note over HostsService,CloudflareAPI: Get full verification details
CloudflareAPI-->>HostsService: Full hostname data

HostsService->>LightningDomainDB: Upsert domain record
Note over HostsService,LightningDomainDB: Store cf_id, ssl status, CNAME records

HostsService->>FunnelCleanup: cleanupFunnelDomain()
Note over HostsService,FunnelCleanup: Remove old domain associations

HostsService-->>Client: Lightning domain created

style HostsService fill:#e3f2fd
style CloudflareAPI fill:#fff4e6
style OneBalance fill:#ffe0b2

Domain Deletion Flowโ€‹

flowchart TD
A[Delete Request] --> B[Find domain in DB]
B --> C{Domain exists?}

C -->|No| D[Throw 404 error]
C -->|Yes| E[Delete from Cloudflare API]

E --> F{Cloudflare deletion successful?}
F -->|No| G[Throw error]
F -->|Yes| H[Cleanup funnel associations]

H --> I[Delete from LightningDomain DB]
I --> J[Update funnel domain status]

J --> K{Was domain 'included'?}
K -->|Yes| L[Promote another domain to 'included']
K -->|No| M[No promotion needed]

L --> N[Return success]
M --> N
D --> O[End]
G --> O

style E fill:#ffcdd2
style H fill:#e1bee7
style L fill:#c8e6c9

๐Ÿ”ง Business Logic & Functionsโ€‹

createHost({ domain, account_id, parent_account, auth })โ€‹

Purpose: Create a new custom hostname on Cloudflare with SSL certificate

Parameters:

  • domain (String) - Custom domain name (e.g., 'example.com')
  • account_id (ObjectId) - DashClicks account ID
  • parent_account (ObjectId) - Main account ID (for white-label accounts)
  • auth (Object) - Authentication context
    • account (Object) - Account object
    • uid (ObjectId) - User ID

Returns:

{
_id: ObjectId,
hostname: String,
cf_id: String, // Cloudflare custom hostname ID
cname_validation: {
name: String, // Subdomain part (e.g., 'example')
value: String // CNAME target: 'sites.urlme.app'
},
ownership_verification: {
name: String, // TXT record name
value: String, // TXT record value
type: String // 'txt'
},
ssl: {
status: String, // 'pending_validation', 'active', etc.
method: String, // 'txt'
type: String, // 'dv' (Domain Validation)
validation_records: Array
},
status: String, // 'pending', 'active', 'moved'
account_id: ObjectId,
parent_account: ObjectId,
included: Boolean, // True if in free tier
created_at: Date
}

Business Logic Flow:

  1. Check Free Tier Eligibility

    let included = false;
    if (account_id.toString() == parent_account.toString()) {
    const totalIncluded = await LightningDomain.countDocuments({
    included: true,
    parent_account,
    });
    if (totalIncluded < 2) {
    included = true; // First 2 domains free for main accounts
    }
    }
  2. Verify OneBalance (If Not Free)

    if (!included) {
    await verifyBalance({
    event: 'lighting_domain',
    account: auth.account,
    user_id: auth.uid,
    quantity: 1,
    });
    }
  3. Create Custom Hostname on Cloudflare

    const reqBody = {
    hostname: domain,
    ssl: {
    type: 'dv', // Domain Validation
    method: 'txt', // TXT record verification
    hosts: [domain],
    settings: {
    min_tls_version: '1.2',
    },
    bundle_method: 'ubiquitous',
    wildcard: false,
    },
    };

    let response = await axios.post(
    `${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames`,
    reqBody,
    {
    headers: {
    Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
    },
    },
    );
  4. Fetch Full Hostname Details

    response = await axios.get(
    `${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${response.result.id}`,
    {
    headers: {
    Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
    },
    },
    );
  5. Store in Database

    response.result.cf_id = response.result.id;
    delete response.result.id;

    let lightningDomain = await LightningDomain.findOneAndUpdate(
    { hostname: response.result.hostname },
    {
    account_id,
    parent_account,
    ...response.result,
    cname_validation: {
    name: response.result.hostname.split('.')[0],
    value: 'sites.urlme.app',
    },
    included: included ? included : undefined,
    },
    { upsert: true, new: true },
    );
  6. Cleanup Funnel Associations

    try {
    await cleanupFunnelDomain({ hostname: hostname });
    } catch (error) {}

API Endpoint: POST https://api.cloudflare.com/client/v4/zones/:zone_id/custom_hostnames

Key Business Rules:

  • Free Tier: Main accounts get first 2 domains free (included = true)
  • Billing: Non-free domains charge OneBalance before creation
  • SSL Type: Always uses Domain Validation (DV) with TXT method
  • TLS Version: Minimum TLS 1.2 enforced
  • CNAME Target: All domains point to 'sites.urlme.app'
  • Upsert Logic: Updates existing domain if hostname already exists

getHosts({ account_id, connected })โ€‹

Purpose: List all custom hostnames for an account with funnel associations

Parameters:

  • account_id (ObjectId) - Account ID
  • connected (String) - Filter by connection status
    • 'all' - All domains
    • undefined or other - Only connected/active domains

Returns:

[
{
_id: ObjectId,
hostname: String,
cf_id: String,
cname_validation: Object,
ownership_verification: Object,
ownership_verification_http: Object,
ssl: {
status: String, // 'pending_validation', 'active'
// ... other SSL fields
},
status: String, // 'pending', 'active', 'moved'
created_at: Date,
verification_errors: Array,
defaultDomainHomePageRule: Object,
defaultDomain404Rule: Object,
funnels: [
// Joined from funnels collection
{
funnel_id: ObjectId,
funnel_name: String,
},
],
},
];

Business Logic Flow:

  1. Build Match Condition

    let condition = {
    account_id,
    };

    if (connected && connected != 'all') {
    condition.$and = [{ 'ssl.status': 'active' }, { status: 'active' }];
    }
  2. Aggregate with Funnels

    let lDomains = await LightningDomain.aggregate([
    { $match: condition },
    {
    $lookup: {
    from: 'funnels',
    localField: '_id',
    foreignField: 'domain_id',
    as: 'funnels',
    },
    },
    {
    $addFields: {
    funnels: {
    $map: {
    input: '$funnels',
    as: 'funnel',
    in: {
    funnel_id: '$$funnel._id',
    funnel_name: '$$funnel.name',
    },
    },
    },
    },
    },
    {
    $project: {
    _id: 1,
    hostname: 1,
    cf_id: 1,
    cname_validation: 1,
    ownership_verification: 1,
    ownership_verification_http: 1,
    ssl: 1,
    status: 1,
    created_at: 1,
    verification_errors: 1,
    defaultDomainHomePageRule: 1,
    defaultDomain404Rule: 1,
    funnels: 1,
    },
    },
    ]);

    return lDomains;

Key Business Rules:

  • Connected Filter: Only returns domains with ssl.status='active' AND status='active'
  • Funnel Associations: Includes all funnels using each domain
  • Account Scoped: Only returns domains for specified account

updateHost({ host_id, hostInfo })โ€‹

Purpose: Update custom hostname configuration (placeholder - not implemented)

Parameters:

  • host_id (ObjectId) - Lightning domain ID
  • hostInfo (Object) - Updated host information

Implementation Status: ๐Ÿšง NOT IMPLEMENTED

try {
//Will change default page and 404 page here only
} catch (error) {
throw badRequest('Bad request', 'BAD_REQUEST');
}

Intended Use: Update default homepage and 404 page rules for domain


getHost({ domain_id, account_id })โ€‹

Purpose: Refresh single domain status from Cloudflare and sync to database

Parameters:

  • domain_id (ObjectId) - Lightning domain ID
  • account_id (ObjectId) - Account ID (for ownership validation)

Returns: Updated LightningDomain document

Business Logic Flow:

  1. Find Domain in Database

    let lDomain = await LightningDomain.findOne({ _id: domain_id, account_id });
    if (!lDomain) throw notFound('Domain not found');
  2. Fetch Latest Status from Cloudflare

    try {
    response = await axios.get(
    `${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${lDomain.cf_id}`,
    {
    headers: {
    Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
    },
    },
    );
    } catch (err) {
    // Handle deleted hostname on Cloudflare
    if (err?.response?.data?.errors?.[0]?.message == 'The custom hostname was not found.') {
    await LightningDomain.deleteOne({ _id: domain_id });
    await reviewAndSetStatusForDomain({ domain_id: domain_id });
    }
    throw badRequest(err?.response?.data?.errors?.[0]?.message || 'Something went wrong');
    }
  3. Update Database with Cloudflare Data

    response = response.data;
    if (!response?.success) {
    throw badRequest(response.errors?.[0]?.message);
    }

    lDomain = await LightningDomain.findByIdAndUpdate(
    domain_id,
    { ...response.result },
    { new: true },
    );

    return lDomain;

Key Business Rules:

  • Sync with Cloudflare: Updates local database with latest Cloudflare status
  • Ownership Validation: Ensures domain belongs to requesting account
  • Orphan Cleanup: Removes domain from DB if deleted from Cloudflare
  • Status Review: Triggers funnel domain status update after sync

deleteHost({ domain_id, account_id })โ€‹

Purpose: Delete custom hostname from Cloudflare and database

Parameters:

  • domain_id (ObjectId) - Lightning domain ID
  • account_id (ObjectId) - Account ID

Returns: true on success

Business Logic Flow:

  1. Find and Validate Domain

    let lDomain = await LightningDomain.findOne({ _id: domain_id, account_id });
    if (!lDomain) throw notFound('Domain not found');
  2. Delete from Cloudflare

    let response = await axios.delete(
    `${BASE_URL}/${process.env.LD_CLOUDFLARE_ZONE_ID}/custom_hostnames/${lDomain.cf_id}`,
    {
    headers: {
    Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
    },
    },
    );

    response = response.data;
    if (!response?.success) {
    throw badRequest(response.errors?.[0]?.message);
    }
  3. Cleanup Funnel Associations

    try {
    await cleanupFunnelDomain({ hostname: hostname, domain_id: domain_id });
    } catch (error) {}
  4. Delete from Database

    await LightningDomain.deleteOne({ _id: domain_id, account_id });
    await reviewAndSetStatusForDomain({ domain_id: domain_id });
  5. Promote Replacement Domain (If Free Tier)

    if (lDomain.included) {
    // Find another domain to make free
    await LightningDomain.findOneAndUpdate(
    { account_id, included: { $ne: true } },
    { included: true },
    );
    }

Key Business Rules:

  • Cloudflare First: Deletes from Cloudflare before database
  • Funnel Cleanup: Removes domain associations from funnels
  • Free Tier Replacement: If deleted domain was free, promotes another domain to free tier
  • Status Review: Updates funnel domain status after deletion

๐Ÿ”€ Integration Pointsโ€‹

Lightning Domains Creationโ€‹

const hostsService = require('./services/hosts.service');

// Create custom hostname
const domain = await hostsService.createHost({
domain: 'mybusiness.com',
account_id: accountId,
parent_account: mainAccountId,
auth: {
account: accountObj,
uid: userId,
},
});

console.log(`Domain created: ${domain.hostname}`);
console.log(`CNAME record: ${domain.cname_validation.name} โ†’ ${domain.cname_validation.value}`);
console.log(`TXT verification: ${domain.ownership_verification.value}`);

Domain Verification Statusโ€‹

// Refresh domain status from Cloudflare
const domain = await hostsService.getHost({
domain_id: domainId,
account_id: accountId,
});

if (domain.ssl.status === 'active' && domain.status === 'active') {
console.log('Domain is fully connected!');
} else {
console.log(`SSL: ${domain.ssl.status}, Domain: ${domain.status}`);
}

List Active Domainsโ€‹

// Get only connected domains
const activeDomains = await hostsService.getHosts({
account_id: accountId,
connected: 'connected', // Only active domains
});

activeDomains.forEach(domain => {
console.log(`${domain.hostname} - ${domain.funnels.length} funnels`);
});

๐Ÿงช Edge Cases & Special Handlingโ€‹

Free Tier Managementโ€‹

First 2 Domains Free for Main Accounts:

if (account_id.toString() == parent_account.toString()) {
const totalIncluded = await LightningDomain.countDocuments({
included: true,
parent_account,
});
if (totalIncluded < 2) {
included = true;
}
}

Free Tier Replacement on Deletion:

if (lDomain.included) {
await LightningDomain.findOneAndUpdate(
{ account_id, included: { $ne: true } },
{ included: true },
);
}

Cloudflare API Errorsโ€‹

Custom Hostname Not Found:

if (err?.response?.data?.errors?.[0]?.message == 'The custom hostname was not found.') {
await LightningDomain.deleteOne({ _id: domain_id });
await reviewAndSetStatusForDomain({ domain_id: domain_id });
}

OneBalance Verification Failureโ€‹

Insufficient Balance:

await verifyBalance({
event: 'lighting_domain',
account: auth.account,
user_id: auth.uid,
quantity: 1,
});
// Throws error if balance insufficient

Duplicate Domain Creationโ€‹

Upsert Logic:

let lightningDomain = await LightningDomain.findOneAndUpdate(
{ hostname: response.result.hostname },
{
/* ... */
},
{ upsert: true, new: true }, // Updates if exists, creates if not
);

โš ๏ธ Important Notesโ€‹

  1. Free Tier: Main accounts get 2 free domains. Sub-accounts never get free domains.

  2. OneBalance: Domains beyond free tier deduct from OneBalance before creation.

  3. SSL Provisioning: Automatic via Let's Encrypt using TXT record validation.

  4. CNAME Target: All domains point to sites.urlme.app for content delivery.

  5. Verification: Requires both DNS (CNAME) and ownership (TXT) verification for SSL activation.

  6. Status Sync: getHost() refreshes domain status from Cloudflare to keep database in sync.

  7. Funnel Cleanup: Deleting domain removes associations from funnels automatically.

  8. Cloudflare Zone: Uses single Cloudflare zone (LD_CLOUDFLARE_ZONE_ID) for all custom hostnames.

  9. TLS Security: Enforces minimum TLS 1.2 for all SSL certificates.

  10. Error Handling: Cloudflare API errors propagated to client with error messages.

  11. Orphan Detection: If domain deleted from Cloudflare, automatically removed from database.

  12. Free Tier Promotion: When free domain deleted, another domain automatically promoted to free tier.

๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM