Skip to content
Notifications
Clear all

Our agency's workflow for client approvals on Synthesia projects.

11 Posts
11 Users
0 Reactions
3 Views
(@devops_not_grunt)
Reputable Member
Joined: 4 months ago
Posts: 159
Topic starter   [#9906]

Let's be honest, the default "share a link and wait for email chaos" approval workflow in Synthesia is fine for a solo creator making a one-off explainer. It falls apart completely when you're an agency handling multiple revisions for clients who still think "Can you just make the blue more... passionate?" is actionable feedback.

We had to build our own process after a particularly spectacular incident where version 3 of a script got applied to video 7, because the client replied to a two-week-old email thread. The platform's strengths are in generation, not orchestration, so we treat it as a render farm within our own pipeline.

Here's the core of it. We use the API to push scripts and assets, never the UI for anything beyond initial avatar selection. Every project gets a unique directory in Git (yes, Git), housing the script, visual asset references, and a config file that locks the video settings.

```yaml
# synthesia-project.yaml
project_id: client-a-q2-campaign
synthesia_video_id: abc123xyz # Populated after initial create call
approval_workflow_step: script_approved # script_approved, visuals_locked, final_review
assets:
- type: script
path: scripts/v3_approved.md
sha: a1b2c3d4
- type: logo
path: assets/client_logo_final.png
- type: background
ref: "synthesia_background:office_clean"
```

When a script is approved, we tag the commit. A GitHub Action triggers, validates the config, and uses the API to update the video. The generated video is never downloaded and shared directly. Instead, it's pushed to a private, timestamped URL on our own CDN, embedded in a simple review page that *only* has approve/reject buttons with required comment fields. This page is the only thing the client sees. All comments are logged as issues back to the repo.

This forces linear, auditable feedback. No more stray emails. No more "I liked the other version." The *other version* is a different commit. It's not without friction—clients sometimes hate the extra step—but it eliminated the 3AM "wrong video" panic deployments. You're not just managing AI videos; you're managing client entropy. The platform doesn't solve that, so your process has to.



   
Quote
(@cloud_watcher_99)
Reputable Member
Joined: 1 month ago
Posts: 172
 

Ooh, that git-based config file is clever. We do something similar, but we actually hook that `approval_workflow_step` field into a tiny Lambda function that posts to a dedicated Slack channel for each client. It cuts down the "replied to an old thread" problem by about 90% because the conversation is forced into one place.

Our pain point was asset versioning. How do you handle it when a client swaps a logo *after* the script is approved but before the visuals are locked? We ended up adding an `asset_hashes` section to our config to flag mismatches on render.


cost first, then scale


   
ReplyQuote
(@latency_king)
Trusted Member
Joined: 4 months ago
Posts: 44
 

Slack integration is smart for serializing conversations, but you're introducing a network dependency in the critical path. What's the p99 latency on your Lambda from the Synthesia webhook trigger to the message being visible in Slack? If that's over 500ms, you've just made the 'state change to notification' loop slower than the email it's replacing.

On the `asset_hashes` approach, we took it a step further. Our render pipeline does a pre-flight check against a manifest stored in S3. If a hash mismatch is detected, it doesn't just flag it - it automatically generates a comparison frame (old logo vs. new logo) and attaches that to the Slack approval request. This eliminates the back-and-forth of "which logo are you talking about?" The overhead is negligible, maybe 80ms for the S3 HEAD request and image diff.


Every microsecond counts.


   
ReplyQuote
(@ericd)
Reputable Member
Joined: 1 week ago
Posts: 180
 

The latency point is a good catch. We actually faced something similar and realized the Slack notification itself wasn't the bottleneck, it was the user *seeing* it. If the approver is heads-down in another task, even a 50ms notification delay is irrelevant compared to the hours before they check. The real win for serialization is just the single, searchable thread.

I really like your automatic comparison frame idea. That moves the discussion from vague to visual instantly. Does your pipeline ever run into issues with logos that are functionally the same file but have different metadata? We've had some false positives from embedded color profiles or creator info.


Keep it civil, keep it real.


   
ReplyQuote
(@cloud_ops_amy)
Estimable Member
Joined: 5 months ago
Posts: 128
 

Yeah, treating it as a render farm is exactly the right mental model. We do the same, using Terraform to manage the project configs and video settings as infrastructure-as-code. It forces a clean separation between our creative pipeline and their generation API.

One tweak we made to the Git approach: we store the `synthesia_video_id` in a separate state file that's ignored in commits. That way, a developer can clone the repo and run a plan without accidentally pointing at the live production video ID. It's a small guardrail that's saved us a few times.


Cloud cost nerd. No, I don't use Reserved Instances.


   
ReplyQuote
(@danielp)
Trusted Member
Joined: 1 week ago
Posts: 50
 

I like the separate state file trick. That's a smart way to handle the ID mapping without cluttering your config history.

We hit a similar snag but with cost visibility. Running a Terraform plan against the live project ID would sometimes trigger API calls for a state refresh, which showed up as actual usage on our Synthesia dashboard. It was a small amount, but enough to confuse our billing.

Now we use environment-specific variable files that are gitignored. `.env.production` holds the real IDs, `.env.staging` has dummy placeholders. Anyone running a plan locally uses staging by default, so there's no accidental render cost or state drift.



   
ReplyQuote
(@ava23)
Estimable Member
Joined: 6 days ago
Posts: 101
 

That's a solid workaround for billing noise, but it just papers over a deeper issue: platforms shouldn't be charging for read-only state operations. It's a sneaky way to inflate perceived API usage.

Your dummy placeholder method also assumes everyone's local environment is configured correctly. What happens when a new hire forgets to copy the .env.staging template and their plan silently uses production defaults because the file is missing? Guardrails are good, but they're not foolproof.

The real fix is Synthesia providing a proper sandbox or read-only API tier for development. We shouldn't need this level of infrastructure yoga just to avoid surprise charges for checking a project's status.


Trust but verify.


   
ReplyQuote
(@carlr)
Estimable Member
Joined: 1 week ago
Posts: 92
 

The sandbox API tier is the correct long term ask, but I've never seen a SaaS platform add one proactively. They all wait until enough enterprise deals fall through over it.

Your point about the missing .env file is real. We sidestep it by having the config loader fail hard with a clear error if the expected environment variable isn't set. A silent fallback to production is a design flaw.

The billing for read-only ops is indefensible. It turns routine automation and safety checks into a cost-benefit analysis.


Your fancy demo doesn't scale.


   
ReplyQuote
(@bench_runner_ai)
Reputable Member
Joined: 5 months ago
Posts: 160
 

Your Git approach for version control is the right foundation. I'd recommend adding a SHA-1 commit hash to your config's `script.path` reference. For example: `scripts/v3_approved_9f86d08.md`. This creates an immutable link between the config state and the exact script version, preventing the "applied to video 7" issue even if someone force-pushes to the `v3_approved` branch later.

The next step is to automate the state transition. A simple CI/CD job can watch for changes to the `approval_workflow_step` field, call the Synthesia API to update the video, and then commit the new `synthesia_video_id` back. This eliminates manual copy-paste errors from the UI.


BenchMark


   
ReplyQuote
(@integration_ian_2)
Reputable Member
Joined: 2 months ago
Posts: 159
 

I love the idea of encoding the commit hash right in the file path. That's a much cleaner single source of truth than storing it separately in the config. We actually tried a similar approach but with Git tags instead, and it got messy fast when scripts needed quick, minor fixes post-approval.

The automated CI/CD transition is the logical next step, but there's a subtle timing risk. If your CI job runs on `approval_workflow_step` change and commits back the new `synthesia_video_id`, you need to ensure that commit doesn't re-trigger the same job, creating a loop. We had to add a check in our GitHub Action to skip runs where the commit message matched a pattern like `"[Automated] Update video ID"`. Without that, we burned a few API calls before noticing.


api first


   
ReplyQuote
(@isabelr)
Estimable Member
Joined: 6 days ago
Posts: 59
 

That commit loop you described is the classic automation trap. It's hilarious how we build these elegant systems just to watch them chase their own tail.

You can dodge it with a skip pattern, sure. But then your CI job becomes brittle; someone merges with a weird commit message and suddenly you're making API calls for no reason. We ended up using a separate state backend (Terraform Cloud) for the final video ID sync. The CI job triggers a state change, but the backend writes it without creating a new commit in the main repo. No commit, no loop.

It feels like overkill, but less wasteful than paying for phantom renders every time someone forgets the magic phrase.


Trust but verify – especially the audit log.


   
ReplyQuote