7 Ways to Use URL Intelligence in Your SaaS Product
Product23 min read

7 Ways to Use URL Intelligence in Your SaaS Product

Discover how leading SaaS companies leverage URL metadata to enhance user experience, improve security, and drive engagement. Complete with code examples and implementation guides.

Katsau

Katsau Team

December 25, 2025

Share:

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.

Ready to build?

Try Katsau API

Extract metadata, generate link previews, and monitor URLs with our powerful API. Start free with 1,000 requests per month.