Evidence delivery
Mandatory startup canaries, event and media buffering, direct storage, degraded states, retries, and clean-end drain.
On this page
Readiness is a delivery claim, not merely “the Worker loaded.” The SDK requires an ingest canary for every session and a storage canary whenever continuous recording is configured. These checks are always enabled internally; the browser client cannot turn them off.
Startup readiness
The runtime boot sequence is:
- Open the per-session event queue.
- Deliver a disposable
sdk.delivery.readyevent to/ingestand wait for acknowledgement. - For screen or webcam recording, request the server-selected upload mode.
- Persist and acknowledge a disposable 1 KiB media canary in the configured storage path.
- Start observers and emit
sdk.ready.
If a mandatory check fails, runtime observers do not start. onError receives a DeliveryReadinessError with a stable code; the Vue wrapper emits session-error and keeps the assessment blocked.
| Error-code family | Meaning | Typical cause |
|---|---|---|
ingest-* | Event canary timed out, was unreachable, unauthorized, rejected, or malformed. | API/DNS/CORS failure, invalid pk_ key, origin not allowed, proxy or rate limit. |
upload-config-* | The recording upload-mode response could not be obtained or validated. | Server/storage configuration error or intercepted response. |
media-storage-* | The disposable recording canary did not reach storage. | S3 CORS, expired/blocked presigned URL, bucket policy, DNS/proxy filtering. |
These are read-only failure classifications, not policy switches.
Event queue
Events are structured-cloned to a Web Worker and persisted in IndexedDB before batch upload. Defaults are 50 events, a 2-second batch interval, a 50 MiB queue cap, and five delivery attempts per batch. When IndexedDB is unavailable, the SDK reports storage-fallback and uses memory; a refresh or crash can then lose queued evidence.
Use onEvent to observe:
| Worker message | Meaning |
|---|---|
ready | Startup delivery checks passed and the worker can accept events. |
queued | An event was accepted into the local queue. |
uploaded | A batch response listed accepted/rejected event ids. |
upload-failed | An attempt failed and will retry within its budget. |
dropped | Oldest queued events were removed after queue overflow. |
storage-fallback | IndexedDB failed; the queue is memory-only. |
init-failed | Mandatory delivery readiness failed. |
ready does not guarantee future connectivity. Treat prolonged upload failures, drops, or memory fallback as an operational degradation and surface them to support/monitoring.
Recording delivery
The server selects one of two recording paths:
- Direct multipart upload — the browser uploads parts to S3-compatible storage with presigned URLs. Unflushed tail bytes use an IndexedDB durable buffer by default so a refresh can recover them.
- POST upload — chunks pass through the Proctor API.
There is no silent path substitution after readiness. A recording session fails closed if its selected path cannot prove storage delivery.
Continuous recording emits chunks on the configured timeslice (10 seconds by preset default). A slow uplink can make buffered bytes grow faster than delivery. maxQueueBytes, retry budgets, browser storage limits, and session end deadlines are therefore safety bounds, not capacity planning.
Capture-time synchronization
Events, screen recording, webcam recording, and webcam snapshots use one monotonic SDK capture clock. Event timestamps are stamped at the originating browser signal before worker queueing. Snapshot and recording timing travels with the media upload, so delayed delivery or a retry does not move evidence later on the review timeline. The server stores arrival time separately for operations and auditing.
Screen and webcam recordings can start at different moments. The dashboard therefore calculates a source-specific seek offset from each recording's own capture anchor. An event outside a source's recorded coverage remains visible but is not seekable on that source; the UI does not clamp it onto an unrelated first or last frame. Sessions created by older SDK versions fall back to recording lifecycle or upload timestamps and may be less precise.
For selected integrity events, the SDK creates the event id and timestamp before
starting image capture. This gives the event row and its image one deterministic
identity even when their uploads arrive in the opposite order. With active
screen share, the captured frame is stamped immediately before drawing and is
labelled screen-share / exact. Without permission, the same-page fallback is
labelled page-capture / estimated; it cannot capture other tabs or
applications.
After a screen recording is finalized, the worker seeks the event's
attempt-specific recording using
event timestamp - recordingStartedAt. A successfully extracted frame is
stored as recording-frame / exact and replaces the lower-quality browser
fallback idempotently. Events before recording began, attempts with no screen
recording, and extraction failures retain a visible Image unavailable
reason instead of silently appearing to have no evidence.
Clean-end drain
await client.end() stops recorders first so their final partial chunks enter the upload queues, drains screen/webcam/media recorders, emits session.ended, flushes the event worker, and then releases resources. The end path is bounded: a browser or network that cannot finish in time may leave a degraded or incomplete evidence set.
Do not close the page immediately after calling end(). Disable repeated submission, display an “ending” state, and wait for the promise before navigating.
pagehide is best-effort for events and cannot perform a reliable media drain. Refresh/close without a clean terminal event is treated as interruption/abandonment and should be reconciled from the server.
Network and storage configuration
For direct uploads, the candidate page's exact origin must be allowed by the storage bucket CORS policy. Scheme, host, and port all matter; localhost and 127.0.0.1 are different origins. Allow the required PUT/multipart requests and response headers, and ensure corporate proxies, DNS filters, ad blockers, or privacy extensions do not block the API or storage host.
Keep presigned URLs short-lived (PRESIGN_TTL_SECONDS, default 900 seconds) while allowing enough time for slow clients. Abort incomplete multipart uploads with storage lifecycle rules (S3_ABORT_INCOMPLETE_DAYS, default 7) as a billing and privacy backstop.
See browser and network support for the trial matrix and troubleshooting for failure isolation.
Operational acceptance criteria
Before admitting a candidate, verify that:
- the Vue wrapper emitted
session-started, or the raw client worker reportedreadyand required capture started; - startup canaries appear in SDK telemetry with the expected SDK version/build;
- uploads continue to receive acknowledgements during the test;
- required media is visible and playable after end;
- the server has a terminal or explicitly abandoned outcome;
- missing or incomplete evidence is shown as degraded, never as a clean pass.