> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streampixel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Security hardening

> Protect your stream from unauthorized access, abuse, and credential leaks.

The outcome: a Streampixel deployment where your API keys aren't on a Pastebin, your stream isn't being embedded by random sites, your webhook endpoint isn't being spoofed, and a leaked credential is contained in minutes instead of months.

This recipe assumes you have already shipped a working integration. It walks through the threats you should care about and the controls that mitigate each.

## Threat model

The realistic risks for a typical Streampixel deployment:

| Threat                                            | Impact                                                 | Mitigation                                  |
| ------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------- |
| API key leaked in client code or git history      | Attacker uploads/distributes builds, runs up your bill | Server-side only, rotate immediately        |
| Stream embedded on unauthorized sites             | Brand misuse, traffic costs                            | Domain whitelisting                         |
| Webhook spoofing                                  | Attacker triggers your downstream actions              | Secret-token URL, payload validation        |
| Unbounded session usage                           | Bill shock, worker exhaustion                          | Session limits, rate limiting your endpoint |
| Stale team members with admin access              | Insider risk after departures                          | Periodic role audits                        |
| Credentials shoulder-surfed in screen-shared logs | Subtle but common                                      | Mask in CI logs, env vars                   |

The rest of the recipe walks through controls in order of impact.

## API key hygiene

### Keep keys server-side

API keys must never appear in any code that ships to a browser, mobile app, or anywhere a user can crack open dev tools. The Web SDK does not need an API key to render a stream — it uses your `appId`. Only server-to-server calls (upload, distribute, webhooks) use the API key.

```text theme={"dark"}
Browser  ->  appId only  ->  Streampixel
Server   ->  apiKey      ->  Streampixel
```

If you find yourself writing `apiKey: "sk_..."` in a `.html`, `.jsx`, or anything bundled to the client, stop and rotate.

### Use environment variables

```bash theme={"dark"}
# .env (gitignored)
STREAMPIXEL_API_KEY=...
STREAMPIXEL_USER_ID=...
STREAMPIXEL_PROJECT_ID=...
```

```javascript theme={"dark"}
// server.js
const apiKey = process.env.STREAMPIXEL_API_KEY;
if (!apiKey) throw new Error('STREAMPIXEL_API_KEY not set');
```

Never commit `.env`. Add it to `.gitignore` on day one. Use a secret manager (AWS Secrets Manager, GCP Secret Manager, Vault, Doppler) for production.

### Rotation

Treat key rotation as a fire drill you should be able to run in under five minutes:

<Steps>
  <Step title="Generate a new key">
    In the [API Keys dashboard](/resources/dashboard/api-keys), create a fresh key.
  </Step>

  <Step title="Roll out the new value">
    Update your secret manager / env vars. Restart workers so they pick up the new key.
  </Step>

  <Step title="Revoke the old key">
    Once you've confirmed the new key is in use (one successful upload or distribute call), revoke the old one in the dashboard.
  </Step>

  <Step title="Audit">
    Check recent build uploads / distribute calls for any activity you don't recognize.
  </Step>
</Steps>

If you ever push a key to a public repo, rotate first, scrub history second. Bots scrape new commits within minutes — assume the key is already compromised.

<Warning>
  Force-pushing to overwrite history does not retroactively unbreach a leaked secret. GitHub's secret scanning, third-party caches, and search engines may have already indexed it. Rotate, then clean up.
</Warning>

## Project-level access control

Streampixel projects have built-in access controls. See [Security control](/resources/quick-start-guide/security-control) for the full settings reference.

The two most useful settings:

* **Allowed domains** — only the domains you list can host the stream. This is the single most effective control against unauthorized embedding.
* **Password protection** — gate the stream behind a project password, useful for private demos.

Set both for any project not intended for fully public access.

## Domain whitelisting for embeds

The Web SDK validates `window.location.origin` against the project's allowed URLs list. Misconfigured domains are the #1 reason "the stream works locally but not in production" support tickets get filed.

| Pattern                   | Matches                        |
| ------------------------- | ------------------------------ |
| `https://app.example.com` | Exactly that host              |
| `*.example.com`           | Any subdomain of example.com   |
| `localhost`               | Always allowed for development |

Add staging domains separately from production. `staging.example.com` is not the same as `example.com`.

## Webhook security

Streampixel webhooks do **not currently include a signature header**. There is no HMAC, no shared secret in headers, no signed token. If your webhook URL is `https://example.com/streampixel-webhook`, anyone who guesses that URL can POST a fake `build.approved` event.

Treat this seriously. Mitigations:

### 1. Use a long random token in the URL path

Make the URL itself the secret. Generate 16+ random bytes, hex-encode, and include in the path:

```bash theme={"dark"}
openssl rand -hex 24
# 9f3a4c1ee21b4f2c8d76b0a3e9f128cc4a1b7d9e5f0c2a83
```

Register the URL with that token in the dashboard:

```
https://example.com/webhooks/streampixel/9f3a4c1ee21b4f2c8d76b0a3e9f128cc4a1b7d9e5f0c2a83
```

In your handler, only accept POSTs to that exact path. Any request to `/webhooks/streampixel` or to a wrong-token path returns 404. This makes the webhook URL effectively a shared secret — keep it out of logs and chat messages.

### 2. Validate the payload matches what you expect

