Backend Proxy
The widget cannot call the VanityCert API directly because that would expose your private API keys in the browser. Instead, you must create a backend proxy endpoint that forwards requests to VanityCert.
Architecture
Implementation Examples
Choose your backend framework:
- Node.js / Express
- PHP / Laravel
- Python / Flask
- Ruby / Rails
// server.js
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
const VANITYCERT_API_URL = 'https://app.vanitycert.com/api';
const VANITYCERT_API_KEY_ID = process.env.VANITYCERT_API_KEY_ID;
const VANITYCERT_API_KEY = process.env.VANITYCERT_API_KEY;
// Proxy endpoint
app.all('/api/vanitycert-proxy/*', async (req, res) => {
try {
const apiPath = req.params[0];
const url = `${VANITYCERT_API_URL}/${apiPath}`;
// Optional: Authenticate your user
// if (!req.user) {
// return res.status(401).json({ error: 'Unauthorized' });
// }
// Forward request to VanityCert API
const response = await axios({
method: req.method,
url: url,
headers: {
'X-API-KEY-ID': VANITYCERT_API_KEY_ID,
'X-API-KEY': VANITYCERT_API_KEY,
'Content-Type': 'application/json',
},
data: req.body,
validateStatus: null, // Don't throw on any status code
});
res.status(response.status).json(response.data);
} catch (error) {
console.error('Proxy error:', error.message);
res.status(500).json({
error: {
code: 'proxy_error',
message: 'Failed to connect to VanityCert API'
}
});
}
});
app.listen(3000);
Environment Variables:
VANITYCERT_API_KEY_ID=vc_pk_abc123def456
VANITYCERT_API_KEY=sk_1234567890abcdef...
<?php
// app/Http/Controllers/VanityCertProxyController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class VanityCertProxyController extends Controller
{
public function proxy(Request $request, string $path)
{
$apiKeyId = config('services.vanitycert.api_key_id');
$apiKey = config('services.vanitycert.api_key');
if (!$apiKeyId || !$apiKey) {
return response()->json([
'error' => ['message' => 'API keys not configured']
], 500);
}
// Optional: Authenticate your user
// if (!auth()->check()) {
// return response()->json(['error' => 'Unauthorized'], 401);
// }
$url = "https://app.vanitycert.com/api/{$path}";
$response = Http::withHeaders([
'X-API-KEY-ID' => $apiKeyId,
'X-API-KEY' => $apiKey,
'Content-Type' => 'application/json',
])->send($request->method(), $url, [
'json' => $request->all()
]);
return response()->json(
$response->json(),
$response->status()
);
}
}
Route (routes/api.php):
Route::any('vanitycert-proxy/{path}', [VanityCertProxyController::class, 'proxy'])
->where('path', '.*')
->middleware('auth'); // Optional
Configuration (.env):
VANITYCERT_API_KEY_ID=vc_pk_abc123def456
VANITYCERT_API_KEY=sk_1234567890abcdef...
Config (config/services.php):
'vanitycert' => [
'api_key_id' => env('VANITYCERT_API_KEY_ID'),
'api_key' => env('VANITYCERT_API_KEY'),
],
# app.py
from flask import Flask, request, jsonify
import requests
import os
app = Flask(__name__)
VANITYCERT_API_URL = 'https://app.vanitycert.com/api'
VANITYCERT_API_KEY_ID = os.environ.get('VANITYCERT_API_KEY_ID')
VANITYCERT_API_KEY = os.environ.get('VANITYCERT_API_KEY')
@app.route('/api/vanitycert-proxy/<path:api_path>', methods=['GET', 'POST', 'DELETE'])
def vanitycert_proxy(api_path):
# Optional: Authenticate your user
# if not current_user.is_authenticated:
# return jsonify({'error': 'Unauthorized'}), 401
url = f"{VANITYCERT_API_URL}/{api_path}"
try:
response = requests.request(
method=request.method,
url=url,
headers={
'X-API-KEY-ID': VANITYCERT_API_KEY_ID,
'X-API-KEY': VANITYCERT_API_KEY,
'Content-Type': 'application/json',
},
json=request.get_json() if request.is_json else None,
timeout=30
)
return jsonify(response.json()), response.status_code
except Exception as e:
return jsonify({
'error': {
'code': 'proxy_error',
'message': 'Failed to connect to VanityCert API'
}
}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Environment Variables:
VANITYCERT_API_KEY_ID=vc_pk_abc123def456
VANITYCERT_API_KEY=sk_1234567890abcdef...
# app/controllers/vanitycert_proxy_controller.rb
class VanitycertProxyController < ApplicationController
before_action :authenticate_user! # Optional
def proxy
api_key_id = ENV['VANITYCERT_API_KEY_ID']
api_key = ENV['VANITYCERT_API_KEY']
url = "https://app.vanitycert.com/api/#{params[:path]}"
response = HTTParty.send(
request.method.downcase,
url,
headers: {
'X-API-KEY-ID' => api_key_id,
'X-API-KEY' => api_key,
'Content-Type' => 'application/json'
},
body: request.body.read
)
render json: response.parsed_response, status: response.code
rescue => e
render json: {
error: {
code: 'proxy_error',
message: 'Failed to connect to VanityCert API'
}
}, status: 500
end
end
Route (config/routes.rb):
match '/api/vanitycert-proxy/*path',
to: 'vanitycert_proxy#proxy',
via: :all
Security Best Practices
1. Authentication
Always verify your own users before proxying requests:
app.all('/api/vanitycert-proxy/*', authenticate, async (req, res) => {
// Only authenticated users can use the proxy
});
2. Rate Limiting
Prevent abuse by rate limiting per user:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each user to 100 requests per window
});
app.use('/api/vanitycert-proxy', limiter);
3. Input Validation
Validate request data before forwarding:
app.post('/api/vanitycert-proxy/domains', async (req, res) => {
const { url, server_id } = req.body;
// Validate domain format
if (!url || !/^[a-z0-9.-]+$/i.test(url)) {
return res.status(400).json({
error: { message: 'Invalid domain format' }
});
}
// Validate server_id belongs to user's organization
if (!userOwnsServer(req.user.id, server_id)) {
return res.status(403).json({
error: { message: 'Access denied' }
});
}
// Forward to VanityCert...
});
4. Error Handling
Don't expose internal errors to the client:
try {
// Proxy request...
} catch (error) {
console.error('VanityCert API error:', error);
// Don't expose internal error details
res.status(500).json({
error: {
code: 'proxy_error',
message: 'An error occurred. Please try again.'
}
});
}
5. Logging
Log all API requests for debugging and auditing:
app.all('/api/vanitycert-proxy/*', async (req, res) => {
console.log(`[VanityCert] ${req.method} /${req.params[0]} - User: ${req.user.id}`);
// Process request...
});
CORS Configuration
If your widget is on a different domain than your API:
const cors = require('cors');
app.use('/api/vanitycert-proxy', cors({
origin: 'https://yourdomain.com',
credentials: true
}));
Testing Your Proxy
Test your proxy endpoint manually before using the widget:
curl -X POST http://localhost:3000/api/vanitycert-proxy/domains \
-H "Content-Type: application/json" \
-H "Cookie: session=..." \
-d '{
"url": "test.yourdomain.com",
"server_id": 123
}'
Expected response:
{
"id": 456,
"url": "test.yourdomain.com",
"dns_status": "pending",
"ssl_status": "pending",
"created_at": "2025-01-01T12:00:00Z"
}
Webhook Integration
Also implement webhook handling to receive real-time events:
app.post('/webhooks/vanitycert',
express.raw({ type: 'application/json' }),
async (req, res) => {
// Verify signature
const signature = req.headers['x-vanitycert-signature'];
const isValid = verifySignature(req.body, signature, WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process webhook
const event = JSON.parse(req.body);
if (event.event === 'certificate.issued') {
// Notify your user
await notifyUser(event.domain_url, 'SSL certificate is ready!');
}
res.status(200).send('OK');
}
);
See Webhooks for complete webhook integration guide.
Next Steps
- Configuration - Configure widget options
- Theming - Customize the widget appearance
- Troubleshooting - Common issues and solutions