URL intelligence — the ability to extract, analyze, and utilize metadata from URLs — has become a competitive advantage for modern SaaS products. From Notion to Slack, the best products use URL metadata to create richer, more engaging experiences.
In this comprehensive guide, we'll explore seven powerful ways to integrate URL intelligence into your SaaS product, with real examples, complete code implementations, and best practices from production systems.
What is URL Intelligence?
Before diving into use cases, let's define what we mean by URL intelligence:
| Capability | What It Provides | Value |
|---|---|---|
| Metadata Extraction | Title, description, images | Rich content previews |
| Technology Detection | Frameworks, libraries, services | Competitive insights |
| Screenshot Capture | Visual page snapshots | Verification & archiving |
| Validation | Link health, SSL, accessibility | Quality assurance |
| Monitoring | Change detection | Awareness & alerting |
| Classification | Content categorization | Organization & filtering |
The Data Available from a URL
A single API call can extract extensive information:
// Example response from Katsau API
const urlIntelligence = {
// Basic metadata
url: "https://example.com/product",
finalUrl: "https://www.example.com/product",
title: "Product Name - Example Company",
description: "The best product for solving X problem...",
// Open Graph data
openGraph: {
title: "Product Name",
description: "Detailed description...",
image: "https://example.com/og-image.png",
type: "product",
siteName: "Example Company"
},
// Twitter Card data
twitter: {
card: "summary_large_image",
site: "@example",
creator: "@founder"
},
// Technical information
favicon: "https://example.com/favicon.ico",
theme: {
color: "#3B82F6",
colorScheme: "light"
},
// Technology stack
technologies: [
{ name: "React", version: "18.2", category: "Frontend" },
{ name: "Next.js", version: "14", category: "Framework" },
{ name: "Vercel", category: "Hosting" }
],
// Links and relationships
links: {
canonical: "https://example.com/product",
ampUrl: null,
feeds: ["https://example.com/rss.xml"]
},
// Performance indicators
performance: {
loadTime: 1234,
ssl: true,
statusCode: 200
}
};
Now let's explore how to leverage this data.
1. Rich Link Previews in Messaging
The most common use case, but far from trivial to implement well. When users share links in your app, displaying rich previews keeps them engaged without leaving your platform.
Why It Matters
| Without Previews | With Previews |
|---|---|
| Users leave to check links | Users understand content instantly |
| Conversation flow breaks | Seamless experience |
| Higher bounce rates | Lower friction, higher retention |
| Lost context | Rich context preserved |
| Requires trust in sender | Visual verification of content |
Complete Implementation
Here's a production-ready implementation:
// types/link-preview.ts
export interface LinkPreviewData {
url: string;
title: string;
description: string | null;
image: string | null;
favicon: string | null;
siteName: string | null;
type: string;
}
export interface MessageWithPreviews {
id: string;
content: string;
authorId: string;
createdAt: Date;
linkPreviews: LinkPreviewData[];
}
// services/link-preview-service.ts
import { LRUCache } from 'lru-cache';
const cache = new LRUCache<string, LinkPreviewData>({
max: 1000,
ttl: 1000 * 60 * 60 // 1 hour
});
const API_KEY = process.env.KATSAU_API_KEY;
export async function fetchLinkPreview(url: string): Promise<LinkPreviewData | null> {
// Check cache first
const cached = cache.get(url);
if (cached) return cached;
try {
const response = await fetch(
`https://api.katsau.com/v1/extract?url=${encodeURIComponent(url)}`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
signal: AbortSignal.timeout(5000) // 5 second timeout
}
);
if (!response.ok) {
console.error(`Failed to fetch preview for ${url}: ${response.status}`);
return null;
}
const { data } = await response.json();
const preview: LinkPreviewData = {
url,
title: data.title || data.openGraph?.title || new URL(url).hostname,
description: data.description || data.openGraph?.description || null,
image: data.openGraph?.image || null,
favicon: data.favicon || null,
siteName: data.openGraph?.siteName || new URL(url).hostname,
type: data.openGraph?.type || 'website'
};
// Cache the result
cache.set(url, preview);
return preview;
} catch (error) {
console.error(`Error fetching preview for ${url}:`, error);
return null;
}
}
export async function fetchMultiplePreviews(urls: string[]): Promise<Map<string, LinkPreviewData>> {
const results = new Map<string, LinkPreviewData>();
const urlsToFetch: string[] = [];
// Check cache first
for (const url of urls) {
const cached = cache.get(url);
if (cached) {
results.set(url, cached);
} else {
urlsToFetch.push(url);
}
}
if (urlsToFetch.length === 0) return results;
// Batch fetch remaining URLs
try {
const response = await fetch('https://api.katsau.com/v1/batch/extract', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ urls: urlsToFetch })
});
const { data } = await response.json();
for (const result of data.results) {
if (result.success && result.data) {
const preview: LinkPreviewData = {
url: result.url,
title: result.data.title || new URL(result.url).hostname,
description: result.data.description || null,
image: result.data.openGraph?.image || null,
favicon: result.data.favicon || null,
siteName: result.data.openGraph?.siteName || null,
type: result.data.openGraph?.type || 'website'
};
results.set(result.url, preview);
cache.set(result.url, preview);
}
}
} catch (error) {
console.error('Batch preview fetch failed:', error);
}
return results;
}
// services/message-service.ts
import { fetchMultiplePreviews, LinkPreviewData } from './link-preview-service';
const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
export function extractUrls(text: string): string[] {
const matches = text.match(URL_REGEX);
return matches ? [...new Set(matches)] : [];
}
export async function enrichMessageWithPreviews(
message: Message
): Promise<MessageWithPreviews> {
const urls = extractUrls(message.content);
if (urls.length === 0) {
return { ...message, linkPreviews: [] };
}
const previews = await fetchMultiplePreviews(urls);
const linkPreviews: LinkPreviewData[] = [];
for (const url of urls) {
const preview = previews.get(url);
if (preview) {
linkPreviews.push(preview);
}
}
return { ...message, linkPreviews };
}
// components/LinkPreviewCard.tsx
import { LinkPreviewData } from '@/types/link-preview';
interface Props {
preview: LinkPreviewData;
compact?: boolean;
}
export function LinkPreviewCard({ preview, compact = false }: Props) {
return (
<a
href={preview.url}
target="_blank"
rel="noopener noreferrer"
className={`
block rounded-lg border border-zinc-200 dark:border-zinc-800
overflow-hidden hover:border-blue-500/50 transition-colors
${compact ? 'max-w-sm' : 'max-w-lg'}
`}
>
{preview.image && !compact && (
<div className="aspect-[1.91/1] relative">
<img
src={preview.image}
alt=""
className="w-full h-full object-cover"
loading="lazy"
/>
</div>
)}
<div className={`p-3 ${compact ? 'flex items-center gap-3' : ''}`}>
{preview.favicon && (
<img
src={preview.favicon}
alt=""
className={compact ? 'w-8 h-8 rounded' : 'w-4 h-4 mb-2'}
/>
)}
<div className="flex-1 min-w-0">
<p className={`font-medium text-zinc-900 dark:text-zinc-100
${compact ? 'text-sm truncate' : 'line-clamp-2'}`}>
{preview.title}
</p>
{!compact && preview.description && (
<p className="text-sm text-zinc-600 dark:text-zinc-400
line-clamp-2 mt-1">
{preview.description}
</p>
)}
<p className="text-xs text-zinc-500 mt-1">
{preview.siteName || new URL(preview.url).hostname}
</p>
</div>
</div>
</a>
);
}
Who Does It Well
| Product | Implementation Quality | Notable Features |
|---|---|---|
| Slack | Excellent | App-specific unfurling, interactive buttons |
| Discord | Excellent | Multi-embed support, video/audio previews |
| Notion | Very Good | Clean inline previews, bookmark blocks |
| Linear | Very Good | Issue context extraction |
| Figma | Good | Design file previews |
2. Content Curation & Discovery
Help users discover and organize content by automatically categorizing and enriching saved URLs.
Use Cases
| Product Type | Application |
|---|---|
| Bookmark Managers | Auto-categorize saved links |
| Research Tools | Extract key information from sources |
| News Aggregators | Identify topics and entities |
| Knowledge Bases | Build structured collections |
| Read-Later Apps | Estimate read time, extract authors |
Implementation: Auto-Tagging Bookmarks
// services/bookmark-enrichment.ts
interface EnrichedBookmark {
url: string;
title: string;
description: string | null;
image: string | null;
favicon: string | null;
tags: string[];
category: string;
readingTime: number | null;
author: string | null;
publishedDate: string | null;
}
// Domain to category mapping
const DOMAIN_CATEGORIES: Record<string, string> = {
'github.com': 'Development',
'gitlab.com': 'Development',
'stackoverflow.com': 'Development',
'medium.com': 'Articles',
'dev.to': 'Development',
'hackernews.ycombinator.com': 'News',
'twitter.com': 'Social',
'linkedin.com': 'Professional',
'youtube.com': 'Video',
'vimeo.com': 'Video',
'figma.com': 'Design',
'dribbble.com': 'Design',
'behance.net': 'Design',
// Add more as needed
};
// OG type to category mapping
const TYPE_CATEGORIES: Record<string, string> = {
'article': 'Articles',
'video.movie': 'Video',
'video.episode': 'Video',
'music.song': 'Music',
'product': 'Shopping',
'book': 'Reading',
};
export async function enrichBookmark(url: string): Promise<EnrichedBookmark> {
const response = await fetch(
`https://api.katsau.com/v1/extract?url=${encodeURIComponent(url)}`,
{
headers: { 'Authorization': `Bearer ${process.env.KATSAU_API_KEY}` }
}
);
const { data } = await response.json();
const hostname = new URL(url).hostname.replace('www.', '');
// Generate tags
const tags: string[] = [];
// Add keywords from metadata
if (data.keywords && Array.isArray(data.keywords)) {
tags.push(...data.keywords.slice(0, 5));
}
// Add technology names for dev sites
if (data.technologies && DOMAIN_CATEGORIES[hostname] === 'Development') {
const techTags = data.technologies
.slice(0, 3)
.map((t: { name: string }) => t.name.toLowerCase());
tags.push(...techTags);
}
// Add OG type as tag
if (data.openGraph?.type && data.openGraph.type !== 'website') {
tags.push(data.openGraph.type);
}
// Determine category
let category = 'Uncategorized';
if (DOMAIN_CATEGORIES[hostname]) {
category = DOMAIN_CATEGORIES[hostname];
} else if (data.openGraph?.type && TYPE_CATEGORIES[data.openGraph.type]) {
category = TYPE_CATEGORIES[data.openGraph.type];
}
// Estimate reading time (if article)
let readingTime: number | null = null;
if (category === 'Articles' && data.article?.wordCount) {
readingTime = Math.ceil(data.article.wordCount / 200); // 200 WPM average
}
return {
url,
title: data.title || hostname,
description: data.description || null,
image: data.openGraph?.image || null,
favicon: data.favicon || null,
tags: [...new Set(tags)].slice(0, 10), // Dedupe and limit
category,
readingTime,
author: data.article?.author || data.twitter?.creator || null,
publishedDate: data.article?.publishedTime || null
};
}
// Example usage in bookmark API
app.post('/api/bookmarks', async (req, res) => {
const { url, userId } = req.body;
// Enrich the bookmark
const enrichedData = await enrichBookmark(url);
// Save to database
const bookmark = await db.bookmarks.create({
data: {
userId,
...enrichedData,
createdAt: new Date()
}
});
res.json({ success: true, bookmark });
});
// Search bookmarks by auto-generated tags
app.get('/api/bookmarks/search', async (req, res) => {
const { userId, tag, category } = req.query;
const bookmarks = await db.bookmarks.findMany({
where: {
userId,
...(tag && { tags: { has: tag } }),
...(category && { category })
},
orderBy: { createdAt: 'desc' }
});
res.json({ bookmarks });
});
3. Security & Phishing Detection
Protect users by validating URLs before they click. Analyze link destinations, detect redirects, and warn about suspicious patterns.
Security Indicators
| Check | Risk Signal | Severity |
|---|---|---|
| New domain (< 30 days) | High risk of phishing | High |
| No SSL certificate | Insecure connection | High |
| Multiple redirects (> 3) | Potential masking | Medium |
| Domain mismatch | Link text ≠ actual URL | Critical |
| Known bad patterns | URL shorteners, typosquatting | Medium |
| Missing metadata | Potentially suspicious | Low |
Implementation: URL Safety Checker
// services/url-safety-service.ts
interface SafetyCheck {
type: string;
severity: 'low' | 'medium' | 'high' | 'critical';
message: string;
details?: Record<string, unknown>;
}
interface SafetyReport {
url: string;
safe: boolean;
score: number; // 0-100, higher is safer
checks: SafetyCheck[];
finalUrl: string;
metadata?: {
title: string;
favicon: string | null;
};
}
// Known suspicious patterns
const SUSPICIOUS_PATTERNS = [
/bit\.ly/i,
/tinyurl\.com/i,
/t\.co/i,
/goo\.gl/i,
// Typosquatting patterns
/amaz0n/i,
/g00gle/i,
/micros0ft/i,
/faceb00k/i,
];
const KNOWN_SAFE_DOMAINS = [
'google.com',
'github.com',
'microsoft.com',
'apple.com',
'amazon.com',
// Add trusted domains
];
export async function checkUrlSafety(url: string): Promise<SafetyReport> {
const checks: SafetyCheck[] = [];
let score = 100;
try {
// Validate URL format
const parsedUrl = new URL(url);
// Check for suspicious patterns in URL
for (const pattern of SUSPICIOUS_PATTERNS) {
if (pattern.test(url)) {
checks.push({
type: 'suspicious_pattern',
severity: 'medium',
message: 'URL matches a known suspicious pattern',
details: { pattern: pattern.toString() }
});
score -= 15;
}
}
// Check SSL
if (parsedUrl.protocol !== 'https:') {
checks.push({
type: 'no_ssl',
severity: 'high',
message: 'Connection is not secure (no HTTPS)'
});
score -= 25;
}
// Fetch metadata and check for redirects
const response = await fetch(
`https://api.katsau.com/v1/extract?url=${encodeURIComponent(url)}`,
{
headers: { 'Authorization': `Bearer ${process.env.KATSAU_API_KEY}` }
}
);
const { data } = await response.json();
// Check for redirect chain
if (data.redirects && data.redirects.length > 2) {
checks.push({
type: 'redirect_chain',
severity: 'medium',
message: `URL redirects ${data.redirects.length} times before reaching destination`,
details: { redirects: data.redirects }
});
score -= 10 * Math.min(data.redirects.length - 2, 3);
}
// Check for domain mismatch (displayed URL vs final URL)
const originalDomain = parsedUrl.hostname.replace('www.', '');
const finalDomain = new URL(data.finalUrl).hostname.replace('www.', '');
if (originalDomain !== finalDomain) {
// Critical if landing on a completely different domain
const severity = KNOWN_SAFE_DOMAINS.includes(finalDomain) ? 'low' : 'critical';
checks.push({
type: 'domain_mismatch',
severity,
message: `Link leads to different domain: ${finalDomain}`,
details: { original: originalDomain, final: finalDomain }
});
score -= severity === 'critical' ? 40 : 5;
}
// Check for missing metadata (potential phishing page)
if (!data.title && !data.description) {
checks.push({
type: 'missing_metadata',
severity: 'low',
message: 'Page has no metadata (may be suspicious or under construction)'
});
score -= 5;
}
// Bonus for known safe domains
if (KNOWN_SAFE_DOMAINS.some(d => finalDomain.endsWith(d))) {
score = Math.min(100, score + 20);
}
return {
url,
safe: checks.every(c => c.severity !== 'critical' && c.severity !== 'high'),
score: Math.max(0, score),
checks,
finalUrl: data.finalUrl,
metadata: {
title: data.title,
favicon: data.favicon
}
};
} catch (error) {
checks.push({
type: 'fetch_failed',
severity: 'high',
message: 'Unable to verify URL safety',
details: { error: String(error) }
});
return {
url,
safe: false,
score: 0,
checks,
finalUrl: url
};
}
}
// components/SafetyIndicator.tsx
interface Props {
report: SafetyReport;
onProceed: () => void;
onCancel: () => void;
}
export function SafetyIndicator({ report, onProceed, onCancel }: Props) {
if (report.safe && report.score > 70) {
// Safe link - show green checkmark
return (
<div className="flex items-center gap-2 text-green-600">
<CheckCircle className="w-4 h-4" />
<span className="text-sm">Verified safe</span>
</div>
);
}
// Show warning modal for unsafe links
return (
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
<div className="flex items-start gap-3">
<AlertTriangle className="w-5 h-5 text-yellow-600 mt-0.5" />
<div className="flex-1">
<h4 className="font-medium text-yellow-800 dark:text-yellow-200">
This link may be unsafe
</h4>
<ul className="mt-2 space-y-1">
{report.checks.map((check, i) => (
<li key={i} className="text-sm text-yellow-700 dark:text-yellow-300">
• {check.message}
</li>
))}
</ul>
<div className="mt-4 flex gap-2">
<button
onClick={onProceed}
className="px-3 py-1.5 text-sm bg-yellow-600 text-white rounded hover:bg-yellow-700"
>
Proceed Anyway
</button>
<button
onClick={onCancel}
className="px-3 py-1.5 text-sm border border-yellow-600 text-yellow-700 rounded hover:bg-yellow-100"
>
Cancel
</button>
</div>
</div>
</div>
</div>
);
}
4. Competitive Intelligence
Monitor competitor websites, track their content changes, and stay ahead of market trends.
What You Can Track
| Element | Insight | Business Value |
|---|---|---|
| Homepage messaging | Positioning shifts | Competitive positioning |
| Pricing page | Pricing strategy changes | Pricing decisions |
| Feature pages | New feature launches | Product roadmap |
| Blog content | Content marketing velocity | Marketing strategy |
| Tech stack | Tool adoption | Technology decisions |
| Job listings | Growth & focus areas | Market intelligence |
Implementation: Competitor Monitoring System
// types/competitor-monitoring.ts
interface MonitoredPage {
url: string;
competitorName: string;
pageType: 'homepage' | 'pricing' | 'features' | 'blog' | 'careers' | 'other';
checkInterval: 'hourly' | 'daily' | 'weekly';
}
interface PageSnapshot {
url: string;
timestamp: Date;
title: string;
description: string;
ogImage: string | null;
textContent: string;
technologies: string[];
links: string[];
}
interface ChangeAlert {
url: string;
competitorName: string;
changeType: 'title' | 'description' | 'image' | 'content' | 'tech';
previousValue: string;
newValue: string;
detectedAt: Date;
severity: 'low' | 'medium' | 'high';
}
// services/competitor-monitor.ts
const MONITOR_CONFIGS: MonitoredPage[] = [
{
url: 'https://competitor1.com/',
competitorName: 'Competitor 1',
pageType: 'homepage',
checkInterval: 'daily'
},
{
url: 'https://competitor1.com/pricing',
competitorName: 'Competitor 1',
pageType: 'pricing',
checkInterval: 'daily'
},
// Add more...
];
async function setupMonitoring() {
for (const config of MONITOR_CONFIGS) {
await fetch('https://api.katsau.com/v1/monitors', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KATSAU_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: config.url,
checkInterval: config.checkInterval,
notifyOn: [
'title_change',
'description_change',
'og_image_change',
'technologies_change'
],
webhookUrl: process.env.WEBHOOK_URL,
metadata: {
competitorName: config.competitorName,
pageType: config.pageType
}
})
});
}
}
// Webhook handler for changes
app.post('/webhooks/competitor-changes', async (req, res) => {
const {
url,
changeType,
previousValue,
newValue,
detectedAt,
metadata
} = req.body;
// Determine severity based on change type and page type
let severity: 'low' | 'medium' | 'high' = 'low';
if (metadata.pageType === 'pricing') {
severity = 'high'; // Pricing changes are always important
} else if (changeType === 'title' || changeType === 'description') {
severity = 'medium'; // Messaging changes are notable
}
// Store the change
await db.competitorChanges.create({
data: {
url,
competitorName: metadata.competitorName,
pageType: metadata.pageType,
changeType,
previousValue,
newValue,
detectedAt: new Date(detectedAt),
severity
}
});
// Send alert for high severity changes
if (severity === 'high') {
await sendSlackAlert({
channel: '#competitive-intel',
text: `🚨 Important change detected on ${metadata.competitorName}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${metadata.competitorName}* - ${metadata.pageType} page`
}
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Change Type*\n${changeType}` },
{ type: 'mrkdwn', text: `*Page*\n${url}` },
{ type: 'mrkdwn', text: `*Before*\n${previousValue.substring(0, 100)}...` },
{ type: 'mrkdwn', text: `*After*\n${newValue.substring(0, 100)}...` }
]
}
]
});
}
res.status(200).json({ received: true });
});
// API endpoint for competitive intelligence dashboard
app.get('/api/competitive-intel', async (req, res) => {
const { competitor, pageType, severity, days = 30 } = req.query;
const since = new Date();
since.setDate(since.getDate() - Number(days));
const changes = await db.competitorChanges.findMany({
where: {
detectedAt: { gte: since },
...(competitor && { competitorName: competitor }),
...(pageType && { pageType }),
...(severity && { severity })
},
orderBy: { detectedAt: 'desc' },
take: 100
});
// Aggregate stats
const stats = {
totalChanges: changes.length,
byCompetitor: groupBy(changes, 'competitorName'),
byPageType: groupBy(changes, 'pageType'),
bySeverity: groupBy(changes, 'severity'),
timeline: groupByDate(changes, 'detectedAt')
};
res.json({ changes, stats });
});
5. Sales Intelligence & Lead Enrichment
Enrich leads with company information by analyzing their website metadata and technology stack.
Data Points for Sales
| Data | Source | Sales Value |
|---|---|---|
| Company name | OG site name | Basic identification |
| Description | Meta description | Value prop understanding |
| Logo/Favicon | Favicon API | CRM branding |
| Technologies | Tech detection | Integration opportunities |
| Social links | Meta tags | Multi-channel outreach |
| Team size signals | Careers page monitoring | Company sizing |
| Recent changes | Monitoring | Trigger events |
Implementation: Lead Enrichment Pipeline
// services/lead-enrichment.ts
interface EnrichedLead {
domain: string;
companyName: string;
description: string | null;
industry: string | null;
logo: string | null;
website: string;
technologies: {
name: string;
category: string;
icon?: string;
}[];
socialLinks: {
twitter?: string;
linkedin?: string;
facebook?: string;
github?: string;
};
signals: {
type: string;
value: string;
insight: string;
}[];
enrichedAt: Date;
}
// Industry detection based on technologies
const TECH_INDUSTRY_MAP: Record<string, string> = {
'Shopify': 'E-commerce',
'WooCommerce': 'E-commerce',
'Magento': 'E-commerce',
'Salesforce': 'B2B Software',
'HubSpot': 'Marketing/Sales',
'WordPress': 'Content/Publishing',
'Webflow': 'Design/Agency',
};
export async function enrichLead(domain: string): Promise<EnrichedLead> {
const url = domain.startsWith('http') ? domain : `https://${domain}`;
// Fetch main page metadata
const [extractResponse, techResponse] = await Promise.all([
fetch(`https://api.katsau.com/v1/extract?url=${encodeURIComponent(url)}`, {
headers: { 'Authorization': `Bearer ${process.env.KATSAU_API_KEY}` }
}),
fetch(`https://api.katsau.com/v1/tech?url=${encodeURIComponent(url)}`, {
headers: { 'Authorization': `Bearer ${process.env.KATSAU_API_KEY}` }
})
]);
const [extractData, techData] = await Promise.all([
extractResponse.json(),
techResponse.json()
]);
const metadata = extractData.data;
const technologies = techData.data?.technologies || [];
// Extract social links from metadata
const socialLinks: EnrichedLead['socialLinks'] = {};
if (metadata.twitter?.site) {
socialLinks.twitter = `https://twitter.com/${metadata.twitter.site.replace('@', '')}`;
}
// Check for LinkedIn in links
metadata.links?.forEach((link: string) => {
if (link.includes('linkedin.com/company')) {
socialLinks.linkedin = link;
}
if (link.includes('github.com')) {
socialLinks.github = link;
}
if (link.includes('facebook.com')) {
socialLinks.facebook = link;
}
});
// Detect industry from technologies
let industry: string | null = null;
for (const tech of technologies) {
if (TECH_INDUSTRY_MAP[tech.name]) {
industry = TECH_INDUSTRY_MAP[tech.name];
break;
}
}
// Generate sales signals
const signals: EnrichedLead['signals'] = [];
// Tech stack signals
const hasCRM = technologies.some((t: { name: string }) =>
['Salesforce', 'HubSpot', 'Pipedrive'].includes(t.name)
);
if (!hasCRM) {
signals.push({
type: 'tech_gap',
value: 'No CRM detected',
insight: 'May be open to CRM solutions'
});
}
// Growth signals from technologies
const hasAnalytics = technologies.some((t: { name: string }) =>
['Google Analytics', 'Mixpanel', 'Amplitude', 'Segment'].includes(t.name)
);
if (hasAnalytics) {
signals.push({
type: 'maturity',
value: 'Analytics present',
insight: 'Data-driven company, likely values metrics'
});
}
// Modern stack signals
const hasModernFrontend = technologies.some((t: { name: string }) =>
['React', 'Vue.js', 'Next.js', 'Nuxt'].includes(t.name)
);
if (hasModernFrontend) {
signals.push({
type: 'tech_maturity',
value: 'Modern frontend stack',
insight: 'Engineering team likely values developer experience'
});
}
return {
domain: new URL(url).hostname,
companyName: metadata.openGraph?.siteName || metadata.title?.split(' - ')[0] || domain,
description: metadata.description,
industry,
logo: metadata.favicon,
website: url,
technologies: technologies.map((t: { name: string; category: string }) => ({
name: t.name,
category: t.category
})),
socialLinks,
signals,
enrichedAt: new Date()
};
}
// API integration with CRM
app.post('/api/enrich-lead', async (req, res) => {
const { email, domain } = req.body;
// Extract domain from email if not provided
const targetDomain = domain || email.split('@')[1];
// Skip free email providers
const freeProviders = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com'];
if (freeProviders.includes(targetDomain)) {
return res.status(400).json({
error: 'Cannot enrich leads with free email providers'
});
}
try {
const enrichedData = await enrichLead(targetDomain);
// Update CRM (example with HubSpot)
await hubspotClient.companies.create({
properties: {
name: enrichedData.companyName,
domain: enrichedData.domain,
description: enrichedData.description,
industry: enrichedData.industry,
website: enrichedData.website,
twitterhandle: enrichedData.socialLinks.twitter,
linkedin_company_page: enrichedData.socialLinks.linkedin,
// Custom properties for technologies
tech_stack: enrichedData.technologies.map(t => t.name).join(', ')
}
});
res.json({ success: true, data: enrichedData });
} catch (error) {
res.status(500).json({ error: 'Enrichment failed' });
}
});
6. Content Validation in CMS
Ensure content quality by validating external links before publishing.
Validation Checks
| Check | Purpose | Severity |
|---|---|---|
| URL accessibility | Avoid 404 links | Critical |
| SSL certificate | Secure connections | High |
| Content appropriateness | Brand safety | High |
| Image availability | Complete content | Medium |
| Load performance | User experience | Low |
Implementation: Pre-Publish Link Validator
// services/content-validator.ts
interface LinkValidationResult {
url: string;
status: 'valid' | 'warning' | 'error';
checks: {
accessible: boolean;
ssl: boolean;
hasTitle: boolean;
hasImage: boolean;
loadTime: number | null;
};
metadata?: {
title: string;
description: string;
image: string | null;
};
error?: string;
}
interface ContentValidationReport {
valid: boolean;
totalLinks: number;
validLinks: number;
warningLinks: number;
errorLinks: number;
results: LinkValidationResult[];
duration: number;
}
// Extract all URLs from HTML content
function extractLinksFromHtml(html: string): string[] {
const linkRegex = /href=["']([^"']+)["']/gi;
const urls: string[] = [];
let match;
while ((match = linkRegex.exec(html)) !== null) {
const url = match[1];
// Only process external URLs
if (url.startsWith('http://') || url.startsWith('https://')) {
urls.push(url);
}
}
return [...new Set(urls)];
}
export async function validateContentLinks(
htmlContent: string
): Promise<ContentValidationReport> {
const startTime = Date.now();
const urls = extractLinksFromHtml(htmlContent);
if (urls.length === 0) {
return {
valid: true,
totalLinks: 0,
validLinks: 0,
warningLinks: 0,
errorLinks: 0,
results: [],
duration: Date.now() - startTime
};
}
// Batch validate all URLs
const response = await fetch('https://api.katsau.com/v1/batch/extract', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KATSAU_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ urls })
});
const { data } = await response.json();
const results: LinkValidationResult[] = [];
for (const result of data.results) {
if (result.error) {
results.push({
url: result.url,
status: 'error',
checks: {
accessible: false,
ssl: result.url.startsWith('https'),
hasTitle: false,
hasImage: false,
loadTime: null
},
error: result.error
});
} else {
const checks = {
accessible: true,
ssl: result.url.startsWith('https'),
hasTitle: !!result.data.title,
hasImage: !!result.data.openGraph?.image,
loadTime: result.data.performance?.loadTime || null
};
// Determine status
let status: 'valid' | 'warning' | 'error' = 'valid';
if (!checks.ssl) status = 'warning';
if (!checks.accessible) status = 'error';
results.push({
url: result.url,
status,
checks,
metadata: {
title: result.data.title,
description: result.data.description,
image: result.data.openGraph?.image || null
}
});
}
}
const validLinks = results.filter(r => r.status === 'valid').length;
const warningLinks = results.filter(r => r.status === 'warning').length;
const errorLinks = results.filter(r => r.status === 'error').length;
return {
valid: errorLinks === 0,
totalLinks: results.length,
validLinks,
warningLinks,
errorLinks,
results,
duration: Date.now() - startTime
};
}
7. Analytics & Attribution
Track which external content drives engagement and conversions.
Metrics to Track
| Metric | What It Tells You | Action |
|---|---|---|
| Click rate by domain | Trusted sources | Feature trusted domains |
| Time to click | Content relevance | Optimize preview placement |
| Bounce rate | Content quality | Warn about low-quality links |
| Conversion path | Attribution | Credit link sources |
Implementation: Link Analytics
// services/link-analytics.ts
interface LinkInteraction {
id: string;
url: string;
userId: string;
sessionId: string;
action: 'impression' | 'hover' | 'click' | 'share';
metadata: {
domain: string;
title: string;
category: string;
source: string; // Where the link was shown
};
timestamp: Date;
}
export async function trackLinkInteraction(
interaction: Omit<LinkInteraction, 'id' | 'metadata' | 'timestamp'>
): Promise<void> {
// Get URL metadata for categorization
const response = await fetch(
`https://api.katsau.com/v1/extract?url=${encodeURIComponent(interaction.url)}`,
{ headers: { 'Authorization': `Bearer ${process.env.KATSAU_API_KEY}` } }
);
const { data } = await response.json();
// Log to analytics database
await db.linkInteractions.create({
data: {
...interaction,
metadata: {
domain: new URL(interaction.url).hostname,
title: data.title || 'Unknown',
category: data.openGraph?.type || 'website',
source: interaction.source
},
timestamp: new Date()
}
});
// Also send to product analytics
await analytics.track('Link Interaction', {
userId: interaction.userId,
action: interaction.action,
url: interaction.url,
domain: new URL(interaction.url).hostname,
contentType: data.openGraph?.type || 'unknown',
source: interaction.source
});
}
// Aggregate analytics
export async function getLinkAnalytics(
options: { userId?: string; days?: number }
): Promise<{
topDomains: { domain: string; clicks: number; ctr: number }[];
actionBreakdown: Record<string, number>;
dailyTrend: { date: string; clicks: number }[];
}> {
const { userId, days = 30 } = options;
const since = new Date();
since.setDate(since.getDate() - days);
const interactions = await db.linkInteractions.findMany({
where: {
timestamp: { gte: since },
...(userId && { userId })
}
});
// Calculate metrics
const domainStats = new Map<string, { impressions: number; clicks: number }>();
const actionCounts: Record<string, number> = {};
const dailyClicks = new Map<string, number>();
for (const interaction of interactions) {
// Domain stats
const domain = interaction.metadata.domain;
const stats = domainStats.get(domain) || { impressions: 0, clicks: 0 };
if (interaction.action === 'impression') stats.impressions++;
if (interaction.action === 'click') stats.clicks++;
domainStats.set(domain, stats);
// Action breakdown
actionCounts[interaction.action] = (actionCounts[interaction.action] || 0) + 1;
// Daily trend
if (interaction.action === 'click') {
const date = interaction.timestamp.toISOString().split('T')[0];
dailyClicks.set(date, (dailyClicks.get(date) || 0) + 1);
}
}
// Format results
const topDomains = Array.from(domainStats.entries())
.map(([domain, stats]) => ({
domain,
clicks: stats.clicks,
ctr: stats.impressions > 0 ? (stats.clicks / stats.impressions) * 100 : 0
}))
.sort((a, b) => b.clicks - a.clicks)
.slice(0, 20);
const dailyTrend = Array.from(dailyClicks.entries())
.map(([date, clicks]) => ({ date, clicks }))
.sort((a, b) => a.date.localeCompare(b.date));
return {
topDomains,
actionBreakdown: actionCounts,
dailyTrend
};
}
Getting Started
URL intelligence can transform your SaaS product from a simple tool into an intelligent platform. Start with the use case that provides the most value for your users.
Quick Start Checklist
| Step | Action | Effort |
|---|---|---|
| 1 | Identify your primary use case | 1 hour |
| 2 | Sign up for Katsau | 5 minutes |
| 3 | Implement proof of concept | 1-2 days |
| 4 | Measure impact on engagement | 1 week |
| 5 | Expand to additional use cases | Ongoing |
Recommended Starting Points by Product Type
| Product Type | Start With | Then Add |
|---|---|---|
| Messaging App | Link Previews | Security Checks |
| CRM | Lead Enrichment | Competitive Intel |
| CMS | Content Validation | Analytics |
| Bookmark Manager | Auto-Categorization | Content Discovery |
| Security Tool | URL Safety | Monitoring |
Ready to add URL intelligence to your product? Get your free API key and start building today. First 1,000 requests/month are free.
Try Katsau API
Extract metadata, generate link previews, and monitor URLs with our powerful API. Start free with 1,000 requests per month.