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

# Test your Orwel install with Dev mode and the development key

> Send test traffic that never emails anyone, watch every request arrive live, and fix each warning the ingest debugger shows before you switch to the production key.

Installing analytics usually ends with "I think it works". Dev mode is there so you can see it work: every request your app sends with the development key appears live in the dashboard, with the exact payload and a plain explanation of anything wrong in it.

## Two keys, one pipeline

Every workspace has two keys meant for the browser:

* the **production API key** (`orwel_...`) for your live site,
* the **development key** (`orwel_dev_...`) for localhost, staging and preview deploys.

Both go through the same ingest pipeline, so what works with one works with the other. The difference is what happens after the data is stored:

| | Production API key | Development key |
| - | - | - |
| Events, sessions and visitors | Stored | Stored |
| Leads created by a conversion | Normal | Marked `is_test`, shown with a **Dev** badge |
| New lead notification emails | Sent | Never sent |
| Integrations (HubSpot, Hyperflow, Zapier, Brevo) | Run | Skipped |
| Cart abandonment and inbox notifications | Run | Skipped |
| Request log in Dev mode | No | Yes, kept for 7 days |

> [!NOTE]
>
> Test traffic still counts in your metrics, so keep test runs short. The `is_test` flag only applies to leads the development key creates: a real customer who converts again from your laptop keeps their real lead.

## Set it up per environment

Use one variable and give it a different value in each environment. The code never changes, only the key.

```bash .env.local (your machine) theme={null}
NEXT_PUBLIC_ORWEL_KEY=orwel_dev_...
```

```bash Preview and staging theme={null}
NEXT_PUBLIC_ORWEL_KEY=orwel_dev_...
```

```bash Production theme={null}
NEXT_PUBLIC_ORWEL_KEY=orwel_...
```

