Skip to main content

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,
};
},
},
}
);
});

See Also