Docs / Surface / embed-widget

Embed Widget

The Surface embed widget drops a complete scanning UI into any webpage (file picker, upload progress, result display, and dark mode support) with just two lines of code. No React, no component library, no UI work required.

The embedded Surface scanner widget rendered on a page The widget, rendered from the two lines below.

Quick start

Add the script tag and initialize the widget with a target container:

html

<script src="https://your-domain.com/static/embed_scanner.js"></script>
<script>
  initSurfaceScanner({
    container:    'scanner',
    scanEndpoint: '/api/scan',
    theme:        'auto'
  });
</script>

That is it. The widget renders a drag-and-drop file picker, handles the upload, shows a progress indicator, and displays the scan result with the safety score and threat details.

Options

Option Default Description
container required Element ID or CSS selector where the widget mounts
scanEndpoint /api/scan URL the widget sends file uploads to
theme auto Color theme: 'light', 'dark', or 'auto' (follows system preference)
onScanComplete Callback function (result, file) => {} called when a scan finishes

The onScanComplete callback receives the full scan result object and the original File object, so you can display custom UI or trigger downstream logic based on the verdict.

The widget also accepts apiKey, webhookUrl, and webhookApiKey options for prototyping against Surface directly, but do not use these in production; see the security note below.

Widget security

Warning

Never put your API key in client-side code. Anyone who views your page source can copy and use it. The embed widget should never call the Surface API directly from the browser with your key.

Instead, use a backend proxy: your server holds the API key and forwards scan requests to Surface. The widget points at your proxy endpoint, and the API key stays server-side.

Backend proxy example (Express.js)

javascript

const express = require('express');
const app = express();

app.post('/proxy/scan', async (req, res) => {
  const resp = await fetch('https://surface.yourdomain.com/api/scan', {
    method: 'POST',
    body: req,
    headers: { 'Authorization': 'Bearer ' + process.env.SURFACE_KEY }
  });
  res.send(await resp.arrayBuffer());
});

app.listen(3000);

Then point the widget at your proxy:

html

<script>
  initSurfaceScanner({
    container:    'scanner',
    scanEndpoint: '/proxy/scan'   // your proxy, not Surface directly
  });
</script>

This way the API key never reaches the browser. You can also add rate limiting, authentication checks, or file type validation in your proxy before forwarding to Surface.

Scan and save in one request

A common pattern is to scan a file, wait for the result, and save it to disk only if it is clean, all in a single request from the widget. Your proxy endpoint buffers the upload, calls Surface synchronously, and conditionally writes the file:

  1. Widget uploads the file to your proxy endpoint
  2. Your proxy forwards it to Surface and waits for the scan result
  3. If clean, your proxy saves the file to storage and returns success
  4. If malicious or suspicious, your proxy rejects the upload and returns an error

This avoids a second API call or webhook round-trip. The widget makes one request and gets back a final status.

Success

The embed widget is the fastest way to add a user-facing scanning experience to your application. For programmatic integrations without a UI, use the REST API directly.