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

# Securing Your Key

> What the Helius API key your embedded-wallet app exposes can and can't do, and how to lock it down — domain and IP/CIDR access control, key-less Secure RPC URLs, and a serverless server-side proxy.

The wallet-kit SDK runs in the browser, so your Helius `apiKey` is present client-side: the provider sends it to the wallet **bootstrap** (`/waas/config`) when your app loads. This is expected for a client-side SDK. This page explains exactly what that key can and can't do, and how to lock it down.

## What the key can — and can't — do

Start here, because it's the part that's easy to get wrong: **the Helius API key is an RPC/credits credential, not a wallet key.**

A leaked key **cannot**:

* sign transactions or messages,
* move or access funds, or
* touch any user's embedded wallet.

Wallet signing is authorized by the **end user's own passkey or session**, and the private keys live in [Turnkey](https://www.turnkey.com)'s secure enclaves — never in your app, your server, or anything the API key can reach. **Exposing the key does not expose wallets.**

What a leaked key **can** do is consume your **Helius credits** (RPC quota). That's the entire blast radius — a billing concern, not a custody one — and the layers below bound and close it.

<Note>
  **Exposure is not a billing bypass either.** WaaS signatures are metered at the
  moment the embedded wallet signs, not at the API-key layer — a leaked key can't
  mint free signatures or bill signatures from someone else's site. The metering
  doesn't depend on key secrecy.
</Note>

## Lock it down

These layers go from lowest-effort to the hardest boundary. The first is the required baseline; stack the rest as your risk tolerance requires.

### 1. Domain-restrict your key (required)

Lock the key to the origins your app runs on, so a key scraped from your bundle is useless from anywhere else.

<Steps>
  <Step title="Open RPC Access Control">
    In the [dashboard](https://dashboard.helius.dev), go to your key under the
    **RPCs** section and open **Access Control**.
  </Step>

  <Step title="Add your domains to Allowed Domains">
    Add every origin your app serves from — production, staging, and preview:

    ```
    yourdapp.com
    www.yourdapp.com
    staging.yourdapp.com
    ```
  </Step>

  <Step title="Use a separate key per environment">
    Keep a distinct key for local, staging, and production so you can rotate one
    without taking down the others.
  </Step>
</Steps>

<Note>
  **Domain allowlists stop browser-based misuse, not a determined script.** The
  check reads the request's `Origin`/`Referer` header — a browser sets it
  honestly, but a non-browser client (e.g. `curl -H "Origin: yourdapp.com"`) can
  forge it. This is true of **every** client-side API key, not just Helius.
  Domain restriction reliably stops the common case — your key showing up on
  someone else's site — but for a boundary that *can't* be forged, use a
  server-side key locked to your IPs/CIDRs ([step 4](#4-add-a-non-spoofable-ipcidr-boundary)).
</Note>

### 2. Key-less Secure RPC URLs (automatic)

RPC traffic doesn't carry your key at all. The SDK resolves your project's **key-less Secure RPC URL** at bootstrap and uses it for `connection` calls automatically — nothing to configure.

Because these URLs contain **no key**, there's nothing in an RPC request to extract, and they're **rate-limited to 5 RPS per IP** — so their protection doesn't depend on Origin checks. (Available on paid plans; when a project has no Secure RPC URL, RPC falls back to the same-origin route handler in step 3.)

### 3. Move the key server-side — serverless

To keep the key out of the browser for RPC, send, and transaction-history calls, route them through your own endpoint that injects the key from a **server-side** secret. This is also how you opt into [Sender-optimized landing](/docs/sending-transactions/sender) and transaction history.

Both options are **100% serverless** — no server to run:

* **Next.js route handler** — deploys as a serverless function (Vercel, Netlify, Cloudflare). Reads `HELIUS_API_KEY` from the server environment.

  ```ts app/api/helius/[...path]/route.ts theme={"system"}
  import { createHeliusRouteHandler } from "helius-wallet-kit/next";

  export const { GET, POST } = createHeliusRouteHandler();
  ```

* **Cloudflare Worker proxy** — the cleanest fully-serverless option: the key lives in a Worker secret and never reaches the browser.

  <Card title="Helius RPC Proxy" icon="github" href="https://github.com/helius-labs/helius-rpc-proxy">
    Open-source RPC proxy you deploy to Cloudflare with one click.
  </Card>

### 4. Add a non-spoofable IP/CIDR boundary

For a boundary an attacker **cannot forge**, lock a **server-side** key to your backend's IP addresses or CIDR ranges. Unlike an `Origin` header, a request's **source IP can't be spoofed** over a normal connection — so a `curl` from anywhere but your servers is rejected outright.

This applies to a **server-side** key only — you can't IP-restrict the browser key, because your users connect from unpredictable IPs. The clean pattern is **two keys**:

| Key                  | Used by                            | Restriction           |
| -------------------- | ---------------------------------- | --------------------- |
| Public / browser key | provider `config` (bootstrap)      | Allowed **Domains**   |
| Server key           | route handler / Worker (RPC, send) | Allowed **IPs/CIDRs** |

<Note>
  Serverless functions have **dynamic egress IPs**, so pinning a CIDR needs a
  stable egress — a Cloudflare Worker with a dedicated egress IP, Vercel Secure
  Compute, or a fixed-IP NAT in front. Without one you still get the main win
  (the key is server-side, never in the browser); you just don't add the IP
  boundary on top.
</Note>

See [Protect your keys](/docs/rpc/protect-your-keys) for the full access-control rule reference.

## How the layers compare

| Layer                                  | Key in browser? | Forgeable?                     | Effort         |
| -------------------------------------- | --------------- | ------------------------------ | -------------- |
| Domain-restricted key                  | Yes             | Origin header is forgeable     | Lowest         |
| Key-less Secure RPC URL                | No key in RPC   | N/A — no key, rate-limited     | None (default) |
| Server-side key (route handler/Worker) | No              | —                              | Low            |
| Server-side key + IP/CIDR              | No              | Source IP is **not** forgeable | Medium         |

Whichever layers you choose, **none of this is your users' wallet keys** — those are never in play.

## The wallet bootstrap

<Note>
  In the **route-handler (production) setup**, the wallet bootstrap
  (`/waas/config`) also goes through your route handler with the **server-side**
  key — so no Helius key ships to the browser for it either. In the
  **prototyping** setup, the browser sends the key for the bootstrap;
  domain-restrict it (step 1). Either way it's the RPC-scoped key — it can't
  touch wallets or funds.
</Note>

## Checklist

Before you ship:

* Client key is domain-restricted to your exact origins
* Separate keys for local / staging / production
* Key is read from an env var, never hardcoded
* (Optional) RPC, send, and history routed through the serverless route handler or Cloudflare Worker
* (Optional) Server-side key locked to your IPs/CIDRs for a non-spoofable boundary

## Next steps

<CardGroup cols={2}>
  <Card title="Protect your keys" icon="shield-halved" href="/docs/rpc/protect-your-keys">
    Full access-control reference: domains, IPs, CIDRs, and proxies.
  </Card>

  <Card title="Configuration" icon="sliders" href="/docs/waas/configuration">
    Provider config and dashboard sign-in methods.
  </Card>
</CardGroup>
