Use case · AI video SaaS

Add a captioning API to your AI video product

Backend-only, webhook-native, per-minute billing

Ship styled captions in your editor without building ASR, timing, render workers, ffmpeg, and a billing layer. ZapCap is the caption-rendering API your users never see — but their finished MP4s come out the other side.

Server-only API keys · HMAC webhook signing · map taskId to your user
The problem

Captions look simple, until they're in your roadmap.

Auto-captioning is the most-asked-for feature in any short-form video app. Building it isn't one feature — it's six. Each has its own edge cases, vendors, and on-call rotation.

  • ASR layer — multilingual, retries, vendor sprawl.
  • Timing & layout — line-break rules, words-per-cue, safe zones, CJK / Thai.
  • Style presets — Beast, Hormozi, karaoke fill, keyword emphasis.
  • Render pipeline — ffmpeg workers, queues, retries, output storage.
  • Async delivery — webhooks, signature verification, polling fallbacks.
  • Usage billing — per-minute metering, retries, credit balances.
Reference architecture

Where ZapCap sits in your stack

Drop the API behind your existing backend. Your users never see ZapCap — they see your editor, your export button, your branded waiting state.

Your product
End user
Your editor (web / mobile)
Upload to your CDN
Your backend
API server (Next, Rails, FastAPI…)
Holds ZAPCAP_API_KEY (server-only)
/webhooks/zapcap handler
ZapCap
POST /videos
POST /videos/:id/task
Signed renderUrl
Security note: API keys never ship to the client. Webhook payloads are HMAC-signed; verify before fetching the renderUrl.
Backend integration

Two routes, one env var, one webhook

Server-side only. Your front-end calls your API; your API calls ZapCap; ZapCap calls you back when the render is ready.

WEBHOOK PAYLOAD
{
  "eventId":   "evt_8kQ2...",
  "taskId":    "tsk_2hP4...",
  "videoId":   "vid_9Lm2...",
  "status":    "completed",
  "renderUrl": "https://cdn.zapcap.../out.mp4"
}
  • Map taskId → your user in your own store when you create the task.
  • Verify HMAC on the x-signature header.
  • Dedupe webhook deliveries on eventId.
1// POST /api/exports — your backend route
2const ZAPCAP_KEY = process.env.ZAPCAP_KEY;
3const ZAP = (path, body) =>
4 fetch(`https://api.zapcap.ai${path}`, {
5 method: 'POST',
6 headers: { 'x-api-key': ZAPCAP_KEY, 'Content-Type': 'application/json' },
7 body: JSON.stringify(body),
8 }).then(r => r.json());
9 
10export async function POST(req) {
11 const { videoUrl, userId, style } = await req.json();
12 
13 // 1. upload to ZapCap (style is a templateId from GET /templates)
14 const video = await ZAP('/videos/url', { url: videoUrl });
15 
16 // 2. create a render task, attach a webhook notification
17 const task = await ZAP(`/videos/${video.id}/task`, {
18 templateId: style,
19 notification: {
20 type: 'webhook',
21 notificationsFor: ['render'],
22 recipient: 'https://acme.com/hooks/zapcap',
23 },
24 });
25 
26 // map taskId → user in your own store
27 await db.exports.create({ id: task.taskId, userId, status: 'processing' });
28 return Response.json({ exportId: task.taskId });
29}
30 
31// POST /api/hooks/zapcap — webhook handler
32export async function POST(req) {
33 const body = await req.text();
34 verifyHmac(req.headers.get('x-signature'), body);
35 const { eventId, taskId, status, renderUrl } = JSON.parse(body);
36 if (await alreadyProcessed(eventId)) return new Response('ok'); // dedupe on eventId
37 const exportRow = await db.exports.update(taskId, { status, renderUrl });
38 await notify.user(exportRow.userId, 'Your export is ready.');
39 return new Response('ok');
40}
State machine

Export lifecycle inside your product

Map each ZapCap status onto a UI state your users already understand.

pending
transcribing
transcriptionCompleted
rendering
completed
In your UI
"Preparing your export" loader, blocking the Export button until status changes.
On webhook
Push a notification, send an email, or surface a download link in your dashboard.
On failure
Create a fresh task to re-render, or surface a typed error code to the user.
Launch checklist

Everything you need before you ship

A short list to keep your captioning launch from looking captioning-launch-shaped. Engineering, billing, and growth in one column.

  • API key in a secret store Never bundled into client builds.
  • Webhook signature verified HMAC-SHA256 on every payload.
  • Dedupe webhook deliveries on eventId Survive retry storms without double-processing.
  • Credit-balance check before queueing Surface upgrade prompts before the failure.
  • Style preset gallery in your UI Mirror the templates ZapCap exposes.
  • Failure-state UI with re-run and contact-support paths.
  • Polling fallback if your stack can't accept inbound webhooks.
Build vs buy

Engineering scope, honestly

Build it yourself

Caption rendering stack

  • 1ASR vendor selection — accuracy, language coverage, retries.
  • 2Caption layout engine — words-per-cue, line breaks, CJK / Thai.
  • 3Style presets — font stack, animation, keyword emphasis logic.
  • 4Render workers — ffmpeg / libass, autoscaling, GPU vs CPU.
  • 5Job queue & state machine — retries, dead-letter, observability.
  • 6Output storage — pre-signed URLs, expiry, cleanup.
  • 7Billing meter — per-minute counters, refunds on failure.
Use ZapCap

Captioning as a primitive

  • 1POST /videos — done.
  • 2POST /videos/:id/task — done.
  • 3Webhook handler — verify, store, notify.
When ZapCap isn't the right answer: if your product generates whole videos from scenes, JSON timelines, or data-driven templates, a broader video automation API likely fits better. Start with our Creatomate comparison.
Customer · Anonymized

Captions shipped in a single sprint

An AI video SaaS platform replaced an internal captioning prototype with the ZapCap API and shipped styled captions to users in the same sprint. Engineering scope shrank from a multi-quarter rendering stack to a webhook handler plus a style picker — caption rendering is now a primitive in the product, not a project.

1 sprint
From spec to user-visible feature
~0 lines
Of ffmpeg / render infra
per-minute
Billing passes through cleanly
webhook
No queue infra to maintain

For SaaS teams

No. Your front-end calls your backend; your backend holds the ZapCap API key. This is the same pattern as Stripe or OpenAI — never bundle keys into client builds.

Ship captions in your next sprint

Backend-only API, webhook-native, from $0.10/min base usage pricing. Mark it up, bundle it, or pass it through — no render workers to babysit.