You wired a network camera's JPEG snapshot URL into an iframe and the first image loaded perfectly. It then never changed, and when you appended a timestamp to force a refresh, the camera answered Auth Failed. Both symptoms have one cause: the camera URL travels inside a gateway URL proxy, so whatever you append lands on the wrong parameter. This guide shows where the refresh timestamp belongs, how to wire it to the iframe at a fixed poll rate, and how to choose a rate the camera can actually serve. It is for engineers embedding camera views in HMI dashboards and test-stand pages.
Why the snapshot freezes after the first load
A browser caches a response under its URL. Your iframe asks for the same URL string on every poll, so after the first fetch the browser answers from its cache and the camera never sees a second request. The snapshot is not broken; the request is identical to one the browser has already answered.
The cure is a URL that differs on every poll, which is what a millisecond timestamp gives you. The only question is where it goes. If the URL itself is not settled yet, the HTTP snapshot URL guide shows how to find and test a camera's still-image endpoint, and the one-button live view on a remote snapshot URL applies the same idea to a panel.
Why a bare timestamp causes Auth Failed
The iframe does not load the camera directly. The dashboard asks its own gateway, and the gateway fetches the camera URL on the browser's behalf. In the address bar the camera URL is the value of the proxy's url parameter:
http://gateway-host:8088/system/urlproxy?url=http://192.168.4.4/cgi-bin/video.cgi?mode=1%26auth_64=BASE64LOGIN
Every separator that belongs to the camera URL travels percent-encoded, so the proxy reads the whole thing as one value: the camera's & is written %26. Paste a timestamp on the end and it lands inside the last parameter:
http://gateway-host:8088/system/urlproxy?url=http://192.168.4.4/cgi-bin/video.cgi?mode=1%26auth_64=BASE64LOGIN1586366523477
The camera decodes its login blob, finds a number glued to it, and rejects the request. That is your Auth Failed. The timestamp idea is right; the address is missing its separator.
The fix: append the timestamp as an encoded parameter
Treat the timestamp like every other camera parameter: separate it with an encoded ampersand, then append it to the wrapped camera URL.
For the address above, one poll's display URL is:
http://gateway-host:8088/system/urlproxy?url=http://192.168.4.4/cgi-bin/video.cgi?mode=1%26auth_64=BASE64LOGIN%26t=1586366523477
The proxy splits off its url value and percent-decodes it once, so the camera receives mode=1, auth_64=BASE64LOGIN and t=1586366523477 as three separate parameters. The login survives and the timestamp arrives on its own, which also makes every poll's outer URL unique: a fresh cache key for the browser and a fresh request to the camera.
The two failures sit on either side of this fix. A bare & hands t to the proxy as one of its own parameters, the camera URL arrives unchanged, and you are back to repeated upstream requests. No separator at all glues the number onto the login and trips Auth Failed. Position inside the camera URL does not matter as long as the separator is encoded; the end is simplest.
Worked example: refreshing a proxied snapshot every 2 seconds
Inputs: the camera serves stills at http://192.168.4.4/cgi-bin/video.cgi?mode=1 with an auth_64 parameter carrying its base64 login; the dashboard shows them through http://gateway-host:8088/system/urlproxy?url=; the tile should be at most 2 s old, so the poll interval is 2,000 ms.
Rule: store the base string once, then append the encoded separator and the clock at every poll — displayURL = proxyBase + "%26t=" + now_ms.
Arithmetic: at one poll the millisecond clock reads 1586366523477, so the iframe src ends in %26t=1586366523477. Two seconds later it reads 1586366525477 (1,586,366,523,477 + 2,000 ms), and every later poll differs again. At 1 fetch per 2 s the tile costs 30 fetches/min, or 1,800 fetches/h, none answered from cache because no two outer URLs repeat.
Decision: time ten snapshot requests and keep the worst answer time. Hold the interval at least twice that. With a worst of 0.9 s the floor is 1.8 s, so 2,000 ms stands. With a worst of 1.4 s the floor is 2.8 s, so move to 3,000 ms. Once the interval passes about 5 s the tile reads as a still image rather than a live view, and the right move is a stream, not a slower poll.
Wire the refresh in six steps
- Open the proxied URL once in a plain browser tab and confirm it returns a current JPEG. Keep that exact string, %26 characters included, as your base.
- Hold the base string once in the page and bind the iframe src to base + "%26t=" + now_ms.
- Drive that binding from a timer at your poll interval, 2,000 ms to start.
- Swap the src on the existing frame instead of destroying and recreating the element. Some browsers serve a freshly created iframe from cache, which quietly reinstates the freeze; it is the same leave-it-running principle as restarting a live camera preview without reopening the device.
- Wait for the frame's load event before the next swap, so a slow camera never has two requests outstanding.
- Verify with movement: wave a hand in view and confirm the tile shows it within one interval, then twice more.
Choosing a poll rate the camera can keep up with
A snapshot endpoint answers one JPEG per request, on demand, and its answer time is not fixed. Under load, cameras and gateways can queue requests or drop them, and the wait for a dropped request has no useful bound. Measure instead of assuming: ten timed requests give you a worst case, and the floor follows — interval at least twice that worst.
Faster is not better. Once the interval approaches the camera's own service time, requests stack up and frames skip. If you need sub-second updates or continuous motion, stop polling stills and run a stream instead; the RTSP stream versus snapshot guide compares the two shapes.
When an iframe is the wrong tool
Polled stills in an iframe are right for status tiles, guard pages and one-button checks. Two jobs need something else.
Continuous viewing: a 2 s poll shows a scene as a slideshow. For a live picture, feed the camera's stream to a decoder component rather than refreshing a frame.
Measurement: the tile proves the camera sees the part, but it is not a vision pipeline. A stale tile is cosmetic; a stale frame in a measurement is a rejected part, so the measurement side needs a fresh trigger every cycle, as in the stale vision result fix. For the measurement itself, use an area scan camera that streams full frames over GigE Vision or USB3 Vision into your SDK, shortlisted with the camera finder.
GigE, USB3, 10GigE and CoaXPress
1,287 models · from $149.00Checks before you ship
- The wrapped camera URL keeps one ? and carries %26 between all of its parameters.
- The timestamp rides as its own parameter, appended as %26t= plus the milliseconds, never concatenated onto an existing value.
- The worst of ten timed requests is at most half the poll interval.
- The iframe src swaps in place; the element is never recreated.
- Back-to-back polls never overlap; each swap waits for load.
- The page source exposes only the proxied URL, and nothing camera-side is reachable from outside the plant network.
| Symptom | Cause | Fix |
|---|---|---|
| Auth Failed on every poll | Timestamp glued to the last parameter | Add %26 before t= |
| Loads once, then freezes | Outer URL identical each poll | Append %26t= plus the time in ms |
| Updates a few times, then stops | A recreated iframe served from cache | Swap src on the existing frame |
| Frames skip under load | Interval shorter than the camera's service time | Interval at least twice the worst measured time |
For a live tile, that is the whole job. If the frame you actually need is a measurement, walk the camera configurator; questions reach us through the contact page, and the systems topic page gathers the rest of the dashboard material.
Questions
Why does my IP camera snapshot show the same image every time the iframe refreshes?
The browser caches a response under its URL, and your URL is identical on every poll, so only the first fetch ever reaches the camera. Give each poll a unique URL by appending a millisecond timestamp through the proxy. Quick test: open the snapshot URL in a plain tab and refresh twice; if the image stays frozen there too, the URL has no changing part.
Where exactly does the timestamp go in a proxied camera URL?
At the end of the camera URL that sits inside the proxy's url parameter, joined with an encoded ampersand. The percent sign keeps the separator inside the wrapped value until the proxy decodes it, so the camera sees the timestamp as its own query parameter rather than an extension of the login before it. Any further parameter follows the same rule.
How fast can I refresh a snapshot in an iframe?
Start at 2 s and let measurement decide. Time ten snapshot requests, take the worst answer, and keep the poll interval at least twice that. A 2 s interval means the tile is at most 2 s old and costs 1,800 fetches per hour. For sub-second updates or continuous motion, run a stream instead; still-image endpoints cannot promise that service time.
Is it safe to leave the camera login encoded in the snapshot URL?
Not on a page anyone else can open. Base64 is transport encoding, not encryption, and anyone with the page source can decode it. Keep the camera and its gateway proxy on the plant network, never publish the proxied URL externally, and give the camera a dedicated account so the exposed credential works nowhere else.
Does the same fix apply without a gateway proxy?
Yes, and it is simpler. When the iframe loads the camera URL directly, append the timestamp as a plain query parameter with an ordinary ampersand, for example &t= plus the milliseconds, because there is no wrapping layer to encode for. The underlying reason is identical: every poll must present a URL the browser has not seen before.