Video testimonials¶
A visitor records themselves in the browser — no app, no upload dialog, no file
to find. It works on a hosted collection page and inside a collect_feedback
widget on your own domain.
Requires a plan that includes video testimonials.
Switching it on¶
Video is off until you ask for it, in one of two places. Either is enough, and each is checked on every single upload:
| Where | Switch |
|---|---|
| Collection page | Collect video on the page's settings |
| Widget | Collect video on a feedback widget |
Why a switch at all
Video costs money — transcoding time and non-refundable minutes from your
plan. A public key alone therefore authorizes nothing here: without one of
these two flags the upload endpoint answers 404, so nobody can spend your
allowance through an embed you never enabled.
The recorder only appears when three things are true: you switched it on, your plan carries the feature, and storage is reachable. Offering a record button that can only fail after somebody has filmed themselves is worse than showing no button, so the server decides this, per request, and the widget obeys.
What the visitor sees¶
- Record a video → the browser asks for camera permission.
- Preview, then Start recording. A timer runs and stops itself at the limit.
- Use this video, or Record again as often as they like.
- The clip uploads with a progress bar while they type anything they want to add.
- They agree to the display permission and send.
Text becomes optional once a clip is attached — the recording is the testimonial, and demanding a caption from somebody who just filmed themselves is a strange thing to ask.
Every failure leaves the typed path working. No camera, refused permission, an old browser, an upload that times out — each one says so plainly and the text box still works. Nothing about video is ever required.
Limits¶
| Length | 60 seconds |
| Size | 100 MB |
| Formats | MP4, WebM, QuickTime |
| Resolution | up to about 2.1 megapixels (1080p either way round) |
The browser stops at these numbers and the server enforces them again by probing the file — over range requests, before downloading it, so a rejection costs a megabyte or two instead of a hundred.
Output is 854×480 with a generated poster frame, served from the CDN.
How the upload works¶
The file never passes through our application server. A 100 MB body would tie up a worker for the whole transfer.
sequenceDiagram
participant B as Browser
participant A as withfeedback
participant S as Object storage
B->>A: POST /uploads/videos/ (reserve)
A-->>B: presigned POST + capability token
B->>S: POST the bytes directly
B->>A: POST /uploads/videos/{id}/complete/
A->>A: transcode, poster, publish on approval
The upload_token is the only authorization for everything after the
reservation. The ids identify; they do not authorize. Both calls are safe to
retry — completing twice returns the current state and does not start a second
transcode.
Send either page_slug or widget_id on the reserve call. One of them
is required; a body with neither is a 400 that says so.
Nothing is public until you approve it¶
A pending video is private. Moderators watch it through a short-lived signed URL; the public playback URL does not exist yet. Approval is what promotes the file to public storage.
Withdrawal reverses all of it: the item leaves every embed, the object is deleted, and the CDN edge is purged. If any of that fails, the leftover is recorded so a sweeper can finish the job rather than the file quietly surviving.
The CAPTCHA asymmetry¶
Worth knowing if you are integrating rather than embedding.
On a hosted page, the video reservation is the one public endpoint that refuses a missing or bad CAPTCHA token. It spends money before any human looks, and a page we serve can always mint a token — so refusing is safe there.
Inside the widget it cannot work at all. A reCAPTCHA v3 site key is bound to its domains, and the widget runs on your domain, so no token can be minted at any score. The widget path therefore records the CAPTCHA outcome as a signal and never refuses.
That is a deliberate trade, and it is only reasonable because five other limits sit in front of the same endpoint — a monthly minute quota, per-project and per-IP byte budgets, a cap on uploads in flight, a daily transcode cap, and the decode-cost cap above. None of them can be influenced by whoever is calling.
When custom domains ship, you will be able to supply your own reCAPTCHA keys for your own domain, and the widget path becomes a hard gate too.