2026-11-05 · 4 min read
How to Add Captions to Webinar Recordings
Webinars are long. Most people who get the replay link never watch the whole thing, and a large share of those who do watch with the sound off, on a phone, between meetings. If your webinar recording has no captions, that audience is gone. Adding captions after the fact usually means importing the file into an editor, waiting on auto-transcription, fixing timing, and exporting again. With an API, the same result is one submit call and a poll.
Why webinar replays need captions
- Replays get watched on mute in feeds, inboxes, and Slack threads.
- Captions make hour-long recordings skimmable, since viewers can read ahead.
- Accessibility requirements increasingly apply to marketing content, not just product UI.
- Captioned clips from the webinar work as social posts without a separate edit.
The manual route vs the API route
The manual route is an editor, a transcription pass, a timing pass, and an export. If you run webinars weekly, that workflow does not scale, and it is usually the first thing that slips. The API route treats captioning as a step in your publishing pipeline: the recording lands in storage, a job is submitted, and the captioned file comes back ready to upload.
Submit a webinar recording for captioning
caption.sh takes a JSON POST with a source_url pointing at your recording and returns a job_id. There is no file upload form to wrestle with; the API fetches the video from the URL you give it. (A two-step signed-URL upload path is also documented if your file is not publicly reachable.)
curl -X POST https://api.caption.sh/v1/videos \
-H "Authorization: Bearer $CAPTION_API_KEY" \
-H "Content-Type: application/json" \
-d '{"source_url": "https://example.com/talk.mp4"}'
The response contains a job_id. Captioning is priced at $0.10 per minute of video, prepaid. Each job is capped at 10 minutes, so a 45-minute webinar is split into segments first and still totals $4.50.
Poll for the finished video
Jobs are asynchronous, so your script submits once and then polls until the job finishes. Keep the poll bounded: exit when the job is done, when it failed, or when you hit a timeout you choose.
import time
import requests
KEY = "your_api_key"
H = {"Authorization": f"Bearer {KEY}"}
job = requests.post(
"https://api.caption.sh/v1/videos",
headers=H,
json={"source_url": "https://example.com/talk.mp4"},
).json()
job_id = job["job_id"]
# A 429 means the transcription throughput ceiling is reached. Wait and retry.
deadline = time.time() + 1800
status = None
while time.time() < deadline:
r = requests.get(f"https://api.caption.sh/v1/jobs/{job_id}", headers=H)
status = r.json()["status"]
if status in ("done", "error"):
break
time.sleep(10)
if status == "done":
r = requests.get(f"https://api.caption.sh/v1/jobs/{job_id}/result", headers=H, allow_redirects=False)
print(r.headers["Location"]) # signed URL; results are stored for 24 hours, then deleted
elif status == "error":
raise SystemExit("captioning failed")
else:
raise SystemExit("timed out waiting for the job")
The result endpoint redirects to a signed download URL, and results are stored for 24 hours, then deleted, so download the captioned file or move it to your own storage promptly. The API documents no webhook surface, so polling is the supported pattern.
Where this fits in a replay pipeline
A typical setup is: webinar ends, the recording is exported to object storage and split into segments under the 10-minute job cap, a caption job is submitted per segment, and the captioned files replace the raw one in your replay page and email follow-up. Clips for social can be cut from the captioned segments, so the captions are already burned in everywhere the video goes.