Back to Blog
Guide

UGC Content Collection: Find & License User-Generated Content at Scale

October 30, 2025
21 min read
By SociaVault Team
UGC ContentUser Generated ContentContent MarketingBrand MonitoringSocial Listening

UGC Content Collection: Find & License User-Generated Content at Scale

Your marketing team spent $5,000 producing a product photoshoot.

Professional photographer. Studio lighting. Models. Props. Full day of shooting.

The result? 50 polished photos that look... like ads. Engagement: mediocre.

Meanwhile, a customer posted a quick iPhone video using your product. Authentic. Real. Relatable.

That video got 10x more engagement than your $5,000 photoshoot.

And you almost missed it. You only found it because someone on your team happened to scroll past it.

Here's the reality: Your customers are creating THOUSANDS of pieces of content about your brand every month. User-generated content (UGC) that:

  • Converts better (92% of consumers trust UGC more than ads)
  • Costs nothing to produce (customers make it for free)
  • Feels authentic (not salesy, not polished, just real)
  • Scales infinitely (more customers = more content)

The problem? Most brands are missing 95% of their UGC because they're only finding content with brand hashtags or @mentions. The best UGC often has neither.

This guide shows you how to build an automated UGC collection system that:

  • Finds ALL content (tagged or untagged)
  • Scores quality automatically
  • Manages licensing/permissions
  • Fills your content calendar forever

No more $5,000 photoshoots. Just authentic content from real customers.

Why UGC is Marketing Gold

Let's talk numbers:

UGC Performance vs Branded Content

