{"ok":true,"data":[{"slug":"webhook-signature-verify","title":"Verify the X-Varosity-Signature header","description":"Every webhook delivery is HMAC-SHA256 signed. Reject requests where the signature doesn't match or the timestamp is stale.","surface":"settings-webhooks","trigger":"first-use","priority":10,"tags":["webhooks","security","agents"],"body":"\nEvery Varosity webhook POST includes two headers you must verify:\n\n- **`X-Varosity-Timestamp`** — unix seconds at send time.\n- **`X-Varosity-Signature`** — `sha256=<hex>`, computed over `<timestamp>.<raw body>` with your webhook secret (shown exactly once at creation).\n\nReject the delivery if the signature doesn't match OR the timestamp is more than 5 minutes old (replay-protection).\n\n### Node (Express)\n\n```js\nimport crypto from \"node:crypto\";\n\nconst SECRET = process.env.VAROSITY_WEBHOOK_SECRET; // from /settings/webhooks\n\napp.post(\"/incoming\", express.raw({ type: \"application/json\" }), (req, res) => {\n  const ts  = Number(req.get(\"X-Varosity-Timestamp\"));\n  const sig = req.get(\"X-Varosity-Signature\");\n  if (!ts || !sig) return res.sendStatus(400);\n  if (Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) return res.sendStatus(401);\n\n  const expected =\n    \"sha256=\" +\n    crypto.createHmac(\"sha256\", SECRET)\n          .update(`${ts}.${req.body.toString(\"utf8\")}`)\n          .digest(\"hex\");\n  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {\n    return res.sendStatus(401);\n  }\n  res.sendStatus(200);\n  // req.body is now a trusted JSON string — parse and handle.\n});\n```\n\n### Python (Flask)\n\n```python\nimport hashlib, hmac, os, time\nfrom flask import Flask, request, abort\n\nSECRET = os.environ[\"VAROSITY_WEBHOOK_SECRET\"].encode()\napp = Flask(__name__)\n\n@app.post(\"/incoming\")\ndef incoming():\n    ts  = request.headers.get(\"X-Varosity-Timestamp\", \"\")\n    sig = request.headers.get(\"X-Varosity-Signature\", \"\")\n    if not ts or not sig: abort(400)\n    try: ts_i = int(ts)\n    except ValueError: abort(400)\n    if abs(int(time.time()) - ts_i) > 300: abort(401)\n\n    body = request.get_data(as_text=True)\n    expected = \"sha256=\" + hmac.new(\n        SECRET, f\"{ts}.{body}\".encode(), hashlib.sha256\n    ).hexdigest()\n    if not hmac.compare_digest(expected, sig): abort(401)\n    return \"\", 200\n```\n\nAlways use a **constant-time** compare (`timingSafeEqual` / `compare_digest`) — direct `==` leaks timing and is exploitable.\n"},{"slug":"veo-structure","title":"Write Veo prompts in three beats","description":"Veo 3.1 responds best to prompts structured as subject → action → setting, each a separate clause.","surface":"storyboard-canvas","model":"veo-3.1","trigger":"first-use","priority":10,"tags":["video","prompting","veo"],"body":"\nVeo 3.1 rewards specificity in this order:\n\n1. **Subject** — who or what (\"A weathered lighthouse keeper with grey stubble\")\n2. **Action** — verb in present continuous (\"raises a brass telescope to his eye\")\n3. **Setting** — place + light + weather (\"on a stormy cliff at dusk, rain slicks the stone\")\n\nJoin the clauses with commas, not periods. Add **camera direction** last: \"slow push-in, anamorphic lens, shallow depth of field.\"\n\nAvoid abstract adjectives like \"cinematic\" — the model already tries to be cinematic. Describe what's physically in the frame.\n"},{"slug":"platform-vs-byok","title":"Platform Credits vs BYOK — how to choose","description":"Platform Credits and BYOK cost the same — the provider's list price. Credits mean zero ops; BYOK keeps your provider contract. Pick per provider, not globally.","surface":"settings-keys","trigger":"first-use","priority":15,"tags":["billing","keys","setup"],"body":"\nVarosity offers two billing modes per provider. You pick one per vendor:\n\n**Platform Credits** — Varosity holds the provider account and bills you in Varosity Credits at the provider's list price (no markup).\n- No provider signup, no rate-limit management, no separate invoices.\n- Good default for early users and agents.\n- Requires a credit balance on your account.\n\n**Bring Your Own Key (BYOK)** — you paste your own fal.ai / OpenAI / Runway / ElevenLabs key.\n- The provider invoices you directly, at the same price.\n- Your rate limits, your free tier, your contract terms.\n- Good when you already have an account with that provider, want maximum control, or have negotiated your own volume pricing.\n\nYou can mix modes per provider. Most users end up Platform-for-ElevenLabs, BYOK-for-fal because fal credits last longer than they expect.\n"},{"slug":"kling-close-ups","title":"Kling 3.0 excels at dialogue close-ups","description":"For talking-head shots with synced lip motion, Kling beats Veo on consistency across a 10-second take.","surface":"storyboard-canvas","model":"kling-3.0","trigger":"always","priority":20,"tags":["video","prompting","kling","dialogue"],"body":"\nWhen your shot is a person speaking directly to camera, Kling 3.0 Pro is the default pick:\n\n- Native audio + phoneme-accurate lip sync\n- Holds facial geometry stable across the full 10s\n- Handles multi-shot cuts better than Veo when the subject reappears\n\nPrompt pattern:\n\n> \"Medium close-up of {subject}, framed shoulders-up, {emotion}, speaking to camera. Soft key light from 45°, shallow depth of field, {location} background blurred. She says: '{dialogue}'\"\n\nPut the line of dialogue in quotes at the end — Kling renders the lip motion to match.\n"},{"slug":"mixed-mode","title":"Mix billing modes per provider","description":"Use Varosity Credits for the providers you don't want to manage, BYOK for the ones where you already have an account. No global toggle.","surface":"settings-keys","trigger":"always","priority":20,"tags":["billing","keys","setup"],"body":"\nThe picker on every provider card is independent. A common setup:\n\n- **Video providers** (fal.ai, Runway) → **BYOK**. Already have an account, want the zero-markup rate.\n- **Voice providers** (ElevenLabs, Cartesia) → **Platform Credits**. Low per-shot cost; not worth a separate vendor relationship.\n- **Smart Router** → **Varosity** (always; the routing LLM runs on our account, doesn't touch your keys or credits).\n\nGenerations bill to whichever mode is active for that provider at submit-time. Switching a provider mid-project doesn't affect already-submitted jobs.\n\nIf a provider has **neither** a BYOK key set **nor** enough Varosity Credits, models from that provider grey out in the shot picker with a link to the right card in this page.\n"},{"slug":"seedance-audio-native","title":"Seedance 2.0 writes video and audio together","description":"Seedance 2.0 generates ambient + diegetic sound in the same pass — describe what you hear, not just what you see.","surface":"storyboard-canvas","model":"seedance-2.0","trigger":"first-use","priority":25,"tags":["video","prompting","seedance","audio"],"body":"\nSeedance 2.0 is audio-native — the model writes picture and sound in one forward pass, which means the soundscape is locked to the frame. In your prompt, describe **both layers**:\n\n- **Visual** — as usual: subject, action, setting\n- **Audio** — ambient bed + any diegetic sound you want to hear\n\nExample:\n\n> \"A commuter train pulls into a concrete station. Wide shot from platform level. **Sound:** the hydraulic hiss of brakes, distant announcement echo, a single pigeon flapping off.\"\n\nThe result comes back with the audio already mixed — skip the separate music/voice pass for this shot. (Seedance 1.0 Pro renders silent video; this applies to Seedance 2.0 only.)\n"},{"slug":"reference-image-weight","title":"Reference images dominate — keep prompts short","description":"When you attach a reference image, the text prompt should describe motion and change, not subject identity.","surface":"storyboard-canvas","trigger":"always","priority":30,"tags":["video","prompting","references"],"body":"\nWith a reference image attached, the model already knows what the subject looks like. A long textual re-description fights the image and muddies the output.\n\n**Do:** describe the **action** and the **camera move**.\n\n> \"Slow dolly-in on the subject as they turn their head left. Shallow depth of field.\"\n\n**Don't:** re-describe hair color, clothing, location, or anything already in the reference frame. That's wasted tokens and usually nudges the output off-model.\n\nIf the reference and text conflict (text says \"red jacket\", image shows blue), the image wins.\n"},{"slug":"voice-markup","title":"Use bracket tags for voice direction","description":"ElevenLabs v3 responds to inline direction tags like [whispers], [sighs], [excited] — put them before the clause.","surface":"voices","model":"elevenlabs-tts-v3","trigger":"always","priority":45,"tags":["voice","elevenlabs","direction"],"body":"\nElevenLabs v3 (Alpha) supports inline tags that change delivery:\n\n```\n[whispers] Don't wake him. [pauses] I'll be downstairs.\n[excited] We actually won? [laughs]\n[sighs] I told you this would happen.\n```\n\nTags go **before** the clause they apply to and affect until the next tag or sentence boundary. Supported tags include: `[whispers]`, `[shouts]`, `[sighs]`, `[laughs]`, `[crying]`, `[excited]`, `[sarcastic]`, `[pauses]`.\n\nTags only work with the v3 model — ignored silently on v2/Multilingual.\n"},{"slug":"punctuation-pacing","title":"Punctuation controls TTS pacing","description":"Commas pause ~200ms. Periods pause ~500ms. Ellipses pause ~800ms. Use ellipses for dramatic beats, not commas.","surface":"voices","trigger":"always","priority":50,"tags":["voice","pacing"],"body":"\nEvery TTS provider (ElevenLabs, Cartesia, Fish Audio) respects punctuation for pacing. Approximate pauses:\n\n- `,` → ~200ms\n- `.` → ~500ms\n- `...` → ~800ms\n- `—` → ~300ms with a pitch lift\n\nFor dramatic delivery, reach for ellipses rather than padding sentences with commas. For faster reads, remove optional commas.\n\nIf a line reads too slowly, shorten sentences. If it reads too fast, add periods rather than commas — the half-second beat is the natural rhythm of English narration.\n"},{"slug":"music-prompt-structure","title":"Music prompts — genre, mood, instruments, BPM","description":"Stack four elements, comma-separated: genre → mood → instrumentation → tempo.","surface":"music","trigger":"first-use","priority":55,"tags":["music","prompting"],"body":"\nMusic generators respond to stacked descriptors. A reliable pattern across Suno, Udio, ElevenLabs Music, and Lyria:\n\n> \"Lo-fi hip-hop, nostalgic and warm, Rhodes piano and brushed drums, 80 BPM\"\n\nOrder matters — put **genre first**, then **mood**, then **instruments**, then **tempo**. Adding lyrics? Put them in quotes at the end:\n\n> \"Indie folk, hopeful, acoustic guitar and harmonica, 110 BPM. Lyrics: 'we drove past the old diner, lights still on at midnight'\"\n\nAvoid adjectives like \"epic\" or \"cinematic\" without a genre anchor — they produce generic results.\n"},{"slug":"bpm-matching","title":"Match music BPM to shot cuts","description":"For a storyboard with 2–3 second cuts, target 120 BPM. For slower 4–6 second shots, aim for 80–90 BPM.","surface":"music","trigger":"always","priority":60,"tags":["music","editing","rhythm"],"body":"\nCuts feel locked to the music when the beat interval divides evenly into shot length. Cheat sheet:\n\n| Shot length | Target BPM |\n|---|---|\n| 1–2s | 140–160 |\n| 2–3s | 120 |\n| 3–4s | 100 |\n| 4–6s | 80–90 |\n| 6s+ | 60–80 |\n\nAt 120 BPM, a beat lands every 0.5s — so 2s and 3s shots both sit on a beat. That's the safest default for marketing-pace cuts.\n\nWhen generating music, specify the BPM explicitly (\"…, 120 BPM\") rather than leaving it to chance.\n"},{"slug":"three-shot-rule","title":"The three-shot rule for social video","description":"Wide establishes context. Medium shows action. Close-up delivers emotion. Cut any of them and the edit feels off.","surface":"storyboard-canvas","trigger":"on-empty","priority":65,"tags":["editing","storyboard"],"body":"\nFor short-form social (8–15 seconds), the most reliable structure is three shots in this order:\n\n1. **Wide** — where are we? (1–2s)\n2. **Medium** — who is the subject and what are they doing? (2–4s)\n3. **Close-up** — why should we care? (2–3s) — ends on the emotional or product beat\n\nMiss the wide and the audience never locates itself. Miss the medium and the cut from wide to close is jarring. Miss the close and you don't leave a feeling.\n\nIf your storyboard is empty, start with these three shots. You can break the rule once you understand why it works.\n"},{"slug":"avatar-pacing","title":"OmniHuman avatars — write the audio first","description":"OmniHuman lip-syncs to an audio clip. Record or TTS the voiceover before you describe the shot.","surface":"storyboard-canvas","model":"omnihuman","trigger":"first-use","priority":70,"tags":["avatar","omnihuman","workflow"],"body":"\nOmniHuman 1.5 drives a talking avatar from one photo + one audio clip. The audio is load-bearing — the model times every mouth shape, head tilt, and blink to the waveform.\n\nWorkflow that avoids rework:\n\n1. Write the script\n2. Generate the voiceover (ElevenLabs or Cartesia) at final length\n3. Pick the photo — head-and-shoulders, well-lit, subject looking at camera\n4. Attach both to the OmniHuman shot\n\nTrying to iterate on the avatar without locking the audio first means every change re-renders the full take. The audio is the skeleton.\n"},{"slug":"cut-on-motion","title":"Cut on motion, not on a static frame","description":"When two shots end/start on an identical direction of movement, the cut disappears. The eye follows the motion through.","surface":"storyboard-canvas","trigger":"always","priority":75,"tags":["editing","motion"],"body":"\nA hidden-cut trick that makes AI video feel edited by a human:\n\n- **End shot A** on a visible direction of motion (a hand rising, a head turning right, a car crossing frame)\n- **Start shot B** with that same direction of motion continuing (another rising hand, a continued right-turn, another car crossing left-to-right)\n\nThe brain stitches the two clips into one action. Even if the subject changes, the cut reads as intentional.\n\nWorst cut: static medium shot followed by another static medium shot. The eye has nothing to track, so the edit announces itself.\n\nWhen prompting, end one shot with a camera move (\"slow pan right\") and start the next with the same direction.\n"}],"count":14}