Error Handling & Troubleshooting
This guide helps you diagnose and resolve common issues with VanityCert.
API Error Responses
All API errors follow this format:
{
"error": {
"code": "dns_validation_failed",
"message": "DNS validation failed: CNAME record not found",
"details": {
"domain": "app.yourdomain.com",
"expected_cname": "my.vanitycert.com"
}
}
}
Error Structure:
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code |
message | string | Human-readable error message |
details | object | Additional context (optional) |
HTTP Status Codes
| Status Code | Meaning | Common Causes |
|---|---|---|
| 200 OK | Request successful | - |
| 201 Created | Resource created | - |
| 400 Bad Request | Invalid request data | Missing fields, validation errors |
| 401 Unauthorized | Invalid/missing API key | Wrong API key, expired key |
| 403 Forbidden | Insufficient permissions | API key lacks required permissions |
| 404 Not Found | Resource doesn't exist | Wrong ID, resource deleted |
| 422 Unprocessable Entity | Validation failed | Invalid domain format, duplicate |
| 429 Too Many Requests | Rate limit exceeded | Too many API requests |
| 500 Internal Server Error | Server error | Contact support |
| 503 Service Unavailable | Service temporarily down | Check status page |
Common Errors
DNS Validation Errors
CNAME Record Not Found
Error:
{
"error": {
"code": "cname_not_found",
"message": "DNS validation failed: CNAME record not found",
"details": {
"domain": "app.yourdomain.com",
"expected_target": "my.vanitycert.com"
}
}
}
Causes:
- CNAME record not configured
- DNS not yet propagated
- Wrong subdomain configured
Resolution:
-
Verify CNAME exists:
dig app.yourdomain.com CNAMEExpected output:
app.yourdomain.com. 300 IN CNAME my.vanitycert.com. -
Check CNAME target: Must be exactly
my.vanitycert.com(case-insensitive) -
Wait for DNS propagation: Can take 5-30 minutes, up to 24 hours maximum
-
Check from multiple DNS servers:
dig @8.8.8.8 app.yourdomain.com CNAME # Google
dig @1.1.1.1 app.yourdomain.com CNAME # Cloudflare
dig @208.67.222.222 app.yourdomain.com CNAME # OpenDNS -
Remove conflicting records: Delete any A or AAAA records for the same subdomain
Incorrect CNAME Target
Error:
{
"error": {
"code": "invalid_cname_target",
"message": "CNAME points to wrong target",
"details": {
"domain": "app.yourdomain.com",
"actual_target": "www.vanitycert.com",
"expected_target": "my.vanitycert.com"
}
}
}
Resolution:
Update CNAME record to point to my.vanitycert.com:
# Correct
app.yourdomain.com. IN CNAME my.vanitycert.com.
# Incorrect
app.yourdomain.com. IN CNAME www.vanitycert.com.
app.yourdomain.com. IN CNAME vanitycert.com.
DNS Validation Timeout
Error:
{
"error": {
"code": "dns_validation_timeout",
"message": "DNS validation failed after 24 hours",
"details": {
"domain": "app.yourdomain.com",
"started_at": "2025-01-01T12:00:00Z",
"timed_out_at": "2025-01-02T12:00:00Z"
}
}
}
Causes:
- DNS never configured
- CNAME incorrect for entire 24 hour period
- DNS propagation issues
Resolution:
- Fix DNS configuration
- Delete the failed domain
- Re-create domain (validation will retry)
Certificate Issuance Errors
Certificate Request Failed
Error:
{
"error": {
"code": "certificate_request_failed",
"message": "Failed to request certificate from ACME provider",
"details": {
"domain": "app.yourdomain.com",
"acme_error": "Rate limit exceeded"
}
}
}
Causes:
- ACME provider rate limits
- Server unreachable
- Invalid domain format
- Network connectivity issues
Resolution:
- Wait and retry (if rate limited)
- Check server health:
curl -I https://app.yourdomain.com - Contact support with domain ID and error details
Certificate Issuance Timeout
Error:
{
"error": {
"code": "certificate_timeout",
"message": "Certificate issuance timed out after 24 hours",
"details": {
"domain": "app.yourdomain.com",
"certificate_request_id": "abc123"
}
}
}
Causes:
- ACME provider processing delay (rare)
- Backend server issues
- Network connectivity problems
Resolution:
- Contact support with domain ID
- Provide certificate request ID from error
- May require manual intervention
API Errors
Invalid API Key
Error:
{
"error": {
"code": "invalid_api_key",
"message": "Invalid or expired API key"
}
}
HTTP Status: 401 Unauthorized
Causes:
- API key not included in request
- Wrong API key
- API key revoked
- API key expired
Resolution:
-
Verify API key in request:
curl -X GET https://app.vanitycert.com/api/domains \
-H "X-API-KEY-ID: vc_pk_YOUR_KEY_ID_HERE" \
-H "X-API-KEY: YOUR_SECRET_KEY_HERE" -
Check API key format:
- Production:
vc_live_... - Test:
vc_test_...(if available)
- Production:
-
Generate new API key:
- Dashboard → Developer → API Keys → Create New
Insufficient Permissions
Error:
{
"error": {
"code": "insufficient_permissions",
"message": "API key lacks required permissions",
"details": {
"required_permission": "domains.write",
"api_key_permissions": ["domains.read"]
}
}
}
HTTP Status: 403 Forbidden
Resolution:
-
Check API key permissions: Dashboard → Developer → API Keys
-
Update permissions or create new key with required permissions:
domains.read- List/view domainsdomains.write- Create/update/delete domainswebhooks.read- List webhookswebhooks.write- Manage webhooks
Rate Limit Exceeded
Error:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"details": {
"limit": 1000,
"remaining": 0,
"reset_at": "2025-01-01T13:00:00Z"
}
}
}
HTTP Status: 429 Too Many Requests
Headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704118800
Resolution:
-
Wait until reset time:
const resetTime = parseInt(response.headers['x-ratelimit-reset']);
const waitSeconds = resetTime - Math.floor(Date.now() / 1000);
await sleep(waitSeconds * 1000); -
Implement exponential backoff:
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.status === 429 && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await sleep(delay);
continue;
}
throw error;
}
}
} -
Optimize API usage:
- Cache responses
- Use bulk endpoints where possible
- Batch operations
Resource Not Found
Error:
{
"error": {
"code": "resource_not_found",
"message": "Domain not found",
"details": {
"domain_id": 999
}
}
}
HTTP Status: 404 Not Found
Causes:
- Wrong domain ID
- Domain deleted
- Domain belongs to different organization
Resolution:
- Verify domain ID:
curl -X GET https://app.vanitycert.com/api/domains \
-H "X-API-KEY-ID: vc_pk_abc123def456" \
-H "X-API-KEY: sk_1234567890abcdef1234567890abcdef"
2. **Check domain exists in dashboard**
3. **Ensure using correct organization's API key**
---
#### Validation Error
**Error:**
```json
{
"error": {
"code": "validation_error",
"message": "Invalid request data",
"details": {
"errors": {
"url": ["The url field is required"],
"server_id": ["The server_id must be an integer"]
}
}
}
}
HTTP Status: 422 Unprocessable Entity
Resolution:
-
Check required fields:
{
"url": "app.yourdomain.com",
"server_id": 123
} -
Validate data types:
url: stringserver_id: integersend_notifications: boolean
-
Check format requirements:
- URLs: No http:// or https://
- Subdomains only (not apex domains)
Webhook Errors
Invalid Webhook Signature
Error in your logs:
Webhook signature verification failed
Expected: sha256=abc123...
Received: sha256=def456...
Causes:
- Using wrong webhook secret
- Not reading raw request body
- Modifying body before verification
Resolution:
-
Verify secret matches webhook configuration:
const secret = process.env.WEBHOOK_SECRET; // Must match -
Read raw body:
// ✅ Correct
app.post('/webhooks',
express.raw({type: 'application/json'}),
handler
);
// ❌ Wrong
app.post('/webhooks',
express.json(), // Parses body first!
handler
); -
Don't modify body:
// Verify BEFORE parsing
const signature = req.headers['x-vanitycert-signature'];
const isValid = verifySignature(req.body, signature, secret);
// THEN parse
const event = JSON.parse(req.body);
Webhook Delivery Failures
Symptoms:
- Webhooks marked as "failed" in dashboard
- Not receiving webhook events
Causes:
- Endpoint not responding
- Endpoint returning non-2xx status
- Request timeout (>30 seconds)
- Network/firewall issues
Resolution:
-
Test endpoint manually:
curl -X POST https://api.yourdomain.com/webhooks/vanitycert \
-H "Content-Type: application/json" \
-d '{"event":"test","domain_id":123}' -
Check endpoint responds quickly (< 5s):
app.post('/webhooks/vanitycert', async (req, res) => {
// Respond immediately
res.status(200).send('OK');
// Process async
processWebhook(req.body).catch(console.error);
}); -
Return correct status codes:
- 200-299: Success
- 400-499: Don't retry
- 500-599: Retry
-
Check firewall rules allow VanityCert's IPs
-
Use HTTPS endpoints (not HTTP)
Debugging Tips
Enable Detailed Logging
const axios = require('axios');
// Log all API requests/responses
axios.interceptors.request.use(request => {
console.log('API Request:', {
method: request.method,
url: request.url,
headers: request.headers,
data: request.data
});
return request;
});
axios.interceptors.response.use(
response => {
console.log('API Response:', {
status: response.status,
data: response.data
});
return response;
},
error => {
console.error('API Error:', {
status: error.response?.status,
data: error.response?.data,
message: error.message
});
throw error;
}
);
Check Domain Status
# Get full domain details
curl -X GET https://app.vanitycert.com/api/domains/456 \
-H "X-API-KEY-ID: vc_pk_abc123def456" \
-H "X-API-KEY: sk_1234567890abcdef1234567890abcdef" \
| jq '.'
Review Certificate Audit Log
Dashboard → Reports → Certificate Audit
Shows complete history of all certificate events with error details.
Test DNS Configuration
# Check CNAME record
dig app.yourdomain.com CNAME +short
# Check from specific DNS server
dig @8.8.8.8 app.yourdomain.com CNAME +short
# Check with trace (shows full resolution path)
dig app.yourdomain.com CNAME +trace
Verify SSL Certificate
# Check if SSL is working
curl -vI https://app.yourdomain.com 2>&1 | grep -i ssl
# Get certificate details
openssl s_client -connect app.yourdomain.com:443 -servername app.yourdomain.com < /dev/null 2>/dev/null | openssl x509 -text -noout
Getting Help
Before Contacting Support
Gather this information:
- Domain ID or Domain URL
- Error message (full JSON response)
- Steps to reproduce the issue
- DNS configuration (
digoutput) - Timestamp when issue occurred
- API request/response logs
Support Channels
Email: support@vanitycert.com
Dashboard: https://app.vanitycert.com/support
Status Page: https://status.vanitycert.com
Check status page first for known issues.
Emergency Support
For urgent issues affecting production:
- Email: support@vanitycert.com with subject "URGENT"
- Include impact description
- Provide all debugging information above
Response Times:
- Normal: Within 24 hours
- Urgent: Within 4 hours
- Critical (service down): Within 1 hour
Best Practices
Error Handling in Code
async function createDomain(url, serverId) {
try {
const response = await axios.post(
'https://app.vanitycert.com/api/domains',
{ url, server_id: serverId },
{
headers: {
'X-API-KEY-ID': process.env.VANITYCERT_API_KEY_ID,
'X-API-KEY': process.env.VANITYCERT_API_KEY,
'Content-Type': 'application/json'
}
}
);
return response.data;
} catch (error) {
if (error.response) {
// API returned error response
const { code, message, details } = error.response.data.error;
switch (code) {
case 'cname_not_found':
console.error('DNS not configured:', details.domain);
// Handle DNS issue
break;
case 'rate_limit_exceeded':
console.error('Rate limited. Retry after:', details.reset_at);
// Implement backoff
break;
case 'invalid_api_key':
console.error('Invalid API key');
// Check API key configuration
break;
default:
console.error('API Error:', code, message);
}
throw new Error(`Failed to create domain: ${message}`);
} else if (error.request) {
// Request made but no response
console.error('No response from API');
throw new Error('API unreachable');
} else {
// Request setup error
console.error('Request error:', error.message);
throw error;
}
}
}
Retry Logic
async function withRetry(fn, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const isLastAttempt = attempt === maxRetries;
const isRetryable = error.response?.status >= 500 ||
error.response?.status === 429;
if (!isRetryable || isLastAttempt) {
throw error;
}
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
console.log(`Retry attempt ${attempt}/${maxRetries} after ${delay}ms`);
await sleep(delay);
}
}
}
// Usage
await withRetry(() => createDomain('app.example.com', 123));
Monitoring & Alerts
// Monitor domain health
async function checkDomainHealth() {
const domains = await api.getDomains();
const issues = domains.filter(d =>
d.dns_status === 'error' ||
d.ssl_status === 'error' ||
(d.renews_on && daysUntil(d.renews_on) < 7)
);
if (issues.length > 0) {
await sendAlert({
title: 'Domain Health Issues',
message: `${issues.length} domain(s) need attention`,
domains: issues.map(d => d.url)
});
}
}
// Run daily
setInterval(checkDomainHealth, 24 * 60 * 60 * 1000);