```javascript theme={"dark"}
const KNOWN_PROJECT_IDS = new Set([process.env.STREAMPIXEL_PROJECT_ID]);

app.post(WEBHOOK_PATH, (req, res) => {
  res.status(200).end();

  const { event, data } = req.body || {};
  if (!event || !data) return;

  // Ignore events for projects this server doesn't own.
  if (!KNOWN_PROJECT_IDS.has(data.projectId)) {
    console.warn('Unexpected projectId:', data.projectId);
    return;
  }

  // Ignore events with unknown event names.
  const VALID = new Set([
    'build.uploaded', 'build.downloading', 'build.extracting',
    'build.saving', 'build.distributing', 'build.approved', 'build.rejected',
  ]);
  if (!VALID.has(event)) return;

  handleEvent(event, data);
});
```

If a forged request arrives, the projectId check stops it from triggering downstream actions even if the URL token leaked.

### 3. Don't log the webhook URL

Audit your logging. Many frameworks log the full request URL by default; if your webhook URL contains the secret token, that token ends up in log aggregators, error monitoring services, and screen captures.

```javascript theme={"dark"}
// Express example: replace req.url in logs
app.use((req, res, next) => {
  if (req.path.startsWith('/webhooks/streampixel/')) {
    res.locals.loggedPath = '/webhooks/streampixel/[redacted]';
  }
  next();
});
```

### 4. Respond fast, work async

Streampixel webhook delivery times out at **10 seconds and does not retry**. If your handler does heavy work synchronously and exceeds that window, the event is lost. Always:

```javascript theme={"dark"}
app.post(WEBHOOK_PATH, (req, res) => {
  res.status(200).end();         // ack immediately
  enqueueAsync(req.body);         // process in background
});
```

## Rate limiting your own endpoints

Any endpoint you expose that triggers Streampixel calls (say, "Reset my project" buttons in your admin UI) should be rate-limited. Otherwise a misbehaving script or a bored user can hammer Streampixel APIs and exhaust your quotas.

```javascript theme={"dark"}
import rateLimit from 'express-rate-limit';

app.use('/api/admin', rateLimit({
  windowMs: 60 * 1000,
  max: 30,
  standardHeaders: true,
}));
```

The distribute endpoint already enforces 1 call per 2 minutes per user; surface that limit gracefully to your admins instead of letting them retry into errors.

## TLS everywhere

There is no good reason to run any part of this over HTTP in production:

* The Web SDK requires HTTPS for getUserMedia (microphone, camera) and WebXR.
* API calls go to `https://api.streampixel.io/...` regardless.
* Webhook listeners must be HTTPS — Streampixel will not deliver to plain HTTP URLs.

Use Let's Encrypt, Cloudflare, or your platform's automatic TLS. There is no excuse.

## Content Security Policy for embeds

If you embed Streampixel via iframe, set a CSP that allows the player and nothing else:

```http theme={"dark"}
Content-Security-Policy: default-src 'self';
  frame-src https://share.streampixel.io https://*.streampixel.io;
  connect-src 'self' https://api.streampixel.io https://*.streampixel.io wss://*.streampixel.io;
  media-src 'self' blob:;
```

If you serve the SDK from your own page (not iframe), add `script-src` and the relevant SDK origins. Test thoroughly — a too-restrictive CSP breaks WebRTC media in non-obvious ways.

## Auditing team members

Regularly review who has access to your Streampixel account:

<Steps>
  <Step title="List active members">
    Open the team / members page in the dashboard. Note everyone with admin or developer roles.
  </Step>

  <Step title="Reconcile against your HR list">
    Anyone who left the company in the last 90 days should not be there.
  </Step>

  <Step title="Reduce privileges">
    Apply least-privilege: people who only need to view stats don't need keys.
  </Step>

  <Step title="Rotate keys after offboarding">
    If a departed member ever had access to API keys, rotate them.
  </Step>
</Steps>

A quarterly cadence is a sensible minimum.

## Compliance

Streampixel encrypts data in transit (HTTPS / DTLS-SRTP for WebRTC). For compliance work — SOC 2, GDPR, processing agreements — contact the Streampixel team directly; this recipe is engineering-focused and intentionally avoids making compliance claims.

## Pre-flight checklist

Before going to production, confirm:

* [ ] No API key exists in any client-side code, including JS bundles served to browsers.
* [ ] `.env` is in `.gitignore`. Run `git log -p -- .env` and `git log --all -p -S "STREAMPIXEL_API_KEY"` to confirm none has ever been committed.
* [ ] Allowed domains list in the dashboard contains only your real production and staging hosts.
* [ ] Webhook URL contains a 24+ byte random token in the path.
* [ ] Webhook handler validates `projectId` and event name.
* [ ] Webhook handler returns 200 within \~1 second.
* [ ] All endpoints are HTTPS.
* [ ] Team member list reviewed in the last 90 days.
* [ ] You have a documented runbook for "API key leaked, what now."

## Next steps

<CardGroup cols={2}>
  <Card title="API keys" icon="key" href="/resources/dashboard/api-keys">
    Manage and rotate your API keys.
  </Card>

  <Card title="Security control" icon="lock" href="/resources/quick-start-guide/security-control">
    Project-level password and domain restrictions.
  </Card>

  <Card title="Webhooks" icon="bell" href="/resources/api-reference/webhooks">
    All seven events and payload shapes.
  </Card>

  <Card title="CI/CD pipeline" icon="github" href="/resources/recipes/ci-cd-pipeline">
    A reference webhook listener with the patterns above.
  </Card>
</CardGroup>
