Integration Examples
Complete code examples for integrating VanityCert into your application.
Table of Contents
Node.js / Express
Complete Integration
// vanitycert-client.js
const axios = require('axios');
const crypto = require('crypto');
class VanityCertClient {
constructor(apiKeyId, apiKey, baseURL = 'https://app.vanitycert.com/api') {
this.apiKeyId = apiKeyId;
this.apiKey = apiKey;
this.baseURL = baseURL;
this.client = axios.create({
baseURL: this.baseURL,
headers: {
'X-API-KEY-ID': this.apiKeyId,
'X-API-KEY': this.apiKey,
'Content-Type': 'application/json'
}
});
}
// Domains
async createDomain(url, serverId, options = {}) {
const response = await this.client.post('/domains', {
url,
server_id: serverId,
...options
});
return response.data;
}
async getDomain(id) {
const response = await this.client.get(`/domains/${id}`);
return response.data;
}
async listDomains(filters = {}) {
const response = await this.client.get('/domains', { params: filters });
return response.data;
}
async deleteDomain(id) {
const response = await this.client.delete(`/domains/${id}`);
return response.data;
}
async bulkCreateDomains(domains) {
const response = await this.client.post('/domains/bulk', { domains });
return response.data;
}
// Webhooks
async createWebhook(url, events, secret) {
const response = await this.client.post('/webhooks', {
url,
events,
secret,
is_active: true
});
return response.data;
}
async listWebhooks() {
const response = await this.client.get('/webhooks');
return response.data;
}
static verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
}
module.exports = VanityCertClient;
Express Webhook Handler
// webhook-handler.js
const express = require('express');
const VanityCertClient = require('./vanitycert-client');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// IMPORTANT: Use raw body for signature verification
app.post('/webhooks/vanitycert',
express.raw({type: 'application/json'}),
async (req, res) => {
const signature = req.headers['x-vanitycert-signature'];
const deliveryId = req.headers['x-vanitycert-delivery'];
// Verify signature
if (!VanityCertClient.verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}
// Parse event
const event = JSON.parse(req.body.toString());
console.log('Webhook received:', {
deliveryId,
event: event.event,
domainId: event.domain_id
});
// Respond immediately
res.status(200).send('OK');
// Process async
processWebhook(event).catch(error => {
console.error('Webhook processing failed:', error);
});
}
);
async function processWebhook(event) {
switch (event.event) {
case 'certificate.issued':
await handleCertificateIssued(event);
break;
case 'certificate.expired':
await handleCertificateExpired(event);
break;
case 'domain.created':
await handleDomainCreated(event);
break;
default:
console.log('Unhandled event type:', event.event);
}
}
async function handleCertificateIssued(event) {
console.log(`Certificate issued for ${event.domain_url}`);
// Update database
await db.certificates.upsert({
domainId: event.domain_id,
url: event.domain_url,
status: 'active',
issuedAt: new Date(event.issued_at),
renewsOn: new Date(event.renews_on)
});
// Send notification
await sendSlack({
channel: '#ops',
message: `:white_check_mark: SSL certificate issued for ${event.domain_url}`
});
}
async function handleCertificateExpired(event) {
console.error(`Certificate expired for ${event.domain_url}`);
// Create incident
await pagerDuty.createIncident({
title: `SSL Certificate Expired: ${event.domain_url}`,
severity: 'high'
});
// Update database
await db.certificates.update(event.domain_id, {
status: 'expired',
expiredAt: new Date(event.expired_at)
});
}
async function handleDomainCreated(event) {
console.log(`Domain created: ${event.domain_url}`);
await db.domains.create({
id: event.domain_id,
url: event.domain_url,
status: 'provisioning',
createdAt: new Date(event.created_at)
});
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
Usage Example
// app.js
const VanityCertClient = require('./vanitycert-client');
const client = new VanityCertClient(
process.env.VANITYCERT_API_KEY_ID,
process.env.VANITYCERT_API_KEY
);
async function main() {
try {
// Create a domain
const domain = await client.createDomain('app.example.com', 123, {
send_notifications: true,
notification_email: 'ops@example.com'
});
console.log('Domain created:', domain);
// Check status
const status = await client.getDomain(domain.id);
console.log('Domain status:', status.dns_status, status.ssl_status);
// List all domains
const domains = await client.listDomains({
ssl_status: 'active'
});
console.log(`Active domains: ${domains.length}`);
// Create webhook
const webhook = await client.createWebhook(
'https://api.example.com/webhooks/vanitycert',
['certificate.issued', 'certificate.expired'],
process.env.WEBHOOK_SECRET
);
console.log('Webhook created:', webhook.id);
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
main();
PHP / Laravel
Client Class
<?php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class VanityCertClient
{
private Client $client;
private string $apiKeyId;
private string $apiKey;
public function __construct(string $apiKeyId, string $apiKey, string $baseUrl = 'https://app.vanitycert.com/api')
{
$this->apiKeyId = $apiKeyId;
$this->apiKey = $apiKey;
$this->client = new Client([
'base_uri' => $baseUrl,
'headers' => [
'X-API-KEY-ID' => $apiKeyId,
'X-API-KEY' => $apiKey,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
]
]);
}
public function createDomain(string $url, int $serverId, array $options = []): array
{
$response = $this->client->post('/domains', [
'json' => array_merge([
'url' => $url,
'server_id' => $serverId,
], $options)
]);
return json_decode($response->getBody(), true);
}
public function getDomain(int $id): array
{
$response = $this->client->get("/domains/{$id}");
return json_decode($response->getBody(), true);
}
public function listDomains(array $filters = []): array
{
$response = $this->client->get('/domains', [
'query' => $filters
]);
return json_decode($response->getBody(), true);
}
public function deleteDomain(int $id): void
{
$this->client->delete("/domains/{$id}");
}
public function createWebhook(string $url, array $events, string $secret): array
{
$response = $this->client->post('/webhooks', [
'json' => [
'url' => $url,
'events' => $events,
'secret' => $secret,
'is_active' => true,
]
]);
return json_decode($response->getBody(), true);
}
public static function verifyWebhookSignature(string $payload, string $signature, string $secret): bool
{
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($expectedSignature, $signature);
}
}
Laravel Webhook Controller
<?php
namespace App\Http\Controllers;
use App\Services\VanityCertClient;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class VanityCertWebhookController extends Controller
{
public function handle(Request $request)
{
$payload = $request->getContent();
$signature = $request->header('X-VanityCert-Signature');
$secret = config('services.vanitycert.webhook_secret');
// Verify signature
if (!VanityCertClient::verifyWebhookSignature($payload, $signature, $secret)) {
Log::error('Invalid webhook signature');
return response('Invalid signature', 401);
}
$event = json_decode($payload, true);
Log::info('Webhook received', [
'event' => $event['event'],
'domain_id' => $event['domain_id'],
'delivery_id' => $request->header('X-VanityCert-Delivery')
]);
// Dispatch job for async processing
\App\Jobs\ProcessVanityCertWebhook::dispatch($event);
return response('OK', 200);
}
}
Laravel Job
<?php
namespace App\Jobs;
use App\Models\Certificate;
use App\Notifications\CertificateIssued;
use App\Notifications\CertificateExpired;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessVanityCertWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected array $event;
public function __construct(array $event)
{
$this->event = $event;
}
public function handle()
{
switch ($this->event['event']) {
case 'certificate.issued':
$this->handleCertificateIssued();
break;
case 'certificate.expired':
$this->handleCertificateExpired();
break;
case 'domain.created':
$this->handleDomainCreated();
break;
}
}
protected function handleCertificateIssued()
{
Certificate::updateOrCreate(
['domain_id' => $this->event['domain_id']],
[
'domain_url' => $this->event['domain_url'],
'status' => 'active',
'issued_at' => $this->event['issued_at'],
'renews_on' => $this->event['renews_on'],
]
);
// Send notification
\Notification::route('mail', 'ops@example.com')
->notify(new CertificateIssued($this->event));
}
protected function handleCertificateExpired()
{
Certificate::where('domain_id', $this->event['domain_id'])
->update([
'status' => 'expired',
'expired_at' => $this->event['expired_at']
]);
// Send urgent notification
\Notification::route('mail', 'ops@example.com')
->notify(new CertificateExpired($this->event));
}
protected function handleDomainCreated()
{
\App\Models\Domain::create([
'id' => $this->event['domain_id'],
'url' => $this->event['domain_url'],
'status' => 'provisioning',
'created_at' => $this->event['created_at'],
]);
}
}
Python / Flask
Client Class
# vanitycert_client.py
import requests
import hmac
import hashlib
class VanityCertClient:
def __init__(self, api_key_id, api_key, base_url='https://app.vanitycert.com/api'):
self.api_key_id = api_key_id
self.api_key = api_key
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
'X-API-KEY-ID': api_key_id,
'X-API-KEY': api_key,
'Content-Type': 'application/json'
})
def create_domain(self, url, server_id, **options):
data = {
'url': url,
'server_id': server_id,
**options
}
response = self.session.post(f'{self.base_url}/domains', json=data)
response.raise_for_status()
return response.json()
def get_domain(self, domain_id):
response = self.session.get(f'{self.base_url}/domains/{domain_id}')
response.raise_for_status()
return response.json()
def list_domains(self, **filters):
response = self.session.get(f'{self.base_url}/domains', params=filters)
response.raise_for_status()
return response.json()
def delete_domain(self, domain_id):
response = self.session.delete(f'{self.base_url}/domains/{domain_id}')
response.raise_for_status()
return response.json()
def create_webhook(self, url, events, secret):
data = {
'url': url,
'events': events,
'secret': secret,
'is_active': True
}
response = self.session.post(f'{self.base_url}/webhooks', json=data)
response.raise_for_status()
return response.json()
@staticmethod
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected_signature = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
Flask Webhook Handler
# app.py
from flask import Flask, request, jsonify
import os
import logging
from vanitycert_client import VanityCertClient
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
@app.route('/webhooks/vanitycert', methods=['POST'])
def webhook():
signature = request.headers.get('X-VanityCert-Signature')
payload = request.get_data()
# Verify signature
if not VanityCertClient.verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
logging.error('Invalid webhook signature')
return 'Invalid signature', 401
event = request.get_json()
logging.info(f"Webhook received: {event['event']} for domain {event.get('domain_id')}")
# Process event async (use Celery in production)
process_webhook(event)
return 'OK', 200
def process_webhook(event):
event_type = event['event']
if event_type == 'certificate.issued':
handle_certificate_issued(event)
elif event_type == 'certificate.expired':
handle_certificate_expired(event)
elif event_type == 'domain.created':
handle_domain_created(event)
def handle_certificate_issued(event):
logging.info(f"Certificate issued for {event['domain_url']}")
# Update database
# db.certificates.upsert({
# 'domain_id': event['domain_id'],
# 'status': 'active',
# 'issued_at': event['issued_at'],
# 'renews_on': event['renews_on']
# })
# Send notification
send_slack_notification(
f":white_check_mark: SSL certificate issued for {event['domain_url']}"
)
def handle_certificate_expired(event):
logging.error(f"Certificate expired for {event['domain_url']}")
# Create incident
# pagerduty.create_incident(
# title=f"SSL Certificate Expired: {event['domain_url']}",
# severity='high'
# )
def handle_domain_created(event):
logging.info(f"Domain created: {event['domain_url']}")
# Update database
# db.domains.create({
# 'id': event['domain_id'],
# 'url': event['domain_url'],
# 'status': 'provisioning'
# })
def send_slack_notification(message):
# Implement Slack notification
pass
if __name__ == '__main__':
app.run(port=3000)
Usage Example
# example.py
import os
from vanitycert_client import VanityCertClient
def main():
client = VanityCertClient(os.environ.get('VANITYCERT_API_KEY'))
try:
# Create a domain
domain = client.create_domain(
'app.example.com',
123,
send_notifications=True,
notification_email='ops@example.com'
)
print(f"Domain created: {domain}")
# Check status
status = client.get_domain(domain['id'])
print(f"DNS Status: {status['dns_status']}")
print(f"SSL Status: {status['ssl_status']}")
# List active domains
domains = client.list_domains(ssl_status='active')
print(f"Active domains: {len(domains)}")
# Create webhook
webhook = client.create_webhook(
'https://api.example.com/webhooks/vanitycert',
['certificate.issued', 'certificate.expired'],
os.environ.get('WEBHOOK_SECRET')
)
print(f"Webhook created: {webhook['id']}")
except requests.exceptions.HTTPError as e:
print(f"API Error: {e.response.json()}")
if __name__ == '__main__':
main()
Ruby / Rails
Client Class
# lib/vanitycert_client.rb
require 'faraday'
require 'json'
require 'openssl'
class VanityCertClient
attr_reader :api_key, :base_url
def initialize(api_key, base_url = 'https://app.vanitycert.com/api')
@api_key = api_key
@base_url = base_url
end
def create_domain(url, server_id, options = {})
post('/domains', {
url: url,
server_id: server_id
}.merge(options))
end
def get_domain(id)
get("/domains/#{id}")
end
def list_domains(filters = {})
get('/domains', filters)
end
def delete_domain(id)
delete("/domains/#{id}")
end
def create_webhook(url, events, secret)
post('/webhooks', {
url: url,
events: events,
secret: secret,
is_active: true
})
end
def self.verify_webhook_signature(payload, signature, secret)
expected_signature = 'sha256=' + OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
Rack::Utils.secure_compare(expected_signature, signature)
end
private
def connection
@connection ||= Faraday.new(url: base_url) do |conn|
conn.request :json
conn.response :json
conn.headers['Authorization'] = "Bearer #{api_key}"
conn.adapter Faraday.default_adapter
end
end
def get(path, params = {})
response = connection.get(path, params)
response.body
end
def post(path, body)
response = connection.post(path, body)
response.body
end
def delete(path)
response = connection.delete(path)
response.body
end
end
Rails Controller
# app/controllers/vanitycert_webhooks_controller.rb
class VanitycertWebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
payload = request.raw_post
signature = request.headers['X-VanityCert-Signature']
secret = Rails.application.credentials.dig(:vanitycert, :webhook_secret)
unless VanityCertClient.verify_webhook_signature(payload, signature, secret)
Rails.logger.error 'Invalid webhook signature'
return head :unauthorized
end
event = JSON.parse(payload)
Rails.logger.info "Webhook received: #{event['event']} for domain #{event['domain_id']}"
# Process async
ProcessVanityCertWebhookJob.perform_later(event)
head :ok
end
end
Sidekiq Job
# app/jobs/process_vanitycert_webhook_job.rb
class ProcessVanityCertWebhookJob < ApplicationJob
queue_as :default
def perform(event)
case event['event']
when 'certificate.issued'
handle_certificate_issued(event)
when 'certificate.expired'
handle_certificate_expired(event)
when 'domain.created'
handle_domain_created(event)
end
end
private
def handle_certificate_issued(event)
Certificate.find_or_create_by(domain_id: event['domain_id']) do |cert|
cert.domain_url = event['domain_url']
cert.status = 'active'
cert.issued_at = event['issued_at']
cert.renews_on = event['renews_on']
end
SlackNotifier.notify("✅ SSL certificate issued for #{event['domain_url']}")
end
def handle_certificate_expired(event)
certificate = Certificate.find_by(domain_id: event['domain_id'])
certificate.update(status: 'expired', expired_at: event['expired_at'])
PagerDutyService.create_incident(
title: "SSL Certificate Expired: #{event['domain_url']}",
severity: 'high'
)
end
def handle_domain_created(event)
Domain.create(
id: event['domain_id'],
url: event['domain_url'],
status: 'provisioning',
created_at: event['created_at']
)
end
end
Go
// vanitycert.go
package vanitycert
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
)
const BaseURL = "https://app.vanitycert.com/api"
type Client struct {
APIKey string
BaseURL string
HTTPClient *http.Client
}
type Domain struct {
ID int `json:"id"`
URL string `json:"url"`
ServerID int `json:"server_id"`
DNSStatus string `json:"dns_status"`
SSLStatus string `json:"ssl_status"`
}
type CreateDomainRequest struct {
URL string `json:"url"`
ServerID int `json:"server_id"`
}
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
BaseURL: BaseURL,
HTTPClient: &http.Client{},
}
}
func (c *Client) CreateDomain(url string, serverID int) (*Domain, error) {
req := CreateDomainRequest{
URL: url,
ServerID: serverID,
}
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
httpReq, err := http.NewRequest("POST", c.BaseURL+"/domains", bytes.NewBuffer(body))
if err != nil {
return nil, err
}
httpReq.Header.Set("Authorization", "Bearer "+c.APIKey)
httpReq.Header.Set("Content-Type", "application/json")
resp, err := c.HTTPClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
bodyBytes, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error: %s", string(bodyBytes))
}
var domain Domain
if err := json.NewDecoder(resp.Body).Decode(&domain); err != nil {
return nil, err
}
return &domain, nil
}
func VerifyWebhookSignature(payload []byte, signature string, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expectedSignature := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
// Webhook handler example
func WebhookHandler(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
signature := r.Header.Get("X-VanityCert-Signature")
secret := os.Getenv("WEBHOOK_SECRET")
if !VerifyWebhookSignature(payload, signature, secret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
var event map[string]interface{}
if err := json.Unmarshal(payload, &event); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Process event async
go processWebhook(event)
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
Infrastructure as Code
Terraform - AWS Route 53
# variables.tf
variable "domains" {
type = list(object({
name = string
server_id = number
}))
default = [
{ name = "app.example.com", server_id = 123 },
{ name = "api.example.com", server_id = 123 },
]
}
variable "vanitycert_api_key" {
type = string
sensitive = true
}
# dns.tf
resource "aws_route53_record" "vanitycert_cname" {
for_each = { for d in var.domains : d.name => d }
zone_id = aws_route53_zone.main.zone_id
name = each.key
type = "CNAME"
ttl = 300
records = ["my.vanitycert.com"]
}
# vanitycert.tf
provider "restapi" {
uri = "https://app.vanitycert.com/api"
write_returns_object = true
headers = {
Authorization = "Bearer ${var.vanitycert_api_key}"
Content-Type = "application/json"
}
}
resource "restapi_object" "domains" {
for_each = { for d in var.domains : d.name => d }
path = "/domains"
data = jsonencode({
url = each.key
server_id = each.value.server_id
})
depends_on = [aws_route53_record.vanitycert_cname]
}
Pulumi - TypeScript
// index.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as axios from "axios";
const config = new pulumi.Config();
const apiKey = config.requireSecret("vanitycertApiKey");
const domains = [
{ name: "app.example.com", serverId: 123 },
{ name: "api.example.com", serverId: 123 },
];
// Create DNS records
domains.forEach(({ name }) => {
new aws.route53.Record(`${name}-vanitycert`, {
zoneId: zoneId,
name: name,
type: "CNAME",
ttl: 300,
records: ["my.vanitycert.com"],
});
});
// Create domains in VanityCert
domains.forEach(({ name, serverId }) => {
const domain = new pulumi.dynamic.Resource(
`vanitycert-${name}`,
{
url: name,
serverId: serverId,
},
{
provider: {
async create(inputs: any): Promise<any> {
const response = await axios.default.post(
"https://app.vanitycert.com/api/domains",
{
url: inputs.url,
server_id: inputs.serverId,
},
{
headers: {
Authorization: `Bearer ${await apiKey.get()}`,
"Content-Type": "application/json",
},
}
);
return {
id: response.data.id.toString(),
outs: response.data,
};
},
},
}
);
});