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:
- Search for brand hashtag → Find 10-20 posts
- Check @mentions → Find 5-10 more posts
- Manually DM creators → 50% never respond
- 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:
- Collect posts from related hashtags (e.g., #waterbottle for water bottle brands)
- Analyze images/videos to detect your product
- 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:
- Runs every morning at 8 AM
- Collects UGC from all your hashtags and brand mentions
- Scores quality automatically
- Saves to database with tracking
- Identifies top 20 high-quality posts
- 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