Traditional branded content:

  • Costs: $2,000-10,000 per piece (photoshoot, video production)
  • Engagement rate: 1-2% (people know it's an ad)
  • Conversion rate: 0.5-1% (skepticism about "perfect" content)
  • Authenticity: Low (polished, staged, "too good to be true")

User-generated content:

  • Costs: $0-200 (licensing fee if you pay creator)
  • Engagement rate: 5-10% (authentic, relatable)
  • Conversion rate: 4-6% (peer recommendations work)
  • Authenticity: High (real people, real situations)

ROI comparison:

  • Branded photoshoot: $5,000 investment → 1.5% conversion → ~$2,000 revenue (if you're lucky)
  • UGC campaign: $500 investment (licensing 10 pieces) → 5% conversion → ~$15,000 revenue

UGC converts 3-4x better for 1/10th the cost.

The Trust Factor

Nielsen research:

  • 92% of consumers trust recommendations from people they know
  • 70% trust online consumer opinions (even from strangers)
  • 33% trust brand advertisements

Translation: A random customer's iPhone video is more trusted than your $10,000 commercial.

Why?

  • No agenda (they're not being paid)
  • Real experience (actually used the product)
  • Unfiltered (shows real results, flaws included)

The Volume Advantage

Your brand right now:

  • Marketing team: 3-5 people
  • Content production: 10-20 pieces per month
  • Cost: $10,000-30,000/month
  • Content calendar: Always running dry

Your customers:

  • Potential creators: Hundreds to thousands
  • Content production: 100-1,000+ pieces per month
  • Cost: $0 (they make it for free)
  • Content calendar: Never-ending supply

The math is simple: Tap into your customer army, 10x your content output, cut costs by 90%.

The UGC Problem: Discovery

Here's what most brands do:

  1. Search for brand hashtag → Find 10-20 posts
  2. Check @mentions → Find 5-10 more posts
  3. Manually DM creators → 50% never respond
  4. Repeat monthly → Always behind

What you're missing:

80% of UGC doesn't have your brand hashtag or @mention.

Why?

  • Customers forget to tag
  • Hashtag is too long or hard to spell
  • They don't know you want UGC
  • They're just sharing with friends (not thinking about brands)

Real example:

  • Brand: "FitBottle" (water bottle company)
  • Posts with #FitBottle: 120/month
  • Posts showing FitBottle without tag: 850/month
  • Missing 85% of their UGC

Method 1: Hashtag Monitoring

Let's start with the basics - monitoring your branded hashtags.

JavaScript Example

const axios = require('axios');

const SOCIAVAULT_API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE_URL = 'https://api.sociavault.com';

async function collectHashtagUGC(hashtag, platform = 'instagram') {
  try {
    let endpoint;
    
    if (platform === 'instagram') {
      endpoint = `${BASE_URL}/api/scrape/instagram/hashtag`;
    } else if (platform === 'tiktok') {
      endpoint = `${BASE_URL}/api/scrape/tiktok/hashtag`;
    }

    const response = await axios.get(endpoint, {
      params: {
        hashtag: hashtag.replace('#', ''),
        limit: 100
      },
      headers: {
        'X-API-Key': SOCIAVAULT_API_KEY
      }
    });

    const posts = response.data;

    // Filter for UGC (not branded accounts)
    const ugcPosts = posts.filter(post => {
      // Exclude posts from your own account
      const isOwnAccount = post.authorUsername === 'yourbrandhandle';
      
      // Exclude verified accounts (likely influencers or partners, not customers)
      const isVerified = post.authorVerified;
      
      // Exclude accounts with >100k followers (not typical customers)
      const isMegaInfluencer = post.authorFollowers > 100000;
      
      return !isOwnAccount && !isVerified && !isMegaInfluencer;
    });

    console.log(`Found ${ugcPosts.length} UGC posts with ${hashtag}`);

    return ugcPosts;

  } catch (error) {
    console.error('Error collecting hashtag UGC:', error.message);
    throw error;
  }
}

// Usage
const brandHashtags = [
  '#yourbrand',
  '#yourbrandreview',
  '#yourbrandunboxing',
  '#yourbrandfam'
];

for (const hashtag of brandHashtags) {
  const instagramUGC = await collectHashtagUGC(hashtag, 'instagram');
  const tiktokUGC = await collectHashtagUGC(hashtag, 'tiktok');
  
  console.log(`Instagram: ${instagramUGC.length} posts`);
  console.log(`TikTok: ${tiktokUGC.length} posts`);
  
  // Store for later processing
  await saveUGCPosts(instagramUGC, 'instagram', hashtag);
  await saveUGCPosts(tiktokUGC, 'tiktok', hashtag);
  
  await new Promise(resolve => setTimeout(resolve, 3000));
}

Python Example

import requests
import os

SOCIAVAULT_API_KEY = os.getenv('SOCIAVAULT_API_KEY')
BASE_URL = 'https://api.sociavault.com'

def collect_hashtag_ugc(hashtag, platform='instagram'):
    """Collect UGC from a hashtag"""
    
    endpoint = f'{BASE_URL}/api/scrape/{platform}/hashtag'
    
    response = requests.get(
        endpoint,
        params={
            'hashtag': hashtag.replace('#', ''),
            'limit': 100
        },
        headers={'X-API-Key': SOCIAVAULT_API_KEY}
    )
    response.raise_for_status()
    posts = response.json()
    
    # Filter for UGC (not branded accounts or mega-influencers)
    ugc_posts = []
    for post in posts:
        is_own_account = post.get('authorUsername') == 'yourbrandhandle'
        is_verified = post.get('authorVerified', False)
        is_mega_influencer = post.get('authorFollowers', 0) > 100000
        
        if not is_own_account and not is_verified and not is_mega_influencer:
            ugc_posts.append(post)
    
    print(f"Found {len(ugc_posts)} UGC posts with {hashtag}")
    
    return ugc_posts

# Usage
brand_hashtags = [
    '#yourbrand',
    '#yourbrandreview',
    '#yourbrandunboxing',
    '#yourbrandfam'
]

all_ugc = []

for hashtag in brand_hashtags:
    instagram_ugc = collect_hashtag_ugc(hashtag, 'instagram')
    tiktok_ugc = collect_hashtag_ugc(hashtag, 'tiktok')
    
    all_ugc.extend(instagram_ugc)
    all_ugc.extend(tiktok_ugc)
    
    print(f"Instagram: {len(instagram_ugc)} posts")
    print(f"TikTok: {len(tiktok_ugc)} posts")
    print()
    
    time.sleep(3)

print(f"\nTotal UGC collected: {len(all_ugc)}")

Cost: 1 credit per hashtag search

Method 2: Brand Mention Detection

Find posts that mention your brand in captions without using hashtags.

async function searchBrandMentions(brandName, platform = 'instagram') {
  try {
    const response = await axios.get(`${BASE_URL}/api/scrape/${platform}/search-keyword`, {
      params: {
        keyword: brandName,
        limit: 100
      },
      headers: {
        'X-API-Key': SOCIAVAULT_API_KEY
      }
    });

    const posts = response.data;

    // Filter for posts that actually mention your brand in text
    const mentions = posts.filter(post => {
      const text = (post.text || post.caption || '').toLowerCase();
      const brandLower = brandName.toLowerCase();
      
      return text.includes(brandLower);
    });

    // Filter out your own posts and mega-influencers
    const ugcMentions = mentions.filter(post => {
      return post.authorUsername !== 'yourbrandhandle' &&
             post.authorFollowers < 100000;
    });

    console.log(`Found ${ugcMentions.length} brand mentions`);

    return ugcMentions;

  } catch (error) {
    console.error('Error searching brand mentions:', error.message);
    throw error;
  }
}

// Search for variations of your brand name
const brandVariations = [
  'YourBrand',
  'Your Brand',
  '@yourbrand',
  'yourbrand.com'
];

let allMentions = [];

for (const variation of brandVariations) {
  const instagramMentions = await searchBrandMentions(variation, 'instagram');
  const tiktokMentions = await searchBrandMentions(variation, 'tiktok');
  
  allMentions.push(...instagramMentions, ...tiktokMentions);
  
  await new Promise(resolve => setTimeout(resolve, 3000));
}

// Remove duplicates
const uniqueMentions = Array.from(
  new Map(allMentions.map(m => [m.id, m])).values()
);

console.log(`\nTotal unique mentions: ${uniqueMentions.length}`);

This finds:

  • Captions like "Just got my YourBrand water bottle!"
  • Reviews like "Tried @yourbrand and it's amazing"
  • Unboxings mentioning your brand but no hashtag

Method 3: Visual Product Detection (Advanced)

The holy grail: Finding UGC where customers don't mention your brand name OR use hashtags, but your product is visible in the image/video.

How it works:

  1. Collect posts from related hashtags (e.g., #waterbottle for water bottle brands)
  2. Analyze images/videos to detect your product
  3. Match based on visual features (colors, logos, shapes)
// This requires computer vision, but here's the workflow:

async function findUntaggedProductUGC(relatedHashtag, productImages) {
  // Step 1: Collect posts from related category hashtags
  const posts = await collectHashtagUGC(relatedHashtag);

  const potentialUGC = [];

  for (const post of posts) {
    // Step 2: Download post image/video thumbnail
    const postImage = await downloadImage(post.thumbnailUrl);

    // Step 3: Compare with your product images using visual similarity
    // (You'd use a computer vision API like Google Vision, AWS Rekognition, or open-source)
    const similarity = await compareImages(postImage, productImages);

    if (similarity > 0.75) { // 75% visual match
      potentialUGC.push({
        ...post,
        similarity,
        matchedProduct: true
      });
    }
  }

  console.log(`Found ${potentialUGC.length} posts with visual product match`);

  return potentialUGC;
}

// Usage
const relatedHashtags = [
  '#waterbottle',      // Category hashtag
  '#hydration',        // Related lifestyle
  '#fitness',          // Related use case
  '#gymessentials'     // Related context
];

// Your product reference images
const productImages = [
  './product-images/bottle-front.jpg',
  './product-images/bottle-side.jpg',
  './product-images/bottle-logo.jpg'
];

for (const hashtag of relatedHashtags) {
  const untaggedUGC = await findUntaggedProductUGC(hashtag, productImages);
  
  // These are customers using your product without tagging you
  await saveUGCPosts(untaggedUGC, 'instagram', `visual_match_${hashtag}`);
}

What this finds:

  • Gym selfies showing your water bottle in the background
  • Desk setup photos with your product visible
  • Lifestyle shots featuring your product (no mention)

This is the 80% of UGC you're missing.

UGC Quality Scoring

Not all UGC is created equal. Let's automatically score content quality:

function scoreUGCQuality(post) {
  const score = {
    total: 0,
    factors: {}
  };

  // Factor 1: Engagement (40 points)
  const engagementRate = ((post.likeCount + post.commentCount * 2) / post.authorFollowers) * 100;

  if (engagementRate > 10) score.factors.engagement = 40;
  else if (engagementRate > 5) score.factors.engagement = 30;
  else if (engagementRate > 3) score.factors.engagement = 20;
  else score.factors.engagement = 10;

  // Factor 2: Content quality (30 points)
  const hasHighResImage = post.imageWidth >= 1080; // HD quality
  const hasCaption = post.caption && post.caption.length > 20;
  const hasMultipleImages = (post.images || []).length > 1;

  let qualityScore = 0;
  if (hasHighResImage) qualityScore += 15;
  if (hasCaption) qualityScore += 10;
  if (hasMultipleImages) qualityScore += 5;

  score.factors.quality = qualityScore;

  // Factor 3: Creator credibility (20 points)
  const followerCount = post.authorFollowers;

  if (followerCount >= 1000 && followerCount <= 10000) {
    // Micro-influencer sweet spot
    score.factors.credibility = 20;
  } else if (followerCount >= 500 && followerCount < 1000) {
    score.factors.credibility = 15;
  } else if (followerCount < 500) {
    score.factors.credibility = 10; // Too small
  } else {
    score.factors.credibility = 5; // Too large (expensive to license)
  }

  // Factor 4: Sentiment (10 points)
  // Analyze caption for positive/negative words
  const positiveWords = ['love', 'amazing', 'best', 'obsessed', 'perfect', 'recommend'];
  const negativeWords = ['bad', 'worst', 'hate', 'terrible', 'disappointed'];

  const caption = (post.caption || '').toLowerCase();

  const positiveMatches = positiveWords.filter(word => caption.includes(word)).length;
  const negativeMatches = negativeWords.filter(word => caption.includes(word)).length;

  if (positiveMatches > 0 && negativeMatches === 0) {
    score.factors.sentiment = 10;
  } else if (positiveMatches > negativeMatches) {
    score.factors.sentiment = 7;
  } else {
    score.factors.sentiment = 3;
  }

  // Calculate total
  score.total = Object.values(score.factors).reduce((sum, val) => sum + val, 0);

  return score;
}

// Usage
for (const post of allUGC) {
  const qualityScore = scoreUGCQuality(post);

  console.log(`\nPost by @${post.authorUsername}`);
  console.log(`Quality Score: ${qualityScore.total}/100`);
  console.log(`  Engagement: ${qualityScore.factors.engagement}/40`);
  console.log(`  Content Quality: ${qualityScore.factors.quality}/30`);
  console.log(`  Credibility: ${qualityScore.factors.credibility}/20`);
  console.log(`  Sentiment: ${qualityScore.factors.sentiment}/10`);

  if (qualityScore.total >= 70) {
    console.log(`✅ HIGH QUALITY - Reach out for licensing`);
  } else if (qualityScore.total >= 50) {
    console.log(`🤔 Good - Consider licensing`);
  } else {
    console.log(`❌ Low quality - Skip`);
  }
}

Quality score interpretation:

  • 80-100: Exceptional content (license immediately)
  • 60-79: High quality (great for content calendar)
  • 40-59: Decent (use as backup content)
  • 0-39: Skip (low engagement or quality)

Database Schema for UGC Management

Let's build a system to manage your UGC library:

-- UGC posts
CREATE TABLE ugc_posts (
  id SERIAL PRIMARY KEY,
  platform VARCHAR(50) NOT NULL,
  post_id VARCHAR(100) UNIQUE,
  post_url VARCHAR(500),
  author_username VARCHAR(255),
  author_url VARCHAR(500),
  author_followers INTEGER,
  caption TEXT,
  hashtags TEXT[],
  media_type VARCHAR(50), -- 'photo', 'video', 'carousel'
  media_url VARCHAR(500),
  thumbnail_url VARCHAR(500),
  like_count INTEGER,
  comment_count INTEGER,
  view_count INTEGER,
  posted_at TIMESTAMP,
  discovered_at TIMESTAMP DEFAULT NOW(),
  discovery_method VARCHAR(100), -- 'hashtag', 'mention', 'visual_match'
  quality_score INTEGER,
  engagement_score INTEGER,
  sentiment VARCHAR(20), -- 'positive', 'neutral', 'negative'
  status VARCHAR(50) DEFAULT 'pending', -- 'pending', 'approved', 'licensed', 'rejected'
  license_status VARCHAR(50), -- 'unlicensed', 'requested', 'licensed', 'declined'
  license_fee DECIMAL(10, 2),
  licensed_at TIMESTAMP,
  usage_rights TEXT, -- What you can do with it
  notes TEXT
);

-- Creator contacts
CREATE TABLE ugc_creators (
  id SERIAL PRIMARY KEY,
  username VARCHAR(255) UNIQUE,
  platform VARCHAR(50),
  email VARCHAR(255),
  contact_status VARCHAR(50) DEFAULT 'not_contacted',
  last_contacted TIMESTAMP,
  response_rate DECIMAL(5, 2), -- Track if they respond
  total_posts_created INTEGER DEFAULT 0,
  total_licensed INTEGER DEFAULT 0,
  avg_license_fee DECIMAL(10, 2),
  notes TEXT
);

-- Outreach tracking
CREATE TABLE ugc_outreach (
  id SERIAL PRIMARY KEY,
  post_id INTEGER REFERENCES ugc_posts(id),
  creator_id INTEGER REFERENCES ugc_creators(id),
  contacted_at TIMESTAMP DEFAULT NOW(),
  contact_method VARCHAR(50), -- 'dm', 'email', 'comment'
  message_sent TEXT,
  response_received BOOLEAN DEFAULT FALSE,
  response_text TEXT,
  responded_at TIMESTAMP,
  outcome VARCHAR(50) -- 'licensed', 'declined', 'no_response'
);

CREATE INDEX idx_ugc_status ON ugc_posts(status);
CREATE INDEX idx_ugc_quality ON ugc_posts(quality_score DESC);
CREATE INDEX idx_ugc_discovered ON ugc_posts(discovered_at DESC);
CREATE INDEX idx_license_status ON ugc_posts(license_status);

Automated UGC Collection System

Let's build a complete system that runs daily:

const { Pool } = require('pg');
const cron = require('node-cron');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

async function dailyUGCCollection() {
  console.log('🔍 Starting daily UGC collection...');

  // Your brand hashtags
  const hashtags = [
    '#yourbrand',
    '#yourbrandreview',
    '#yourbrandunboxing',
    '#yourbrandfam'
  ];

  // Brand name variations
  const brandMentions = [
    'YourBrand',
    'Your Brand',
    '@yourbrand'
  ];

  // Related category hashtags (for visual detection)
  const categoryHashtags = [
    '#waterbottle',
    '#hydration',
    '#fitness'
  ];

  let totalCollected = 0;

  // Step 1: Collect from branded hashtags
  for (const hashtag of hashtags) {
    try {
      console.log(`Collecting from ${hashtag}...`);

      const instagramPosts = await collectHashtagUGC(hashtag, 'instagram');
      const tiktokPosts = await collectHashtagUGC(hashtag, 'tiktok');

      await saveAndScoreUGC(instagramPosts, 'instagram', 'hashtag', hashtag);
      await saveAndScoreUGC(tiktokPosts, 'tiktok', 'hashtag', hashtag);

      totalCollected += instagramPosts.length + tiktokPosts.length;

      await new Promise(resolve => setTimeout(resolve, 3000));

    } catch (error) {
      console.error(`Error collecting ${hashtag}:`, error.message);
    }
  }

  // Step 2: Collect brand mentions
  for (const mention of brandMentions) {
    try {
      console.log(`Searching for "${mention}"...`);

      const instagramMentions = await searchBrandMentions(mention, 'instagram');
      const tiktokMentions = await searchBrandMentions(mention, 'tiktok');

      await saveAndScoreUGC(instagramMentions, 'instagram', 'mention', mention);
      await saveAndScoreUGC(tiktokMentions, 'tiktok', 'mention', mention);

      totalCollected += instagramMentions.length + tiktokMentions.length;

      await new Promise(resolve => setTimeout(resolve, 3000));

    } catch (error) {
      console.error(`Error searching "${mention}":`, error.message);
    }
  }

  console.log(`✅ Collected ${totalCollected} UGC posts`);

  // Step 3: Identify high-quality unlicensed content
  const { rows: highQualityUGC } = await pool.query(`
    SELECT *
    FROM ugc_posts
    WHERE quality_score >= 70
      AND license_status = 'unlicensed'
      AND discovered_at >= NOW() - INTERVAL '7 days'
    ORDER BY quality_score DESC
    LIMIT 20
  `);

  console.log(`Found ${highQualityUGC.length} high-quality posts to license`);

  // Send notification
  await sendUGCNotification(highQualityUGC);

  console.log('Daily UGC collection complete!');
}

async function saveAndScoreUGC(posts, platform, discoveryMethod, source) {
  for (const post of posts) {
    try {
      // Calculate quality score
      const qualityScore = scoreUGCQuality(post);

      // Save to database
      await pool.query(`
        INSERT INTO ugc_posts (
          platform, post_id, post_url, author_username, author_url,
          author_followers, caption, hashtags, media_type, media_url,
          thumbnail_url, like_count, comment_count, view_count,
          posted_at, discovery_method, quality_score, engagement_score,
          sentiment
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
        ON CONFLICT (post_id) DO UPDATE SET
          like_count = EXCLUDED.like_count,
          comment_count = EXCLUDED.comment_count,
          view_count = EXCLUDED.view_count,
          quality_score = EXCLUDED.quality_score
      `, [
        platform,
        post.id,
        post.url,
        post.authorUsername,
        post.authorUrl,
        post.authorFollowers,
        post.caption || post.text,
        post.hashtags || [],
        post.type || 'photo',
        post.videoUrl || post.imageUrl,
        post.thumbnailUrl,
        post.likeCount,
        post.commentCount,
        post.viewCount || null,
        post.timestamp ? new Date(post.timestamp * 1000) : null,
        discoveryMethod,
        qualityScore.total,
        qualityScore.factors.engagement,
        qualityScore.factors.sentiment > 7 ? 'positive' : 'neutral'
      ]);

      console.log(`✅ Saved UGC from @${post.authorUsername} (score: ${qualityScore.total})`);

    } catch (error) {
      console.error('Error saving UGC:', error.message);
    }
  }
}

async function sendUGCNotification(highQualityPosts) {
  if (highQualityPosts.length === 0) return;

  const nodemailer = require('nodemailer');
  const transporter = nodemailer.createTransport({
    host: process.env.SMTP_HOST,
    port: 587,
    auth: {
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS
    }
  });

  let emailBody = '<h2>🎉 New High-Quality UGC Found!</h2>';
  emailBody += `<p>Found ${highQualityPosts.length} pieces of high-quality UGC today. Consider reaching out for licensing.</p>`;

  highQualityPosts.forEach((post, i) => {
    emailBody += `
      <div style="margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 8px;">
        <h3>${i + 1}. Quality Score: ${post.quality_score}/100</h3>
        <p><strong>Creator:</strong> @${post.author_username} (${post.author_followers.toLocaleString()} followers)</p>
        <p><strong>Platform:</strong> ${post.platform}</p>
        <p><strong>Engagement:</strong> ${post.like_count.toLocaleString()} likes, ${post.comment_count} comments</p>
        <p><strong>Caption:</strong> ${(post.caption || '').substring(0, 150)}...</p>
        <p><a href="${post.post_url}" style="background: #3b82f6; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">View Post</a></p>
      </div>
    `;
  });

  await transporter.sendMail({
    from: '"UGC Scout" <ugc@yourbrand.com>',
    to: process.env.NOTIFICATION_EMAIL,
    subject: `🎉 ${highQualityPosts.length} High-Quality UGC Posts Found`,
    html: emailBody
  });

  console.log('✅ Sent UGC notification email');
}

// Run daily at 8 AM
cron.schedule('0 8 * * *', dailyUGCCollection);

// Run immediately on start (for testing)
if (require.main === module) {
  dailyUGCCollection();
}

What this system does:

  1. Runs every morning at 8 AM
  2. Collects UGC from all your hashtags and brand mentions
  3. Scores quality automatically
  4. Saves to database with tracking
  5. Identifies top 20 high-quality posts
  6. Emails you daily digest with links

Your morning routine:

  • Check email
  • Review 20 high-quality UGC posts
  • Pick 5-10 to reach out to for licensing
  • 15 minutes total

Outreach & Licensing Automation

Once you find great UGC, you need to license it. Let's automate the outreach:

async function reachOutForLicensing(postId) {
  // Get post details
  const { rows: [post] } = await pool.query(`
    SELECT * FROM ugc_posts WHERE id = $1
  `, [postId]);

  if (!post) {
    console.log('Post not found');
    return;
  }

  // Check if already contacted
  const { rows: existingOutreach } = await pool.query(`
    SELECT * FROM ugc_outreach WHERE post_id = $1
  `, [postId]);

  if (existingOutreach.length > 0) {
    console.log('Already contacted this creator');
    return;
  }

  // Generate personalized message
  const message = generateLicensingMessage(post);

  // Send DM (you'd use Instagram/TikTok API or manually)
  console.log(`\n📧 Message to send to @${post.author_username}:`);
  console.log(message);
  console.log(`\nPost URL: ${post.post_url}`);

  // Track outreach
  await pool.query(`
    INSERT INTO ugc_outreach (post_id, contacted_at, contact_method, message_sent)
    VALUES ($1, NOW(), 'dm', $2)
  `, [postId, message]);

  // Update post status
  await pool.query(`
    UPDATE ugc_posts
    SET license_status = 'requested'
    WHERE id = $1
  `, [postId]);

  console.log('✅ Outreach tracked');
}

function generateLicensingMessage(post) {
  return `
Hey ${post.author_username}! 👋

We LOVE your post featuring our product! The [specific thing you like] is amazing.

We'd love to feature your content on our Instagram and website. Would you be open to us resharing your post? We'll credit you and send you a $[amount] gift card or [free product] as a thank you!

Let us know if you're interested! 🎉

- [Your Name]
  [Your Brand] Team
  `.trim();
}

// Batch outreach to top posts
async function batchOutreach(limit = 10) {
  const { rows: topPosts } = await pool.query(`
    SELECT *
    FROM ugc_posts
    WHERE quality_score >= 70
      AND license_status = 'unlicensed'
      AND status = 'approved'
    ORDER BY quality_score DESC
    LIMIT $1
  `, [limit]);

  console.log(`Reaching out to ${topPosts.length} creators...`);

  for (const post of topPosts) {
    await reachOutForLicensing(post.id);
    
    // Don't spam - space out messages
    await new Promise(resolve => setTimeout(resolve, 5000));
  }

  console.log('Batch outreach complete!');
}

// Run batch outreach
batchOutreach(10);

UGC Dashboard

Build a simple dashboard to review and approve UGC:

const express = require('express');
const app = express();

// List all pending UGC
app.get('/ugc/pending', async (req, res) => {
  const { rows } = await pool.query(`
    SELECT *
    FROM ugc_posts
    WHERE status = 'pending'
    ORDER BY quality_score DESC, discovered_at DESC
    LIMIT 50
  `);

  res.json({ posts: rows });
});

// Approve UGC for licensing
app.post('/ugc/:id/approve', async (req, res) => {
  const postId = req.params.id;

  await pool.query(`
    UPDATE ugc_posts
    SET status = 'approved'
    WHERE id = $1
  `, [postId]);

  res.json({ success: true, message: 'Post approved' });
});

// Mark as licensed
app.post('/ugc/:id/license', async (req, res) => {
  const postId = req.params.id;
  const { fee, rights } = req.body;

  await pool.query(`
    UPDATE ugc_posts
    SET
      license_status = 'licensed',
      licensed_at = NOW(),
      license_fee = $1,
      usage_rights = $2
    WHERE id = $3
  `, [fee, rights, postId]);

  res.json({ success: true, message: 'Post marked as licensed' });
});

// Get licensed content for posting
app.get('/ugc/licensed', async (req, res) => {
  const { rows } = await pool.query(`
    SELECT *
    FROM ugc_posts
    WHERE license_status = 'licensed'
      AND status = 'approved'
    ORDER BY licensed_at DESC
  `);

  res.json({ posts: rows });
});

// Stats dashboard
app.get('/ugc/stats', async (req, res) => {
  const { rows: [stats] } = await pool.query(`
    SELECT
      COUNT(*) AS total_ugc,
      COUNT(*) FILTER (WHERE license_status = 'licensed') AS licensed_count,
      COUNT(*) FILTER (WHERE quality_score >= 70) AS high_quality_count,
      AVG(quality_score) AS avg_quality_score,
      SUM(license_fee) AS total_licensing_cost
    FROM ugc_posts
  `);

  res.json(stats);
});

app.listen(3000, () => {
  console.log('UGC Dashboard running on http://localhost:3000');
});

UGC Licensing Best Practices

1. Always Get Permission

Never repost UGC without permission. It's:

  • Against platform TOS
  • Legally risky (copyright infringement)
  • Bad PR if they call you out

Always:

  • DM the creator
  • Get explicit permission
  • Document the agreement

2. Offer Fair Compensation

Options:

  • Gift card ($25-100 depending on follower count)
  • Free products ($50-200 value)
  • Discount code (20-50% off)
  • Cash payment ($50-500 for micro-influencers)

Don't be cheap. $50 for great content is a bargain compared to a $5,000 photoshoot.

3. Clear Usage Rights

Specify:

  • Where you'll post (Instagram, website, ads?)
  • How long (1 year? Forever?)
  • Modifications (can you crop, add text?)

Example: "We'd like to reshare your post on our Instagram, website, and in ads. We'll credit you as @username. This will be ongoing (no time limit). Sound good?"

4. Credit the Creator

Always:

  • Tag them when reposting
  • Credit in caption ("📷: @username")
  • Link to their profile on website

Benefits:

  • Good karma
  • Creator might promote you to their followers
  • Other creators see you credit and want to create for you

5. Build Relationships

Best UGC creators:

  • Create multiple pieces over time
  • Engage with your brand regularly
  • Refer friends

Nurture them:

  • Send thank you notes
  • Give them early access to new products
  • Feature them in "Customer Spotlight"
  • Build ambassador program

UGC Content Calendar

Use your UGC library to fill your content calendar forever:

async function generateUGCContentCalendar(weeks = 4) {
  // Get licensed UGC
  const { rows: licensedUGC } = await pool.query(`
    SELECT *
    FROM ugc_posts
    WHERE license_status = 'licensed'
      AND status = 'approved'
    ORDER BY quality_score DESC
  `);

  if (licensedUGC.length === 0) {
    console.log('No licensed UGC available');
    return;
  }

  // Calculate posts needed (posting 3x per week)
  const postsPerWeek = 3;
  const totalPosts = weeks * postsPerWeek;

  // Create calendar
  const calendar = [];
  const startDate = new Date();

  for (let i = 0; i < totalPosts; i++) {
    // Rotate through licensed UGC
    const post = licensedUGC[i % licensedUGC.length];

    // Calculate post date (Mon, Wed, Fri)
    const daysToAdd = Math.floor(i / 3) * 7 + (i % 3) * 2;
    const postDate = new Date(startDate);
    postDate.setDate(postDate.getDate() + daysToAdd);

    calendar.push({
      date: postDate.toISOString().split('T')[0],
      post: {
        image: post.media_url,
        caption: post.caption,
        credit: `📷: @${post.author_username}`,
        platform: post.platform
      }
    });
  }

  console.log(`\n📅 UGC Content Calendar (${weeks} weeks):\n`);

  calendar.forEach((item, i) => {
    console.log(`${i + 1}. ${item.date} - ${item.post.platform.toUpperCase()} post by @${item.post.credit}`);
  });

  return calendar;
}

// Generate 4-week calendar
const calendar = await generateUGCContentCalendar(4);

Result: Never run out of content. Your customers create it for you.

Measuring UGC ROI

Track the business impact of your UGC strategy:

-- UGC performance tracking
CREATE TABLE ugc_post_performance (
  id SERIAL PRIMARY KEY,
  post_id INTEGER REFERENCES ugc_posts(id),
  reposted_at TIMESTAMP,
  platform_reposted VARCHAR(50),
  reach INTEGER,
  impressions INTEGER,
  engagement INTEGER,
  clicks INTEGER,
  conversions INTEGER,
  revenue DECIMAL(10, 2)
);

-- Calculate ROI
SELECT
  COUNT(*) AS total_ugc_licensed,
  SUM(license_fee) AS total_licensing_cost,
  SUM(p.revenue) AS total_revenue,
  (SUM(p.revenue) - SUM(u.license_fee)) / SUM(u.license_fee) * 100 AS roi_percentage
FROM ugc_posts u
LEFT JOIN ugc_post_performance p ON p.post_id = u.id
WHERE u.license_status = 'licensed';

Track:

  • Cost per UGC post (licensing fees)
  • Engagement rate (vs branded content)
  • Conversion rate (vs branded content)
  • Revenue attributed to UGC
  • ROI (revenue / cost)

Typical results:

  • UGC engagement: 5-10% (vs 1-2% branded)
  • UGC conversion: 4-6% (vs 0.5-1% branded)
  • UGC ROI: 400-800% (vs 50-150% branded)

Conclusion

Your customers are creating thousands of pieces of content about your brand every month.

Stop missing it. Stop paying $5,000 for photoshoots that convert worse than a customer's iPhone video.

This guide gave you:

  • Hashtag monitoring (find tagged UGC)
  • Brand mention detection (find untagged captions)
  • Visual product detection (find UGC without any mention)
  • Quality scoring (automatically rank content)
  • Automated collection system (runs daily, finds everything)
  • Licensing workflow (track outreach, permissions, usage rights)
  • Content calendar (fill it forever with UGC)

The result:

  • 10x more content discovered
  • 90% lower production costs
  • 3-4x better engagement
  • Never-ending content supply

Investment:

  • 8-12 hours setup (one-time)
  • 15 minutes/day review (pick top UGC to license)
  • $200-500/month licensing fees (vs $10,000+ photoshoots)

ROI: 95% cost savings + 3-4x better performance

Every day you wait, hundreds of customers are posting about your brand and you're missing it.

Start collecting UGC today. Your content calendar will never be empty again.

Get started: sociavault.com - Get your API key and build your UGC collection system this weekend.

Your customers are creating your marketing. You just need to find it.

Found this helpful?

Share it with others who might benefit

Ready to Try SociaVault?

Start extracting social media data with our powerful API