Hudu Websites Management
Overview
Websites in Hudu represent website records associated with client companies. Beyond basic URL tracking, Hudu provides monitoring for SSL/TLS certificates and email security standards (DMARC, DKIM, SPF). MSPs use website records to track client web properties, monitor certificate expiration, and verify email authentication configuration.
Key Concepts
Website Monitoring
Hudu can automatically monitor websites for:
| Monitoring Area | Description | |----------------|-------------| | SSL/TLS | Certificate validity, expiration date, issuer | | DMARC | Domain-based Message Authentication, Reporting & Conformance | | DKIM | DomainKeys Identified Mail | | SPF | Sender Policy Framework | | HTTP Status | Whether the site is reachable |
Email Security Trifecta
The three email security standards that Hudu tracks:
| Standard | Purpose | DNS Record Type | |----------|---------|----------------| | SPF | Specifies authorized mail servers | TXT record on domain | | DKIM | Cryptographic email authentication | TXT record on selector._domainkey | | DMARC | Policy for handling failed SPF/DKIM | TXT record on _dmarc subdomain |
Website Scope
Each website record is linked to a company. A company can have multiple website records (e.g., primary domain, marketing site, client portal).
Field Reference
Core Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | id | integer | System | Auto-generated unique identifier | | company_id | integer | Yes | Parent company | | name | string | Yes | Website display name / URL | | slug | string | System | URL-friendly identifier | | notes | string | No | Additional notes | | paused | boolean | No | Whether monitoring is paused | | disable_dns | boolean | No | Disable DNS checks | | disable_ssl | boolean | No | Disable SSL checks | | disable_whois | boolean | No | Disable WHOIS checks |
Monitoring Fields
| Field | Type | Description | |-------|------|-------------| | url | string | The monitored URL | | monitoring_status | string | Current monitoring status |
SSL/TLS Fields
| Field | Type | Description | |-------|------|-------------| | ssl_status | string | SSL certificate status | | ssl_expiration | datetime | SSL certificate expiration date | | ssl_issuer | string | SSL certificate issuer |
Email Security Fields
| Field | Type | Description | |-------|------|-------------| | dmarc_status | string | DMARC record status | | dmarc_policy | string | DMARC policy (none, quarantine, reject) | | dkim_status | string | DKIM record status | | spf_status | string | SPF record status | | spf_record | string | SPF record value |
DNS Fields
| Field | Type | Description | |-------|------|-------------| | dns_a_records | array | A record values | | dns_mx_records | array | MX record values | | dns_ns_records | array | NS record values |
Metadata Fields
| Field | Type | Description | |-------|------|-------------| | created_at | datetime | Creation timestamp | | updated_at | datetime | Last update timestamp | | object_type | string | Always "Website" | | company_name | string | Parent company name (read-only) |
API Patterns
List Websites
GET /api/v1/websites
x-api-key: YOUR_API_KEY
Content-Type: application/json
By Company:
GET /api/v1/websites?company_id=123
By Name:
GET /api/v1/websites?name=acme.com
With Pagination:
GET /api/v1/websites?page=1
Get Single Website
GET /api/v1/websites/456
x-api-key: YOUR_API_KEY
Response:
{
"website": {
"id": 456,
"company_id": 123,
"company_name": "Acme Corporation",
"name": "acme.com",
"url": "https://www.acme.com",
"notes": "Primary company website",
"paused": false,
"disable_dns": false,
"disable_ssl": false,
"disable_whois": false,
"monitoring_status": "up",
"ssl_status": "valid",
"ssl_expiration": "2026-08-15T00:00:00.000Z",
"ssl_issuer": "Let's Encrypt Authority X3",
"dmarc_status": "pass",
"dmarc_policy": "reject",
"dkim_status": "pass",
"spf_status": "pass",
"spf_record": "v=spf1 include:_spf.google.com include:spf.protection.outlook.com ~all",
"dns_a_records": ["203.0.113.10"],
"dns_mx_records": ["aspmx.l.google.com"],
"dns_ns_records": ["ns1.example.com", "ns2.example.com"],
"created_at": "2024-03-15T10:30:00.000Z",
"updated_at": "2026-02-20T08:00:00.000Z"
}
}
Create Website
POST /api/v1/websites
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"website": {
"name": "acme.com",
"company_id": 123,
"notes": "Primary company website. Hosted on AWS.",
"paused": false,
"disable_dns": false,
"disable_ssl": false,
"disable_whois": false
}
}
Update Website
PUT /api/v1/websites/456
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"website": {
"notes": "Primary company website. Migrated to Azure on 2026-02-15.",
"paused": false
}
}
Delete Website
DELETE /api/v1/websites/456
x-api-key: YOUR_API_KEY
Common Workflows
SSL Expiration Monitoring
async function getExpiringSslCerts(daysAhead = 30) {
const today = new Date();
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + daysAhead);
const websites = await fetchAllWebsites();
return websites
.filter(w => {
if (!w.ssl_expiration || w.paused) return false;
const expiry = new Date(w.ssl_expiration);
return expiry >= today && expiry <= futureDate;
})
.map(w => ({
name: w.name,
company: w.company_name,
sslExpiration: w.ssl_expiration,
daysRemaining: Math.ceil(
(new Date(w.ssl_expiration) - today) / (1000 * 60 * 60 * 24)
),
issuer: w.ssl_issuer
}))
.sort((a, b) => a.daysRemaining - b.daysRemaining);
}
Email Security Audit
async function emailSecurityAudit(companyId) {
const websites = await fetchWebsites({ company_id: companyId });
return websites.map(w => ({
domain: w.name,
spf: {
status: w.spf_status || 'Not checked',
record: w.spf_record || 'Not found'
},
dkim: {
status: w.dkim_status || 'Not checked'
},
dmarc: {
status: w.dmarc_status || 'Not checked',
policy: w.dmarc_policy || 'Not set'
},
overallScore: calculateEmailSecurityScore(w)
}));
}
function calculateEmailSecurityScore(website) {
let score = 0;
if (website.spf_status === 'pass') score += 33;
if (website.dkim_status === 'pass') score += 33;
if (website.dmarc_status === 'pass') score += 34;
if (website.dmarc_policy === 'reject') score += 10; // bonus for strict policy
return Math.min(score, 100);
}
Website Inventory Report
async function generateWebsiteReport(companyId) {
const websites = await fetchWebsites({ company_id: companyId });
return {
total: websites.length,
monitored: websites.filter(w => !w.paused).length,
paused: websites.filter(w => w.paused).length,
sslValid: websites.filter(w => w.ssl_status === 'valid').length,
sslExpiring: websites.filter(w => {
if (!w.ssl_expiration) return false;
const daysLeft = Math.ceil(
(new Date(w.ssl_expiration) - new Date()) / (1000 * 60 * 60 * 24)
);
return daysLeft <= 30;
}).length,
emailSecurityComplete: websites.filter(w =>
w.spf_status === 'pass' &&
w.dkim_status === 'pass' &&
w.dmarc_status === 'pass'
).length,
websites: websites.map(w => ({
name: w.name,
ssl: w.ssl_status,
sslExpiry: w.ssl_expiration,
spf: w.spf_status,
dkim: w.dkim_status,
dmarc: w.dmarc_status
}))
};
}
Onboard Client Domains
async fu