Overview
Polls tracked campaign hashtags every 6 hours, records video count and engagement totals, calculates growth velocity, and flags viral moments when video count or engagement grows more than 50% in a 6-hour window.
Trigger
Every 6 hours (cron: 0 */6 * * *)
Schedule
Every 6 hours (cron: 0 */6 * * *)
Workflow Steps
Fetch hashtag stats
POST to Scavio TikTok hashtag endpoint for each tracked hashtag. Extract video_count and total_view_count from the response.
Store snapshot
Write hashtag, video_count, view_count, and timestamp to a snapshots table.
Calculate growth velocity
Compare current video_count and view_count against the snapshot from 6 hours ago. Calculate new_videos and new_views in the window.
Detect viral threshold
Flag if new_videos in the window exceeds a threshold (e.g., 200 new videos in 6 hours for a mid-size campaign). Thresholds are configurable per hashtag.
Alert on viral detection
Send Slack or webhook alert with hashtag name, new video count, view velocity (views per hour), and a TikTok link to the hashtag page.
Python Implementation
import sqlite3
import requests
from datetime import datetime, timedelta
import time
DB_PATH = "tiktok_campaigns.db"
SCRAVIO_KEY = "YOUR_API_KEY"
API_BASE = "https://api.scavio.dev/api/v1/tiktok"
HEADERS = {"Authorization": f"Bearer {SCRAVIO_KEY}"}
ALERT_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK"
VIRAL_VIDEO_THRESHOLD = 200 # new videos per 6h window
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.executescript("""
CREATE TABLE IF NOT EXISTS hashtags (hashtag TEXT PRIMARY KEY, viral_threshold INTEGER DEFAULT 200);
CREATE TABLE IF NOT EXISTS snapshots (
hashtag TEXT, video_count INTEGER, view_count INTEGER, ts TEXT
);
""")
conn.commit()
return conn
def fetch_hashtag_stats(hashtag: str) -> dict:
resp = requests.post(
f"{API_BASE}/hashtag/info",
headers=HEADERS,
json={"hashtag": hashtag}
)
resp.raise_for_status()
data = resp.json().get("data", {})
return {
"video_count": data.get("videoCount", 0),
"view_count": data.get("viewCount", 0)
}
def run():
conn = init_db()
now = datetime.utcnow().isoformat()
six_hours_ago = (datetime.utcnow() - timedelta(hours=6)).isoformat()
hashtags = conn.execute("SELECT hashtag, viral_threshold FROM hashtags").fetchall()
for hashtag, threshold in hashtags:
stats = fetch_hashtag_stats(hashtag)
conn.execute("INSERT INTO snapshots VALUES (?,?,?,?)",
(hashtag, stats["video_count"], stats["view_count"], now))
prev = conn.execute(
"SELECT video_count, view_count FROM snapshots "
"WHERE hashtag=? AND ts <= ? ORDER BY ts DESC LIMIT 1",
(hashtag, six_hours_ago)
).fetchone()
if prev:
new_videos = stats["video_count"] - prev[0]
new_views = stats["view_count"] - prev[1]
views_per_hour = new_views // 6
if new_videos >= (threshold or VIRAL_VIDEO_THRESHOLD):
requests.post(ALERT_WEBHOOK, json={
"text": f"Viral alert #{hashtag}: +{new_videos} videos in 6h, +{views_per_hour:,} views/hr. https://tiktok.com/tag/{hashtag}"
})
conn.commit()
time.sleep(0.5)
if __name__ == "__main__":
run()
JavaScript Implementation
const Database = require('better-sqlite3');
const fetch = require('node-fetch');
const DB_PATH = 'tiktok_campaigns.db';
const API_BASE = 'https://api.scavio.dev/api/v1/tiktok';
const SCRAVIO_KEY = 'YOUR_API_KEY';
const HEADERS = { 'Authorization': `Bearer ${SCRAVIO_KEY}`, 'Content-Type': 'application/json' };
const ALERT_WEBHOOK = 'https://hooks.slack.com/services/YOUR/WEBHOOK';
const db = new Database(DB_PATH);
db.exec(`
CREATE TABLE IF NOT EXISTS hashtags (hashtag TEXT PRIMARY KEY, viral_threshold INTEGER DEFAULT 200);
CREATE TABLE IF NOT EXISTS snapshots (hashtag TEXT, video_count INTEGER, view_count INTEGER, ts TEXT);
`);
async function fetchHashtagStats(hashtag) {
const res = await fetch(`${API_BASE}/hashtag/info`, {
method: 'POST', headers: HEADERS,
body: JSON.stringify({ hashtag })
});
const data = (await res.json()).data || {};
return { video_count: data.videoCount || 0, view_count: data.viewCount || 0 };
}
async function run() {
const now = new Date().toISOString();
const sixHoursAgo = new Date(Date.now() - 6 * 3600000).toISOString();
const hashtags = db.prepare('SELECT hashtag, viral_threshold FROM hashtags').all();
for (const { hashtag, viral_threshold } of hashtags) {
const stats = await fetchHashtagStats(hashtag);
db.prepare('INSERT INTO snapshots VALUES (?,?,?,?)').run(hashtag, stats.video_count, stats.view_count, now);
const prev = db.prepare(
'SELECT video_count, view_count FROM snapshots WHERE hashtag=? AND ts <= ? ORDER BY ts DESC LIMIT 1'
).get(hashtag, sixHoursAgo);
if (prev) {
const newVideos = stats.video_count - prev.video_count;
const newViews = stats.view_count - prev.view_count;
if (newVideos >= (viral_threshold || 200)) {
await fetch(ALERT_WEBHOOK, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: `Viral alert #${hashtag}: +${newVideos} videos, +${Math.round(newViews/6).toLocaleString()} views/hr. https://tiktok.com/tag/${hashtag}` })
});
}
}
await new Promise(r => setTimeout(r, 500));
}
}
run().catch(console.error);
Platforms Used
TikTok
Trending video, creator, and product discovery