Skip to content

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

  • homeTeamKey is the answer the widget collects. GET /videos/{id}/preview-teams reads it back as confirmed_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. Poll GET /videos/{id} or GET /videos/{id}/meta then, rather than expecting it in the postMessage.
  • 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-mapping and 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-teams server-to-server, then set left_start_team via PUT /videos/{id}.