ScreenshotNeo

BlogEngineering

Can Websites Detect Screen Captures? What Developers Need to Know

Most websites cannot detect OS screenshots. Learn what browsers, Android apps, and screen-sharing APIs can actually observe.

By the ScreenshotNeo team1 October 20268 min read

Short answer: An ordinary website has no documented general browser API that tells it a visitor used the operating system’s screenshot command. A page can request a user-authorized screen-sharing stream with getDisplayMedia(), but that is a different action: the user chooses what to share and the page receives a media stream. Native Android apps have a separate screenshot-detection API, with narrower coverage.

The distinction matters because a browser page, a native mobile app, and a screen-sharing session have different capabilities. A website cannot reliably infer that a user pressed a screenshot key or button merely from JavaScript running in the page.

What websites can and cannot detect

Mechanism Where it applies What software receives Key limitation
getDisplayMedia() Supporting browsers A user-authorized MediaStream containing a display, window, or selected region It starts screen sharing; it is not a notification that an OS screenshot was taken. Browser support is limited and user interaction is required.
Android screenshot-detection API Native Android activities on Android 14 A callback that a supported screenshot action occurred It covers specific hardware-button actions, supplies no screenshot image, excludes ADB and instrumentation captures, and shows a system notice.
FLAG_SECURE Native Android windows A window policy that restricts screenshots and non-secure display output It is a native app control, not a setting available to an ordinary website.

These boundaries are described in the Android screenshot-detection documentation, MDN’s getDisplayMedia() reference, the MDN Screen Capture API guide, and the W3C Screen Capture specification.

Why a normal webpage cannot see an OS screenshot

Browser JavaScript runs inside a security boundary. The documented web platform exposes page events, input events, visibility changes, camera and microphone permissions, and other browser-managed signals. It does not expose a general screenshotTaken event for the operating system’s screenshot function.

A page might observe indirect changes, such as a window losing focus or a display becoming hidden, but those signals have many causes. They cannot identify a screenshot reliably. Treating them as proof would create false positives for tab switching, notifications, window management, screen locking, and accessibility tools.

This conclusion is limited to documented web-platform capabilities. Managed devices, browser extensions, native wrappers, remote-control software, or custom WebView integrations can have privileges that an ordinary website does not.

What getDisplayMedia() actually does

getDisplayMedia() lets a page ask the browser to start a display-capture session. The browser shows its own picker, the user selects a display or window, and the page receives a MediaStream. The page can then preview or process that stream.

  • The call must be made from a secure context such as HTTPS.
  • Browsers require transient user activation, so call it from a click or similar user gesture.
  • The user chooses what to share and grants permission through browser UI.
  • Support varies by browser and version; MDN marks the API as limited availability.
  • Permissions Policy and browser privacy controls can also affect availability.

Runnable browser example

<button id='share'>Start screen sharing</button>
<video id='preview' autoplay muted playsinline></video>
<script>
const button = document.querySelector('#share');
const preview = document.querySelector('#preview');

button.addEventListener('click', async () => {
  try {
    const stream = await navigator.mediaDevices.getDisplayMedia({
      video: true,
      audio: false
    });

    preview.srcObject = stream;
    const [track] = stream.getVideoTracks();
    track.addEventListener('ended', () => {
      preview.srcObject = null;
      console.log('The user stopped screen sharing.');
    });
  } catch (error) {
    console.error('Display capture was not started:', error.name, error.message);
  }
});
</script>

This code detects whether a display-sharing request was accepted or stopped. It does not tell you that the user took an operating-system screenshot before, during, or after the session.

What the stream can be used for

  • Previewing the selected display or window in a video element.
  • Recording the stream with MediaRecorder after the user grants access.
  • Reading video frames for an explicitly requested collaboration or support workflow.

Do not describe any of these uses as silent screenshot monitoring. The capture starts only after browser permission and user selection.

Android 14 screenshot detection

Android 14 adds a native app feature for detecting supported screenshot actions. An Android activity declares DETECT_SCREEN_CAPTURE and registers a callback. When Android detects the supported hardware-button screenshot action, the callback runs and Android displays a notice to the user.

<manifest ...>
    <uses-permission android:name='android.permission.DETECT_SCREEN_CAPTURE' />
</manifest>
class ReaderActivity : ComponentActivity() {
    private val screenshotCallback = Activity.ScreenCaptureCallback {
        // React to the event, for example by recording an audit entry.
        Log.d('ReaderActivity', 'Supported screenshot action detected')
    }

    override fun onStart() {
        super.onStart()
        if (Build.VERSION.SDK_INT >= 34) {
            registerScreenCaptureCallback(mainExecutor, screenshotCallback)
        }
    }

    override fun onStop() {
        if (Build.VERSION.SDK_INT >= 34) {
            unregisterScreenCaptureCallback(screenshotCallback)
        }
        super.onStop()
    }
}

Check the current Android reference for exact API details and lifecycle handling. The callback does not receive the screenshot pixels. Android’s documentation states: “The callback doesn’t provide an image of the actual screenshot.” It also documents that ADB screenshot commands and instrumentation-test captures are not covered by this feature.

What Android’s feature is not

  • It is not available to JavaScript running on a normal website.
  • It is not a universal detector for every way an image can be captured.
  • It does not identify the person who captured the image.
  • It does not provide the screenshot file to the app.

