Custom Domains for Branded Short Links
Build trust and brand recognition by using your own domain for short links. Transform generic r.sniplink.co/abc123 into branded go.yourcompany.com/product.
What You'll Learn
This tutorial covers complete custom domain setup from registration to production use, including DNS configuration, automated verification, SSL management, and multi-domain strategies.
Plan Requirement: Custom domains require a Pro plan or higher. Free tier accounts use the default r.sniplink.co domain.
Why Use Custom Domains?
Custom domains provide critical benefits for professional link management:
- Brand Trust - Users recognize and trust your domain
- Click-Through Rates - Branded links get 39% higher CTR than generic shorteners
- Link Permanence - You own the domain, so links never break if you switch services
- Professional Appearance - Branded links look professional in emails, social media, and print
- Customization - Create memorable slugs like
go.company.com/careers - Analytics Control - Separate analytics by domain for different departments or campaigns
Step 1: Register Your Domain
Basic Domain Registration
First, register your custom domain with SnipLink. This creates a domain record and generates a verification token:
const API_KEY = process.env.SNIPLINK_API_KEY;
const WORKSPACE_ID = 'your-workspace-id';
const API_BASE = 'https://api.sniplink.co/v1';
async function registerCustomDomain(domain: string) {
const response = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/domains`,
{
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ domain }),
}
);
const { data } = await response.json();
return data;
}
// Register your branded domain
const domainInfo = await registerCustomDomain('go.yourcompany.com');
console.log(`Domain ID: ${domainInfo.id}`);
console.log(`Verification Token: ${domainInfo.verificationToken}`);
console.log(`Status: ${domainInfo.verified ? 'Verified' : 'Pending verification'}`);
// Save these values - you'll need them for DNS setup
// Domain ID: dom_abc123
// Verification Token: sniplink-verify=xyz789abc
// Status: Pending verificationChoosing the Right Domain
Consider these options when selecting a domain:
- Subdomain of your main site -
go.company.com,link.brand.io(Recommended) - Dedicated short domain -
cmpny.co,brnd.link - Department-specific -
mkt.company.com,sales.company.com - Campaign-specific -
event2025.co,promo.brand.io
Subdomain Recommendation: Use a subdomain (go.company.com) instead of apex domain (company.com). Subdomains support CNAME records and are easier to configure. Apex domains require A records with specific IP addresses that may change.
Step 2: Configure DNS Records
Required DNS Records
You need to configure two DNS records:
- CNAME Record - Routes traffic to SnipLink's servers
- TXT Record - Verifies domain ownership
DNS Configuration Example
For domain go.yourcompany.com:
# CNAME Record (Routes traffic)
Type: CNAME
Name: go
Value: redirect.sniplink.co
TTL: 3600
# TXT Record (Verifies ownership)
Type: TXT
Name: _sniplink.go
Value: sniplink-verify=xyz789abc
TTL: 3600DNS Provider Configuration
Here's how to configure DNS on popular providers:
Cloudflare
# In Cloudflare Dashboard:
# 1. Go to DNS > Records
# 2. Add CNAME record:
# - Type: CNAME
# - Name: go
# - Target: redirect.sniplink.co
# - Proxy status: DNS only (gray cloud)
# - TTL: Auto
#
# 3. Add TXT record:
# - Type: TXT
# - Name: _sniplink.go
# - Content: sniplink-verify=xyz789abc
# - TTL: Auto
# IMPORTANT: Disable Cloudflare proxy (orange cloud) for the CNAMEAWS Route 53
// Automate Route 53 DNS setup with AWS SDK
import { Route53Client, ChangeResourceRecordSetsCommand } from '@aws-sdk/client-route-53';
async function configureRoute53DNS(
hostedZoneId: string,
subdomain: string,
verificationToken: string
) {
const client = new Route53Client({ region: 'us-east-1' });
const changes = [
// CNAME for traffic routing
{
Action: 'CREATE',
ResourceRecordSet: {
Name: `${subdomain}.yourcompany.com`,
Type: 'CNAME',
TTL: 300,
ResourceRecords: [{ Value: 'redirect.sniplink.co' }],
},
},
// TXT for verification
{
Action: 'CREATE',
ResourceRecordSet: {
Name: `_sniplink.${subdomain}.yourcompany.com`,
Type: 'TXT',
TTL: 300,
ResourceRecords: [{ Value: `"${verificationToken}"` }],
},
},
];
const command = new ChangeResourceRecordSetsCommand({
HostedZoneId: hostedZoneId,
ChangeBatch: { Changes: changes },
});
const result = await client.send(command);
console.log(`DNS records created. Change ID: ${result.ChangeInfo?.Id}`);
return result;
}
// Usage
await configureRoute53DNS('Z123456789ABC', 'go', 'sniplink-verify=xyz789abc');Vercel
# In Vercel Dashboard:
# 1. Go to your domain > DNS Records
# 2. Add records via UI or use Vercel CLI:
vercel dns add yourcompany.com go CNAME redirect.sniplink.co
vercel dns add yourcompany.com _sniplink.go TXT "sniplink-verify=xyz789abc"Verify DNS Propagation
Check if DNS records are live before attempting verification:
import { promises as dns } from 'dns';
async function checkDNSPropagation(domain: string, verificationToken: string) {
try {
// Check CNAME record
const cnameRecords = await dns.resolve(domain, 'CNAME');
const cnameValid = cnameRecords.includes('redirect.sniplink.co');
// Check TXT record
const txtRecords = await dns.resolve(`_sniplink.${domain}`, 'TXT');
const txtValid = txtRecords.flat().some((record) => record === verificationToken);
return {
cnameValid,
txtValid,
ready: cnameValid && txtValid,
};
} catch (error) {
return { cnameValid: false, txtValid: false, ready: false, error };
}
}
// Wait for DNS propagation
let attempts = 0;
while (attempts < 20) {
const status = await checkDNSPropagation('go.yourcompany.com', 'sniplink-verify=xyz789abc');
if (status.ready) {
console.log('✓ DNS records are live!');
break;
}
console.log(`Attempt ${attempts + 1}/20: Waiting for DNS propagation...`);
console.log(` CNAME: ${status.cnameValid ? '✓' : '✗'}`);
console.log(` TXT: ${status.txtValid ? '✓' : '✗'}`);
await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait 30s
attempts++;
}Step 3: Verify Domain Ownership
Trigger Verification
Once DNS records are configured, verify domain ownership:
async function verifyDomain(domainId: string) {
const response = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/domains/${domainId}/verify`,
{
method: 'POST',
headers: {
'X-API-Key': API_KEY,
},
}
);
const result = await response.json();
if (result.success) {
console.log('✓ Domain verified successfully!');
console.log(` Domain: ${result.data.domain}`);
console.log(` Verified at: ${result.data.verifiedAt}`);
console.log(` SSL Status: Provisioning... (takes 1-5 minutes)`);
} else {
console.error('✗ Verification failed:', result.error.message);
}
return result;
}
// Verify the domain
const verificationResult = await verifyDomain('dom_abc123');Automated Verification Flow
Combine registration, DNS check, and verification into one automated flow:
async function setupCustomDomain(domain: string) {
console.log(`Setting up custom domain: ${domain}`);
// Step 1: Register domain
console.log('1. Registering domain with SnipLink...');
const registration = await registerCustomDomain(domain);
console.log(` ✓ Domain ID: ${registration.id}`);
// Step 2: Display DNS instructions
console.log('\n2. Configure these DNS records:');
console.log(' CNAME Record:');
console.log(` Name: ${domain.split('.')[0]}`);
console.log(' Value: redirect.sniplink.co');
console.log(' TXT Record:');
console.log(` Name: _sniplink.${domain.split('.')[0]}`);
console.log(` Value: ${registration.verificationToken}`);
// Step 3: Wait for user to configure DNS
console.log('\n3. Waiting for DNS propagation...');
let dnsReady = false;
for (let i = 0; i < 20; i++) {
await new Promise((resolve) => setTimeout(resolve, 30000));
const status = await checkDNSPropagation(domain, registration.verificationToken);
if (status.ready) {
dnsReady = true;
console.log(' ✓ DNS records detected!');
break;
}
console.log(` Attempt ${i + 1}/20: Not ready yet...`);
}
if (!dnsReady) {
throw new Error('DNS propagation timeout. Check your DNS configuration.');
}
// Step 4: Verify domain
console.log('\n4. Verifying domain ownership...');
const verification = await verifyDomain(registration.id);
if (!verification.success) {
throw new Error(`Verification failed: ${verification.error.message}`);
}
// Step 5: Wait for SSL
console.log('\n5. Waiting for SSL certificate provisioning...');
await new Promise((resolve) => setTimeout(resolve, 180000)); // Wait 3 minutes
console.log('\n✓ Custom domain setup complete!');
console.log(`You can now create links using: ${domain}`);
return registration.id;
}
// One-command setup
const domainId = await setupCustomDomain('go.yourcompany.com');Step 4: Create Branded Short Links
Using Specific Domain
Create links with your verified custom domain:
async function createBrandedLink(
url: string,
domainId: string,
slug?: string
) {
const response = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/links`,
{
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url,
domainId,
...(slug && { slug }), // Custom slug (optional)
}),
}
);
const { data } = await response.json();
return data.link;
}
// Create a branded link
const brandedLink = await createBrandedLink(
'https://company.com/product/premium',
'dom_abc123',
'premium' // Custom slug
);
console.log(`Short URL: ${brandedLink.shortUrl}`);
// Output: https://go.yourcompany.com/premiumSet Default Domain
Configure a default domain for your workspace to avoid specifying domainId every time:
async function setDefaultDomain(domainId: string) {
const response = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}`,
{
method: 'PATCH',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
defaultDomainId: domainId,
}),
}
);
return (await response.json()).data;
}
// Set default domain
await setDefaultDomain('dom_abc123');
// Now all links use your custom domain by default
const link = await fetch(`${API_BASE}/workspaces/${WORKSPACE_ID}/links`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://company.com/product',
// No domainId needed - uses default
}),
}).then((r) => r.json());
// Automatically uses go.yourcompany.comMulti-Domain Strategies
Department-Specific Domains
Use different domains for different teams or purposes:
const DEPARTMENT_DOMAINS = {
marketing: 'mkt.company.com',
sales: 'sales.company.com',
support: 'help.company.com',
events: 'events.company.com',
};
async function createDepartmentLink(
department: keyof typeof DEPARTMENT_DOMAINS,
url: string,
slug?: string
) {
// Get or create domain for department
const domains = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/domains`,
{ headers: { 'X-API-Key': API_KEY } }
).then((r) => r.json());
let domainId = domains.data.domains.find(
(d: any) => d.domain === DEPARTMENT_DOMAINS[department]
)?.id;
if (!domainId) {
// Auto-setup domain if it doesn't exist
domainId = await setupCustomDomain(DEPARTMENT_DOMAINS[department]);
}
return await createBrandedLink(url, domainId, slug);
}
// Marketing creates branded campaign link
const marketingLink = await createDepartmentLink(
'marketing',
'https://company.com/summer-sale',
'summer-sale'
);
// Result: https://mkt.company.com/summer-sale
// Sales creates outreach link
const salesLink = await createDepartmentLink(
'sales',
'https://company.com/demo',
'demo'
);
// Result: https://sales.company.com/demoCampaign-Specific Domains
Use temporary domains for major campaigns or events:
async function createCampaignDomain(
campaignName: string,
expirationDate: Date
) {
const domain = `${campaignName.toLowerCase()}.company.com`;
// Setup domain
const domainId = await setupCustomDomain(domain);
// Tag for cleanup later
await fetch(`${API_BASE}/workspaces/${WORKSPACE_ID}/domains/${domainId}`, {
method: 'PATCH',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
metadata: {
campaign: campaignName,
expiresAt: expirationDate.toISOString(),
},
}),
});
return domainId;
}
// Create domain for Black Friday 2025
const bfDomainId = await createCampaignDomain(
'bf2025',
new Date('2025-12-01T00:00:00Z')
);
// All Black Friday links use bf2025.company.com
const bfLink = await createBrandedLink(
'https://company.com/black-friday-deals',
bfDomainId,
'deals'
);
// Result: https://bf2025.company.com/dealsDomain Health Monitoring
Check Domain Status
async function getDomainHealth(domainId: string) {
const domain = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/domains/${domainId}`,
{ headers: { 'X-API-Key': API_KEY } }
).then((r) => r.json());
// Check SSL status
const sslCheck = await fetch(`https://${domain.data.domain}/health`, {
method: 'HEAD',
}).catch(() => null);
return {
domain: domain.data.domain,
verified: domain.data.verified,
sslActive: sslCheck?.status === 200,
verifiedAt: domain.data.verifiedAt,
createdAt: domain.data.createdAt,
};
}
async function monitorAllDomains() {
const domains = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/domains`,
{ headers: { 'X-API-Key': API_KEY } }
).then((r) => r.json());
console.log('Domain Health Report:');
console.log('='.repeat(50));
for (const domain of domains.data.domains) {
const health = await getDomainHealth(domain.id);
const status = health.verified && health.sslActive ? '✓' : '✗';
console.log(`${status} ${health.domain}`);
console.log(` Verified: ${health.verified}`);
console.log(` SSL Active: ${health.sslActive}`);
console.log(` Age: ${Math.floor((Date.now() - new Date(health.createdAt).getTime()) / (1000 * 60 * 60 * 24))} days`);
console.log('');
}
}
// Run daily health check
monitorAllDomains();Automated Alerts
async function checkAndAlertDomainIssues() {
const domains = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/domains`,
{ headers: { 'X-API-Key': API_KEY } }
).then((r) => r.json());
const issues = [];
for (const domain of domains.data.domains) {
const health = await getDomainHealth(domain.id);
if (!health.verified) {
issues.push({
severity: 'critical',
domain: health.domain,
message: 'Domain not verified - links will not work',
});
}
if (health.verified && !health.sslActive) {
issues.push({
severity: 'high',
domain: health.domain,
message: 'SSL certificate issue - HTTPS may not work',
});
}
// Check DNS records
try {
const dnsCheck = await checkDNSPropagation(domain.domain, domain.verificationToken);
if (!dnsCheck.cnameValid) {
issues.push({
severity: 'critical',
domain: health.domain,
message: 'CNAME record missing or incorrect',
});
}
} catch (error) {
issues.push({
severity: 'critical',
domain: health.domain,
message: 'DNS resolution failed',
});
}
}
if (issues.length > 0) {
// Send alert (email, Slack, PagerDuty, etc.)
await sendAlert({
title: `${issues.length} Domain Issues Detected`,
issues,
});
}
return issues;
}
// Run every hour via cron
// crontab: 0 * * * * /path/to/check-domains.shProduction Best Practices
1. Domain Naming Conventions
- Short and memorable -
go.cobetter thanlinks.company.com - Clear purpose -
help.cofor support,join.cofor recruitment - Avoid ambiguity - Don't use
l.ink(looks likelink) - Consistent branding - All domains should reflect your brand
2. SSL and Security
- SnipLink auto-provisions SSL certificates via Let's Encrypt
- Certificates auto-renew before expiration
- HTTP traffic automatically redirects to HTTPS
- Monitor SSL status with automated health checks
3. DNS Management
// Use infrastructure as code for DNS
// Terraform example for Cloudflare
resource "cloudflare_record" "shortlink_cname" {
zone_id = var.cloudflare_zone_id
name = "go"
type = "CNAME"
value = "redirect.sniplink.co"
proxied = false // Important: disable Cloudflare proxy
}
resource "cloudflare_record" "shortlink_verify" {
zone_id = var.cloudflare_zone_id
name = "_sniplink.go"
type = "TXT"
value = var.sniplink_verification_token
}4. Failover Strategy
Always have a backup domain in case of DNS issues:
const PRIMARY_DOMAIN = 'go.company.com';
const FALLBACK_DOMAIN = 'r.sniplink.co'; // Default SnipLink domain
async function createLinkWithFailover(url: string, slug?: string) {
try {
// Try primary domain
return await createBrandedLink(url, PRIMARY_DOMAIN_ID, slug);
} catch (error) {
console.warn('Primary domain failed, using fallback');
// Fallback to default domain
return await fetch(`${API_BASE}/workspaces/${WORKSPACE_ID}/links`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url, slug }),
}).then((r) => r.json());
}
}5. Domain Rotation for Load Distribution
const DOMAIN_POOL = [
'go.company.com',
'link.company.com',
'r.company.com',
];
async function createLinkWithRotation(url: string) {
// Round-robin domain selection
const domainIndex = Math.floor(Math.random() * DOMAIN_POOL.length);
const selectedDomain = DOMAIN_POOL[domainIndex];
const domains = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/domains`,
{ headers: { 'X-API-Key': API_KEY } }
).then((r) => r.json());
const domainId = domains.data.domains.find(
(d: any) => d.domain === selectedDomain
)?.id;
return await createBrandedLink(url, domainId);
}
// Distribute links across multiple domains
const links = await Promise.all([
createLinkWithRotation('https://company.com/page1'),
createLinkWithRotation('https://company.com/page2'),
createLinkWithRotation('https://company.com/page3'),
]);Troubleshooting
Domain Verification Fails
Problem: Verification endpoint returns error.
Solutions:
- Check DNS propagation with
digornslookup:
# Check CNAME
dig go.yourcompany.com CNAME +short
# Should return: redirect.sniplink.co
# Check TXT
dig _sniplink.go.yourcompany.com TXT +short
# Should return: "sniplink-verify=xyz789abc"
# Check from different DNS servers
dig @8.8.8.8 go.yourcompany.com CNAME +short # Google DNS
dig @1.1.1.1 go.yourcompany.com CNAME +short # Cloudflare DNS- Wait 15-30 minutes for DNS propagation
- Verify TXT record format exactly matches (no quotes in value)
- If using Cloudflare, disable proxy (gray cloud, not orange)
SSL Certificate Not Provisioning
Problem: Domain verified but HTTPS doesn't work.
Solutions:
- Wait 5-10 minutes after verification
- Check CNAME points to correct value
- Ensure port 443 is not blocked
- Verify domain is not behind a proxy that blocks ACME challenges
Links Return 404
Problem: Branded links show 404 error.
Solutions:
- Verify domain is still active and verified
- Check DNS records haven't changed
- Ensure link was created with correct domain ID
- Confirm domain hasn't been deleted
Next Steps
Link Expiration →
Combine custom domains with expiration for time-limited branded campaigns
Bulk Link Generator →
Generate hundreds of branded links at once with custom domains
Domains Guide →
Complete API reference for custom domain management
Analytics Dashboard →
Track performance by custom domain with dedicated analytics
Need Help with Custom Domains?
Having trouble with DNS configuration or domain verification? Loading contact information or check our troubleshooting guide.