On Vercel, Netlify or similar hosts, set the development key for the **Preview** environment and the production API key for **Production**, so preview deploys never send real lead emails. The variable name changes per framework (`PUBLIC_ORWEL_KEY` in SvelteKit and Astro, `VITE_ORWEL_KEY` in Vite); see [Install by framework](https://www.orwel.io/docs/guides/frameworks.md).

While you test, add `debug: true` to see what the SDK does in the browser console:

```ts theme={null}
orwel.init({ apiKey, debug: true })
```

## A five minute test run

**1. Open Dev mode next to your app**

In the dashboard go to **Development, Dev mode** (`/dev-mode`). If the section is locked, turn on the development tools with the toggle. Keep it open in a second window.

**2. Load a page**

A `session` request should appear within a few seconds. If nothing shows up, see [Common problems](#common-problems).

**3. Identify yourself**

Log in or submit a form that calls `orwel.identify({ email })`. Check that the `identify` row has no `identify_no_email` warning.

**4. Fire your custom events**

Click through the flows you instrumented. Each `track()` should be OK; an `event_not_found` error means that code is not in the catalog yet.

**5. Convert once, with a campaign**

Open the page with `?utm_source=test&utm_campaign=devmode` and submit your main form. The `conversion` row should show only the test mode note, and the lead appears in **Leads** with the **Dev** badge and the test campaign as its source.

## Reading Dev mode

* **Requests** lists everything received with the development key, newest first: type, event code, visitor, endpoint and HTTP status. Filter by status (errors, warnings, OK) or by type, or search by code, visitor, session or any text in the payload.
* **Detail** opens one request: the exact payload, each error or warning with the field it refers to and a suggested fix, and the **session trace**, every other request from the same session in order. The trace is the fastest way to see whether `identify` really happened before `conversion`.
* **Event types** shows the latest status per kind of request, so one glance tells you whether identify, track and conversion all work.

The HTTP status in each row is the one your app received. The SDK sends events in batches: `200` means every event in the batch was accepted, `207` means some were accepted and some were rejected (open the rejected ones to see why), and `500` means none were accepted.

## Every check, and how to fix it

**Errors** mean the event was rejected and not stored. **Warnings** mean it was stored, but something will make your data worse.

| Code | Kind | Why it happens | Fix |
| - | - | - | - |
| `event_not_found` | Error | `track()` used a code that is not in the workspace catalog. | Use a code from the catalog, or register yours in a [journey or feature](https://www.orwel.io/docs/guides/journeys-and-features.md). |
| `invalid_field` | Error | A field is missing or has the wrong type. The detail names the field. | Fix the value at the path shown, for example `properties.amount` as a number. |
| `missing_session_id` / `session_not_found` | Error | An event was sent before the SDK had a session. | Call `orwel.init()` on every page, before any `track`, `identify` or `conversion`. |
| `missing_session_data` | Error | A session request arrived without its session data. | Usually a custom integration posting by hand. Use the SDK, or send the full session object. |
| `invalid_json` | Error | The body is not valid JSON. | Same as above: let the SDK build the request. |
| `invalid_batch` | Error | The batch is malformed: `batchId`, `count` and `events` are required, and `count` must match. | Update the SDK, or fix the custom sender. |
| `payload_too_large` | Error | The request exceeds the size limit. | Do not send large blobs (HTML, base64 images) as properties. |
| `unsupported_type` | Error | The event type is not one of `track`, `identify`, `session` or `conversion`. | Use one of those types. |
| `code_format` | Warning | The event code is not snake\_case. | Use lowercase letters, digits and `_` only: `pricing_viewed`, not `Pricing Viewed`. |
| `conversion_no_email` | Warning | The conversion request itself carries no email or phone. | Pass `email` in the conversion properties. The server can still fall back to the email from an earlier `identify()`, but without any email the lead cannot be enriched, notified or sent to integrations. |
| `conversion_no_id` | Warning | The conversion has no `conversionId`, so retries cannot be deduplicated exactly. | Update the SDK: `npm i orwel@latest`. Versions before 1.6 do not send it. |
| `missing_attribution` | Warning | No UTM parameters or ad click ids (`gclid`, `gbraid`, `wbraid`, `fbclid`) reached the conversion, so the lead shows as direct. | Expected for direct visits. To test attribution, open the page with `?utm_source=test`. See [Attribution](https://www.orwel.io/docs/sdk/attribution.md). |
| `sdk_outdated` | Warning | The SDK version is older than the latest release. | `npm i orwel@latest`. The CDN script `orwel@1` updates by itself. |
| `identify_no_email` | Warning | `identify()` was called with traits but no email, so the visitor stays anonymous. | Pass `email`. The automatic identify the SDK sends with browser traits on the first visit is expected and does not trigger this warning. |
| `test_mode_integrations_skipped` | Info | The conversion was stored as a Dev lead. | Nothing to fix: no email or integration runs with the development key. |

## Common problems

> [accordion] Nothing arrives
>
> * The app is using the production API key, not the development key. Production traffic is not logged in Dev mode. Check the value in the browser: `window.orwel` exists and the requests to `api.orwel.io` carry a key that starts with `orwel_dev_`.
> * `orwel.init()` never runs: the env var is undefined in this environment (a missing `NEXT_PUBLIC_`, `PUBLIC_` or `VITE_` prefix is the usual cause), or the init code only runs on the server.
> * An ad blocker or a strict Content Security Policy blocks `api.orwel.io`. Try a private window without extensions, and allow `https://api.orwel.io` in `connect-src`.

> [accordion] The lead shows as direct even though I came from a campaign
>
> The UTMs have to be in the URL of a page where the SDK runs, before the conversion. Opening the page with `?utm_source=...` and converting in the same browser is enough. If the conversion row shows `missing_attribution`, open its session trace and check the first `session` request: its URL should contain the UTM parameters.

> [accordion] The same conversion appears twice
>
> Each conversion carries a `conversionId` and the server deduplicates on it, so a retried request creates one lead. Two rows with different ids mean the app called `conversion()` twice, for example from a submit handler and a success callback. Call it once, after the form is validated.

> [accordion] Events show up twice in development only
>
> React Strict Mode runs effects twice in development, so an `orwel.init()` inside `useEffect` runs twice. Guard it so it runs once; see [Install by framework](https://www.orwel.io/docs/guides/frameworks.md#initialize-once).

## Going live

**1. Clear the list**

Every request in Dev mode should be OK or carry only the test mode note.

**2. Swap the key in production**

Set the production API key in your production environment and deploy. Leave the development key in preview and local environments.

**3. Confirm with one real conversion**

Submit your form once on the live site. The lead appears without the **Dev** badge, the notification email arrives and your integrations receive it. From now on production traffic no longer appears in Dev mode.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.