Confirm-sides widget
An embeddable, cookie-less iframe. It shows the user the two detected kits (from
preview teams) and asks which kit is the home team. The
two team names sit above the kits as editable fields. The user can correct them, which is
useful when the video was created without names and carries the Team Left and Team Right
defaults. Selecting a kit and confirming sends the answer. The widget records which detected
kit is the home team, applies any changed name, and notifies the parent page via
postMessage. It then shows the same screen back, read-only, as a receipt.
The kit answer is the part the vision pass cannot determine on its own. It describes the two kits reliably, but not which one the client calls home. The answer is stored with the colours and kit vector the user saw, so a later detection pass re-orders itself to match the human rather than overwriting the answer.
The widget does not ask which side the home team starts on. The engine derives which half a
team defends at kickoff from the kit answer, and hub writes it to the video's
left_start_team when the analysis completes. If you already know the side, set it yourself
with POST/PUT /videos. A value you supply is never overwritten.
Nothing waits on the widget. The analysis starts regardless. Identity is composed at read
time via GET /analyses/{id}/team-mapping (see
Teams and sides). A confirmation arriving during or after
the run fixes the labels on every result. The wait_for create parameters no longer exist.
The only remaining hold is hub's automatic waiting_for_meta pass, described in the
workflow guide.
1. Mint an embed token
POST /v1/videos/{id}/embed-token
{
"frame_ancestors": ["https://app.your-domain.com"],
"ttl_seconds": 7200
}
| Field | Type | Description |
|---|---|---|
frame_ancestors |
array | null | Origins allowed to frame the widget, written to its CSP frame-ancestors. Omitting it means the widget can only be opened directly, not embedded. Set it when embedding. |
ttl_seconds |
integer | 60-86400. Default 7200. |
readonly_names |
boolean | Default false. When true, the widget shows the team names as fixed labels instead of editable inputs, and the confirm endpoint refuses name writes for this token. Set it when the names come from your own records. It lives on the token, not the iframe URL, so the end user cannot change it. |
{
"token": "wZ3…",
"url": "https://<hub>/embed/confirm-sides?t=wZ3…",
"expires_at": "2026-07-17T14:00:00.000000Z"
}
The raw token is returned once. Only a hash is stored. It is scoped to this one video: read
the team preview, and set the confirmed home kit, the two team names and left_start_team.
Mint a fresh token per page view.
2. Embed the iframe
<iframe src="<url from the response>&lang=nl"
style="width:100%;max-width:480px;height:470px;border:0"
referrerpolicy="no-referrer"></iframe>
The widget shows a spinner and polls the preview itself. You do not need to wait for the preview pass before embedding. For an older video that predates the preview pass, minting the token starts one, and the spinner runs longer. See Older videos.
Language
lang=en or lang=nl on the iframe URL. locale= is accepted as an alias, and lang=
wins when both are set. An omitted or unknown value falls back to the browser's
Accept-Language, then English. Set it explicitly. The widget is framed inside your
app, and the user's browser language is often not the language of your page. It also sets
the page's <html lang>.
3. Listen for the result
The iframe posts messages to the parent window:
window.addEventListener('message', (e) => {
if (e.data?.type === 'aib:sides-confirmed') {
// e.data.videoId
// e.data.homeTeamKey: "team_a" or "team_b", the kit the user says is the home team
// e.data.leftStartTeam: the video's current side, normally still "unknown" here
// e.data.homeName, e.data.awayName: names as stored after the submit
}
});
type |
Payload | Meaning |
|---|---|---|
aib:sides-confirmed |
videoId, leftStartTeam, homeTeamKey, homeName, awayName |
User confirmed; the video is updated. |
aib:sides-close |
videoId |
Nothing left to ask. You may remove the iframe. Follows aib:sides-confirmed by a few seconds. |
aib:sides-expired |
videoId |
Token expired mid-session. Mint a new one. |
aib:sides-error |
videoId, message |
Something failed inside the widget. |
Act on the data in aib:sides-confirmed. Treat aib:sides-close as a hint about the
iframe's lifetime. Between the two events the widget shows the confirmed teams back to the
user, so closing on aib:sides-confirmed cuts that off. Ignoring aib:sides-close is also
fine; the widget stays on its confirmation screen.
No webhook is sent for this event. Listen for the postMessage, or poll GET /videos/{id}
or GET /videos/{id}/meta.
Notes
homeTeamKeyis the answer the widget collects.GET /videos/{id}/preview-teamsreads it back asconfirmed_home_key. Reopening the widget for a video that already has one renders that kit pre-selected. The user can confirm it again or switch to the other.left_start_team: "home"means the home team defends the left half at kickoff. It is a separate axis from identity. For a widget-only flow it stays"unknown"until the analysis finishes. PollGET /videos/{id}orGET /videos/{id}/metathen, rather than expecting it in thepostMessage.- The spinner is typically up for about 10 seconds when you embed the widget straight after creating the video, most of it spent reading frames from the source URL. The kit colours and the example player crops are detected together. The widget holds briefly for the crops, then falls back to plain colour swatches. Embedding later renders immediately.
- Name edits are optional. A blank field is ignored and the stored name kept. Names are capped at 100 characters.
- Confirming after an analysis has started still lands. Names and roles are resolved at read
time, so
team-mappingand every labelled response pick the answer up immediately, with no re-run. - To use your own UI, skip the widget and call
GET /videos/{id}/preview-teamsserver-to-server, then setleft_start_teamviaPUT /videos/{id}.