Preventing screenshots in a native Android app

If the requirement is prevention rather than detection, Android documents FLAG_SECURE. Applying this flag to a native activity window can keep its content out of screenshots and non-secure displays.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    window.setFlags(
        WindowManager.LayoutParams.FLAG_SECURE,
        WindowManager.LayoutParams.FLAG_SECURE
    )
    setContentView(R.layout.activity_reader)
}

FLAG_SECURE is not a website setting. A browser page cannot set the user’s Android window flags. For web content, design around the assumption that anything rendered can be copied by a user or another capture path.

How to choose an implementation

  1. You need a website to know about OS screenshots: there is no general documented browser API for this requirement.
  2. You need collaborative screen sharing: use getDisplayMedia(), explain the picker and permission flow, and test supported browsers.
  3. You own a native Android app: use Android’s screenshot callback where its documented coverage is sufficient.
  4. You need to reduce screenshot exposure in Android: evaluate FLAG_SECURE and explain the user-visible tradeoffs.
  5. You need screenshots of web pages for QA, previews, or documentation: capture the page from your own automation or a screenshot API rather than trying to detect a visitor’s private capture.

Or skip the browser setup

If your goal is to capture a website—not detect a visitor’s screenshot—ScreenshotNeo provides a single GET request that returns a PNG, JPEG, WebP, or PDF. Its clean-shot process accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Each step can be turned off.

cURL

curl -G 'https://api.screenshotneo.com/v1/shot' \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python

import requests

r = requests.get(
    'https://api.screenshotneo.com/v1/shot',
    params={
        'access_key': 'YOUR_API_KEY',
        'url': 'https://stripe.com',
    },
    timeout=90,
)
r.raise_for_status()
open('shot.webp', 'wb').write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await Bun.write('shot.webp', bytes);

See the ScreenshotNeo documentation for request parameters and response details. The API supports full-page captures with lazy images loaded, CSS-element captures, device presets or custom viewports, dark mode, retina scale, custom CSS and JavaScript, clicks, waits, blocked resources, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous jobs, webhooks, bulk capture, usage data, PDFs, and HTML/CSS-to-image conversion.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the result through X-Page-Verdict and X-Billed headers. ScreenshotNeo also includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan.

Create a free ScreenshotNeo account and start with 1,000 screenshots a month without adding a card.

Troubleshooting

Problem Cause Fix
navigator.mediaDevices is undefined The page is not in a secure context, or the browser does not support the API. Use HTTPS or localhost and check the target browser’s current compatibility data.
NotAllowedError The user canceled the picker, denied permission, or the call was not made from a permitted user gesture. Start the call directly from a click and handle cancellation without treating it as a screenshot event.
NotFoundError No capturable display source is available. Check OS and browser policies, then retry with a supported display or window.
NotReadableError The selected source cannot currently be captured, often because of an operating-system or browser restriction. Try another window or display and check browser and OS permissions.
Android callback never fires The app is below Android 14, the permission or registration is missing, or the capture method is outside the documented coverage. Gate the code by API level, declare DETECT_SCREEN_CAPTURE, register the callback for the activity lifecycle, and remember that ADB and instrumentation screenshots are excluded.
The Android callback has no image This is expected behavior. Use the callback only as an event signal. Android does not pass the screenshot pixels.
A ScreenshotNeo response is not an image The target may have returned a bot check, blank page, timeout, or failed load. Inspect X-Page-Verdict and X-Billed, then adjust waits, headers, cookies, user agent, blocking rules, or authentication as appropriate.

Performance, reliability, and privacy considerations

Browser screen sharing

  • Request capture only in response to a deliberate user action.
  • Stop tracks when the workflow ends so the browser can release the display stream.
  • Keep preview and recording work bounded; processing every video frame can consume substantial CPU.
  • Test the browsers and versions your product supports because availability is not uniform.

Android detection

  • Register and unregister the callback with the activity lifecycle.
  • Keep the callback lightweight and move network or database work off the main thread.
  • Document exactly which screenshot actions your policy covers; do not promise universal detection.
  • Use prevention controls only when their effect on support, casting, and testing is acceptable.

Automated website screenshots

For repeatable captures, wait for a meaningful selector, a known delay, or network idle instead of relying on an arbitrary short sleep. Full-page pages with lazy-loaded images require enough time and viewport coverage for assets to load. Cache stable pages when freshness permits, and use asynchronous jobs or bulk capture when a request would otherwise exceed an interactive timeout.

FAQ

Can JavaScript detect the Print Screen key?

Not as a reliable, documented browser signal for an operating-system screenshot. Keyboard events are subject to browser and OS handling and do not prove that a screenshot was saved.

Can a website detect screenshots on iPhone or iPad?

This article’s documented web conclusion still applies: an ordinary webpage has no general browser API that reports an OS screenshot. Native app capabilities are separate from browser capabilities.

Does screen recording make screenshots detectable?

No. getDisplayMedia() gives a page a user-authorized stream for screen sharing. It does not notify the page whenever the operating system takes a screenshot.

Can I use a watermark to identify screenshots?

A watermark can associate an image with a session or account, but it does not detect the capture event. Treat it as an attribution measure, not a browser detection API.

Does Android’s screenshot callback expose the screenshot file?

No. Android explicitly documents that the callback does not provide an image of the actual screenshot.