Cloudflare Integration
🎯 Overview
The Cloudflare integration provides CDN (Content Delivery Network) and SSL certificate management for DashClicks' Lightning Domains feature. It enables custom domain hosting with automatic SSL provisioning through Cloudflare's Custom Hostnames API, allowing client websites to be served through custom domains with Let's Encrypt SSL certificates.
Provider: Cloudflare (https://www.cloudflare.com)
API Version: v4
Integration Type: REST API with Bearer Token authentication
Use Case: Custom domain hosting with automatic SSL for Lightning Domains
📁 Directory Structure
Documentation Structure:
cloudflare/
├── 📄 index.md - Integration overview and setup (this file)
└── 📄 hosts.md - Custom hostname management with SSL provisioning
Source Code Location:
- Base Path:
external/Integrations/Cloudflare/ - Services:
services/ - Controllers:
controllers/ - Routes:
routes/ - Schemas:
schemas/
🗄️ MongoDB Collections
📚 Detailed Schema: See Database Collections Documentation
lightning-domain
- Purpose: Store custom domain records and Cloudflare hostname IDs
- Model:
external/models/lightning-domain.js - Primary Use: Track custom hostnames, SSL status, DNS validation records
- Key Fields:
hostname- Custom domain name (e.g., 'example.com')cf_id- Cloudflare custom hostname IDssl.status- SSL certificate status ('pending_validation', 'active')status- Domain status ('pending', 'active', 'moved')cname_validation- DNS CNAME record requirementsownership_verification- TXT record for domain ownershipaccount_id- DashClicks account ownerparent_account- Main account (for white-label)included- Whether domain is in free tier (first 2 free)
funnels
- Purpose: Sales funnels using custom domains
- Model:
external/models/funnels.js - Primary Use: Link funnels to Lightning Domains via
domain_id
🔐 Authentication & Configuration
Authentication Method: Bearer Token (Cloudflare API Key)
Required Environment Variables:
| Variable | Description | Required |
|---|---|---|
CLOUDFLARE_API_KEY | Cloudflare API token with Zone:Edit permissions | ✅ |
LD_CLOUDFLARE_ZONE_ID | Cloudflare Zone ID for Lightning Domains | ✅ |
Credential Storage: Environment variables only (no database storage)
Authentication Flow:
// All API requests include Bearer token
headers: {
'Authorization': `Bearer ${process.env.CLOUDFLARE_API_KEY}`
}
🏗️ Architecture Overview
Key Responsibilities:
- Create custom hostnames on Cloudflare CDN
- Provision SSL certificates via Let's Encrypt (Domain Validation)
- Manage DNS validation (CNAME) and ownership verification (TXT)
- Track SSL certificate lifecycle (pending → active)
- Sync domain status between Cloudflare and DashClicks
- Implement free tier (first 2 domains free per account)
- Clean up funnel domain associations on deletion
- Handle domain status transitions (pending → active → deleted)
API Communication Pattern:
- RESTful HTTP/HTTPS API
- Synchronous request/response
- Bearer token authentication
- No webhook support (polling required for status updates)
Business Logic Flow:
graph TB
A[Create Domain Request] --> B{Check Free Tier}
B -->|First 2 domains| C[Mark as included=true]
B -->|Beyond free tier| D[Charge OneBalance]
C --> E[Create Custom Hostname on Cloudflare]
D --> E
E --> F[Store in lightning-domain collection]
F --> G[Return CNAME + TXT records]
G --> H[User configures DNS]
H --> I[Cloudflare validates DNS]
I --> J[Let's Encrypt issues SSL]
J --> K[Domain status: active]
style E fill:#fff4e6
style J fill:#c8e6c9
Rate Limiting:
- Cloudflare enforces API rate limits (varies by plan)
- No explicit rate limiting in code
- Recommended: 1200 requests/5 minutes for Enterprise
🔗 Features & Capabilities
Core Features
- 📘 Custom Hostname Management - Create, read, update, delete custom hostnames
- 📗 SSL Certificate Provisioning - Automatic SSL via Let's Encrypt with TXT validation
- 📙 Domain Verification - CNAME and ownership verification
- 📕 Free Tier Management - First 2 domains free per main account
- 📓 Funnel Integration - Link domains to funnels for Lightning Domains
- 📔 Status Synchronization - Sync domain status with Cloudflare API
🔄 Integration Data Flow
Custom Hostname Creation Flow
sequenceDiagram
participant Client
participant HostsService
participant OneBalance
participant LightningDomainDB
participant CloudflareAPI
Client->>HostsService: createHost(domain, account_id)
HostsService->>HostsService: Check free tier eligibility
alt Free tier available (first 2 domains)
HostsService->>HostsService: 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<br/>Min TLS: 1.2
CloudflareAPI-->>HostsService: Custom hostname created
HostsService->>CloudflareAPI: GET /custom_hostnames/:id
CloudflareAPI-->>HostsService: Full hostname details
HostsService->>LightningDomainDB: Upsert domain record
Note over LightningDomainDB: Store cf_id, ssl status,<br/>CNAME + TXT records
HostsService->>HostsService: cleanupFunnelDomain()
HostsService-->>Client: Return domain with DNS instructions
style CloudflareAPI fill:#fff4e6
style OneBalance fill:#ffe0b2
Domain Status Sync Flow
sequenceDiagram
participant Client
participant HostsService
participant LightningDomainDB
participant CloudflareAPI
Client->>HostsService: getHost(domain_id)
HostsService->>LightningDomainDB: Find domain by ID
LightningDomainDB-->>HostsService: Domain with cf_id
HostsService->>CloudflareAPI: GET /custom_hostnames/:cf_id
alt Domain exists on Cloudflare
CloudflareAPI-->>HostsService: Updated domain data
HostsService->>LightningDomainDB: Update local record
HostsService-->>Client: Return updated domain
else Domain deleted on Cloudflare
CloudflareAPI-->>HostsService: 404 Not Found
HostsService->>LightningDomainDB: Delete local record
HostsService->>HostsService: reviewAndSetStatusForDomain()
HostsService-->>Client: Error: Domain not found
end
style CloudflareAPI fill:#fff4e6
🔗 Submodules
Service Files
📘 Hosts Service
File: services/hosts.service.js
Functions: 5 functions for custom hostname management
Core capabilities:
createHost()- Create custom hostname with SSLgetHosts()- List all custom hostnames for accountgetHost()- Refresh single domain status from CloudflareupdateHost()- Update hostname configuration (not implemented)deleteHost()- Delete custom hostname from Cloudflare
🚨 Error Handling
Common Error Scenarios:
-
Invalid API Key
- Status: 401 Unauthorized
- Cause: Invalid or missing CLOUDFLARE_API_KEY
- Solution: Verify environment variable
-
Custom Hostname Not Found
- Status: 404
- Error: "The custom hostname was not found."
- Cause: Domain deleted from Cloudflare but still in database
- Handling: Automatically removes from database and updates funnel status
-
Zone ID Invalid
- Status: 404
- Cause: Invalid LD_CLOUDFLARE_ZONE_ID
- Solution: Verify zone ID in Cloudflare dashboard
-
Insufficient OneBalance
- Error: Balance verification failure
- Cause: Account lacks credits for domain beyond free tier
- Solution: Add credits to account
-
Domain Already Exists
- Behavior: Updates existing domain (upsert logic)
- No error thrown - domain record updated
📊 Monitoring & Logging
- No Explicit Logging: Errors propagated to caller
- Status Tracking: Domain status stored in
lightning-domaincollection - Sync Detection: Automatically detects Cloudflare-side deletions during getHost()
🎯 Key Integration Patterns
Free Tier Management
First 2 Domains Free for Main Accounts:
// Only main accounts (not sub-accounts) eligible
if (account_id.toString() == parent_account.toString()) {
const totalIncluded = await LightningDomain.countDocuments({
included: true,
parent_account,
});
if (totalIncluded < 2) {
included = true; // Free!
}
}
Free Tier Replacement on Deletion:
// If deleted domain was free, promote another
if (lDomain.included) {
await LightningDomain.findOneAndUpdate(
{ account_id, included: { $ne: true } },
{ included: true },
);
}
SSL Certificate Provisioning
Automatic SSL with Let's Encrypt:
ssl: {
type: 'dv', // Domain Validation
method: 'txt', // TXT record verification
hosts: [domain],
settings: {
min_tls_version: '1.2'
},
bundle_method: 'ubiquitous',
wildcard: false
}
Verification Requirements:
- CNAME Record:
example.com→sites.urlme.app - TXT Record:
_acme-challenge.example.com→[validation_value]
Status Synchronization
Refresh Domain Status:
// Fetch latest status from Cloudflare
const response = await axios.get(`${BASE_URL}/${ZONE_ID}/custom_hostnames/${cf_id}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
// Update local database
await LightningDomain.findByIdAndUpdate(domain_id, response.result);
Orphan Detection:
// If domain deleted on Cloudflare, clean up locally
if (err?.response?.data?.errors?.[0]?.message == 'The custom hostname was not found.') {
await LightningDomain.deleteOne({ _id: domain_id });
await reviewAndSetStatusForDomain({ domain_id });
}
⚠️ Important Notes
- CNAME Target: All domains point to
sites.urlme.appfor content delivery - Free Tier: Main accounts get 2 free domains; sub-accounts pay for all domains
- OneBalance Integration: Domains beyond free tier deduct from OneBalance before creation
- SSL Provisioning: Automatic via Let's Encrypt using TXT record validation
- Minimum TLS: Enforces TLS 1.2 minimum for all SSL certificates
- Upsert Logic: Creating domain with existing hostname updates instead of erroring
- Funnel Cleanup: Deleting domain automatically removes funnel associations
- Status Sync: Use
getHost()to refresh domain status from Cloudflare - Single Zone: All custom hostnames created in one Cloudflare zone
- No Webhooks: Must poll Cloudflare API to check SSL activation status
- Free Tier Promotion: When free domain deleted, another automatically promoted
- Ownership Validation: All operations validate domain belongs to requesting account
🔗 Related Documentation
- Hosts Service: Detailed hosts.md documentation
- Cloudflare Custom Hostnames API: https://developers.cloudflare.com/ssl/ssl-for-saas/
- Lightning Domain Model:
external/models/lightning-domain.js - Funnels Integration: Integration with sales funnels
- OneBalance System: Billing integration for domain charges
- Funnel Domain Status Utility:
external/utilities/funnelDomainStatusUtility.js
🚀 Quick Start
Create Custom Domain
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: ${domain.hostname}`);
console.log(`CNAME: ${domain.cname_validation.name} → ${domain.cname_validation.value}`);
console.log(`TXT: ${domain.ownership_verification.name} → ${domain.ownership_verification.value}`);
console.log(`SSL Status: ${domain.ssl.status}`);
Check Domain 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 fully connected and SSL active!');
} else {
console.log(`Status: ${domain.status}, SSL: ${domain.ssl.status}`);
}
List Connected Domains
// Get only fully connected domains
const activeDomains = await hostsService.getHosts({
account_id: accountId,
connected: 'connected', // Only active SSL + active status
});
activeDomains.forEach(domain => {
console.log(`${domain.hostname} - ${domain.funnels.length} funnels`);
});
💡 Integration Use Cases
Lightning Domains for Funnels
// 1. Create domain
const domain = await hostsService.createHost({
domain: 'promo.mybusiness.com',
account_id: accountId,
parent_account: accountId,
auth: { account, uid },
});
// 2. Associate with funnel
await Funnel.findByIdAndUpdate(funnelId, {
domain_id: domain._id,
custom_domain: domain.hostname,
});
// 3. Wait for DNS configuration and SSL activation
// (Check domain.ssl.status === 'active')
// 4. Funnel accessible at https://promo.mybusiness.com
Multi-Tenant White-Label Setup
// Main account creates domains for sub-account
const domain = await hostsService.createHost({
domain: 'client-site.com',
account_id: subAccountId, // Sub-account owns it
parent_account: mainAccountId, // Main account for billing
auth: { account: mainAccount, uid: mainUserId },
});
// Sub-account's domain counts toward main account's free tier
Domain Migration
// 1. Create new domain
const newDomain = await hostsService.createHost({
domain: 'new-domain.com',
account_id: accountId,
parent_account: accountId,
auth: { account, uid },
});
// 2. Update funnel to use new domain
await Funnel.findByIdAndUpdate(funnelId, {
domain_id: newDomain._id,
custom_domain: newDomain.hostname,
});
// 3. Delete old domain
await hostsService.deleteHost({
domain_id: oldDomainId,
account_id: accountId,
});
// If old domain was free, new domain automatically becomes free