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

# Events mode — full integration

> Send PromptWall a copy of every prompt + answer for observability and audit. Lowest-friction integration.

<Note>
  **Events mode is the cheapest and easiest integration.** You keep calling
  the LLM yourself; you just send PromptWall a copy of each interaction
  *after* it happens. PromptWall does not block, rewrite, or enforce
  anything — it only logs, classifies, and powers the dashboard.
</Note>

## ⚡ 30-second integration

Three steps. Each step says exactly **where** the code goes — terminal
or a specific file.

<Tabs>
  <Tab title="🐍 Python">
    **Step 1 — In your terminal**, install the SDK:

    ```bash Terminal theme={null}
    pip install promptwall-sdk
    ```

    **Step 2 — Create a new file `test_promptwall.py`** in any folder.
    Paste this and replace `pk_live_xxxxxxxx` with your real key from
    [prompt-wall.com/settings → Apps → + New App → Events](https://www.prompt-wall.com/settings):

    ```python test_promptwall.py theme={null}
    import os
    os.environ["PROMPTWALL_API_KEY"] = "pk_live_xxxxxxxx"   # paste your real key

    from promptwall import PromptWall
    pw = PromptWall()

    # events.send takes a single dict (the event body).
    pw.events.send({
        "prompt":            "What is the capital of France?",
        "answer":            "Paris is the capital of France.",
        "model":             "gpt-4o-mini",
        "prompt_tokens":     8,
        "completion_tokens": 6,
    })
    print("event sent — check the dashboard")
    ```

    **Step 3 — Back in your terminal**, run it:

    ```bash Terminal theme={null}
    python test_promptwall.py
    ```

    Then open [prompt-wall.com/observability](https://www.prompt-wall.com/observability)
    — within \~5 seconds you'll see the event in **Recent Traces**.

    **Step 4 — Wire into your real LLM call.** Open whichever existing
    file holds your OpenAI / Anthropic call (commonly `app.py`,
    `services/chat.py`, `routes/chat.py`). Add **two lines** — one import,
    one fire-and-forget call **after** you return the answer to the user:

    ```python services/chat.py (your existing file — edit it) theme={null}
    from promptwall import PromptWall                 # ← line 1: import
    pw = PromptWall()                                  # singleton

    def answer(prompt, user_id):
        completion = openai_client.chat.completions.create(...)   # unchanged
        text = completion.choices[0].message.content
        return_to_user(text)                                       # unchanged

        pw.events.send({                               # ← line 2: log it
            "prompt":            prompt,
            "answer":            text,
            "model":             completion.model,
            "prompt_tokens":     completion.usage.prompt_tokens,
            "completion_tokens": completion.usage.completion_tokens,
            "user_id":           user_id,
        })
    ```

    The send call is fire-and-forget — it won't slow your app or break it
    if PromptWall is unreachable.
  </Tab>

  <Tab title="🟨 Node.js">
    **Step 1 — In your terminal**, in any folder:

    ```bash Terminal theme={null}
    npm init -y                 # only if you don't already have a package.json
    npm install @promptwall/node
    ```

    **Step 2 — Create a new file `test_promptwall.mjs`**. Paste this and
    replace `pk_live_xxxxxxxx`:

    ```js test_promptwall.mjs theme={null}
    process.env.PROMPTWALL_API_KEY = "pk_live_xxxxxxxx";   // paste your real key

    import { PromptWall } from '@promptwall/node';
    const pw = new PromptWall();

    // events.send is a passthrough — keys go to the API verbatim
    // (use snake_case to match the API contract).
    await pw.events.send({
      prompt:            "What is the capital of France?",
      answer:            "Paris is the capital of France.",
      model:             "gpt-4o-mini",
      prompt_tokens:     8,
      completion_tokens: 6,
    });
    console.log("event sent — check the dashboard");
    ```

    **Step 3 — Back in your terminal**:

    ```bash Terminal theme={null}
    node test_promptwall.mjs
    ```

    Then open [prompt-wall.com/observability](https://www.prompt-wall.com/observability)
    to see the event.

    **Step 4 — Wire into your real LLM call.** Open whichever existing
    file holds your OpenAI / Anthropic call (commonly `pages/api/chat.ts`,
    `app/api/chat/route.ts`, `src/services/llm.ts`). Add **two lines**:

    ```ts src/services/chat.ts (your existing file — edit it) theme={null}
    import { PromptWall } from '@promptwall/node';     // ← line 1
    const pw = new PromptWall();

    export async function answer(prompt: string, userId: string) {
      const completion = await openai.chat.completions.create({...});   // unchanged
      const text = completion.choices[0].message.content ?? '';
      returnToUser(text);                                                // unchanged

      void pw.events.send({                            // ← line 2 (don't await)
        prompt,
        answer:            text,
        model:             completion.model,
        prompt_tokens:     completion.usage?.prompt_tokens     ?? 0,
        completion_tokens: completion.usage?.completion_tokens ?? 0,
        user_id:           userId,
      });
    }
    ```

    The `void` keyword tells JS not to wait — fire-and-forget. Even if
    PromptWall is down, your app keeps running normally.
  </Tab>

  <Tab title="🔧 cURL">
    **No file to create.** Just paste this in your terminal — replace
    `pk_live_xxxxxxxx`:

    ```bash Terminal theme={null}
    curl https://api.prompt-wall.com/v1/events \
      -H "Authorization: Bearer pk_live_xxxxxxxx" \
      -H "Content-Type: application/json" \
      -d '{
        "prompt": "What is the capital of France?",
        "answer": "Paris is the capital of France.",
        "model":  "gpt-4o-mini",
        "prompt_tokens": 8,
        "completion_tokens": 6
      }'
    ```

    You'll get `{"ok": true, "request_id": "..."}` back, and the event
    will appear in
    [prompt-wall.com/observability](https://www.prompt-wall.com/observability)
    within seconds.
  </Tab>
</Tabs>

<Note>
  **Don't have an API key yet?** Sign up at
  [prompt-wall.com/signup](https://www.prompt-wall.com/signup) (free —
  \$50 of credits), then click **+ New App** in Settings and pick mode
  **Events**. Copy the `pk_live_…` key shown on the final step.
</Note>

The rest of this page covers `user_id` / `session_id` / `metadata`
for richer dashboards, multi-language clients (Go, Ruby, Java, .NET,
PHP), and failure-mode handling. Skip ahead only if you need them.

***

## When this mode is right for you

<CardGroup cols={2}>
  <Card title="✅ Pick Events when…" icon="check">
    * You need observability + audit trail with **zero risk to production
      latency or behaviour**
    * You're proving compliance / SOC 2 readiness
    * You want to start collecting data **before** you commit to enforcement
    * Your LLM stack is locked-in (Bedrock, Azure, OpenAI Enterprise) and
      you can't insert a proxy
  </Card>

  <Card title="❌ Don't pick Events if…" icon="xmark">
    * You want PromptWall to **block or rewrite** unsafe answers — that's
      [Verify](/modes/verify) or [Full Control](/modes/full-control)
    * You want low-latency *pre-flight* injection detection — Verify is better
    * You want a single API to call instead of two — Full Control collapses
      everything into one request
  </Card>
</CardGroup>

**Pricing: \$30 per 1,000,000 tokens.** No setup fee. Counted from the
`prompt_tokens + completion_tokens` you send us.

***

## What you'll build

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant App as Your app
    participant LLM as Your LLM (OpenAI / Anthropic / etc.)
    participant PW as PromptWall

    App->>LLM: 1. Normal request
    LLM-->>App: 2. Normal response
    App-->>App: 3. Return to user (uninterrupted)
    App->>PW: 4. POST /v1/events  (fire-and-forget)
    PW-->>App: 5. 202 Accepted
```

The PromptWall call is **fire-and-forget** and runs **after** you've
already returned the answer. It never blocks your user-visible latency.

***

## Choose your integration

<Tabs>
  <Tab title="🐍 Python SDK">
    ### Step 1 — Install the SDK

    Run in your project root:

    ```bash theme={null}
    pip install promptwall-sdk
    ```

    If you use Poetry / uv / Pipenv, run the equivalent. The SDK has zero
    heavy dependencies (just `httpx`).

    ### Step 2 — Add API key to your environment

    <Note>
      **Create new file: `.env`** (in your project root, next to `package.json`
      or `pyproject.toml`). If `.env` already exists, **add** the line below.
      Add `.env` to `.gitignore` if it isn't already.
    </Note>

    ```bash .env theme={null}
    PROMPTWALL_API_KEY=pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ```

    Get the key from [prompt-wall.com/settings → Apps tab](https://www.prompt-wall.com/settings).
    Click **+ New App** if you don't have one yet, choose mode **Events**,
    and copy the key shown on the final step (it's only displayed once).

    ### Step 3 — Create a thin wrapper around your LLM call

    <Note>
      **Create new file: `lib/promptwall_client.py`** (or wherever you keep
      shared infrastructure code).
    </Note>

    ```python lib/promptwall_client.py theme={null}
    import os
    from promptwall import PromptWall

    # Singleton — initialise once at module load. The SDK is
    # thread-safe and reuses a single connection pool.
    _pw = PromptWall(api_key=os.environ["PROMPTWALL_API_KEY"])

    def log_interaction(*, prompt: str, answer: str, model: str,
                        prompt_tokens: int, completion_tokens: int,
                        user_id: str | None = None,
                        session_id: str | None = None,
                        metadata: dict | None = None) -> None:
        """Fire-and-forget. Never raises — Events mode is purely
        observability and must not affect production code paths."""
        try:
            _pw.events.send(
                prompt=prompt,
                answer=answer,
                model=model,
                prompt_tokens=prompt_tokens,
                completion_tokens=completion_tokens,
                user_id=user_id,
                session_id=session_id,
                metadata=metadata or {},
            )
        except Exception as exc:
            # Log but don't propagate. Events mode is not in the critical path.
            print(f"[promptwall] events.send failed: {exc}")
    ```

    ### Step 4 — Wire into your existing LLM call

    <Note>
      **Edit existing file: wherever you call OpenAI / Anthropic / etc.**
      Common locations: `app.py`, `main.py`, `services/chat.py`,
      `routes/chat.py`. Find the place you receive the LLM response.
    </Note>

    Before:

    ```python services/chat.py (before) theme={null}
    from openai import OpenAI
    client = OpenAI()

    def answer(prompt: str, user_id: str) -> str:
        completion = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        return completion.choices[0].message.content
    ```

    After (3 lines added):

    ```python services/chat.py (after) theme={null}
    from openai import OpenAI
    from lib.promptwall_client import log_interaction   # ← added

    client = OpenAI()

    def answer(prompt: str, user_id: str) -> str:
        completion = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
        )
        text = completion.choices[0].message.content

        log_interaction(                                 # ← added
            prompt=prompt,
            answer=text,
            model=completion.model,
            prompt_tokens=completion.usage.prompt_tokens,
            completion_tokens=completion.usage.completion_tokens,
            user_id=user_id,
        )

        return text
    ```

    That's the entire change. Restart the app and the next request triggers
    an Events call.

    ### Step 5 — Verify it worked

    Run a request through your app, then open
    [prompt-wall.com/observability](https://www.prompt-wall.com/observability).

    Within \~5 seconds you should see:

    * **Requests** counter ticked up
    * A new row in **Recent Traces** showing your prompt/answer
    * The **Events** mode counted in the breakdown

    Or hit the API directly to confirm:

    ```bash theme={null}
    curl https://api.prompt-wall.com/api/console/requests?limit=1 \
      -H "Authorization: Bearer $PROMPTWALL_JWT"
    ```

    ### Step 6 — Deploy to production

    Set `PROMPTWALL_API_KEY` as a secret in your hosting platform:

    | Platform                         | Where to set it                                           |
    | -------------------------------- | --------------------------------------------------------- |
    | **Vercel**                       | Project → Settings → Environment Variables                |
    | **Render**                       | Service → Environment → Add Environment Variable          |
    | **Fly.io**                       | `fly secrets set PROMPTWALL_API_KEY=pk_...`               |
    | **AWS Lambda**                   | Function → Configuration → Environment variables          |
    | **Heroku**                       | `heroku config:set PROMPTWALL_API_KEY=pk_...`             |
    | **Railway / Cloudflare Workers** | Variables panel in the dashboard                          |
    | **Docker**                       | `--env` flag or `docker-compose.yml` `environment:` block |

    Restart / redeploy after setting it.
  </Tab>

  <Tab title="🟨 Node.js SDK">
    ### Step 1 — Install the SDK

    ```bash theme={null}
    npm install @promptwall/node
    # or: yarn add @promptwall/node
    # or: pnpm add @promptwall/node
    ```

    ### Step 2 — Add API key to your environment

    <Note>
      **Create new file: `.env`** (in your project root). If it already
      exists, **add** the line below. Make sure `.env` is in your
      `.gitignore`.
    </Note>

    ```bash .env theme={null}
    PROMPTWALL_API_KEY=pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ```

    If you use Next.js, name the file `.env.local`. If you use a build
    system that doesn't auto-load `.env`, use `dotenv`:

    ```bash theme={null}
    npm install dotenv
    ```

    ```js (top of your entry file, e.g. server.ts) theme={null}
    import 'dotenv/config';
    ```

    ### Step 3 — Create a thin wrapper

    <Note>
      **Create new file: `lib/promptwall.ts`** (or `lib/promptwall.js` if
      not on TypeScript). Place it next to your other shared utilities.
    </Note>

    ```ts lib/promptwall.ts theme={null}
    import { PromptWall } from '@promptwall/node';

    // Singleton — instantiate once at module load.
    const pw = new PromptWall({ apiKey: process.env.PROMPTWALL_API_KEY! });

    export interface LogInteractionInput {
      prompt: string;
      answer: string;
      model: string;
      promptTokens: number;
      completionTokens: number;
      userId?: string;
      sessionId?: string;
      metadata?: Record<string, unknown>;
    }

    export async function logInteraction(input: LogInteractionInput): Promise<void> {
      // Fire-and-forget. Never throws — Events mode is purely
      // observability and must not affect production code paths.
      try {
        await pw.events.send(input);
      } catch (err) {
        console.warn('[promptwall] events.send failed:', err);
      }
    }
    ```

    ### Step 4 — Wire into your existing LLM call

    <Note>
      **Edit existing file: wherever you call OpenAI / Anthropic / etc.**
      Common locations: `pages/api/chat.ts`, `app/api/chat/route.ts`,
      `server/routes/chat.js`, `src/services/llm.ts`.
    </Note>

    Before:

    ```ts src/services/chat.ts (before) theme={null}
    import OpenAI from 'openai';
    const openai = new OpenAI();

    export async function answer(prompt: string, userId: string) {
      const completion = await openai.chat.completions.create({
        model: 'gpt-4o-mini',
        messages: [{ role: 'user', content: prompt }],
      });
      return completion.choices[0].message.content;
    }
    ```

    After (4 lines added):

    ```ts src/services/chat.ts (after) theme={null}
    import OpenAI from 'openai';
    import { logInteraction } from '../lib/promptwall';   // ← added

    const openai = new OpenAI();

    export async function answer(prompt: string, userId: string) {
      const completion = await openai.chat.completions.create({
        model: 'gpt-4o-mini',
        messages: [{ role: 'user', content: prompt }],
      });
      const text = completion.choices[0].message.content ?? '';

      // Fire-and-forget — don't await. The user already has their answer.
      void logInteraction({                                // ← added
        prompt, answer: text, model: completion.model,
        promptTokens: completion.usage?.prompt_tokens ?? 0,
        completionTokens: completion.usage?.completion_tokens ?? 0,
        userId,
      });

      return text;
    }
    ```

    ### Step 5 — Verify it worked

    Run a request, then open
    [prompt-wall.com/observability](https://www.prompt-wall.com/observability).
    You should see the request appear within \~5 seconds.

    ### Step 6 — Deploy to production

    Set the env var on your platform (see the Python tab for platform-specific
    instructions — they're identical for Node.js).
  </Tab>

  <Tab title="🔧 cURL / raw HTTP">
    Use this if your runtime doesn't have an official SDK (Go, Ruby, Java,
    PHP, .NET, Rust, etc.) or if you want to drop into pure HTTP for
    debugging.

    ### Step 1 — Get your API key

    [prompt-wall.com/settings → Apps tab → + New App](https://www.prompt-wall.com/settings) →
    mode **Events** → copy the `pk_live_…` key.

    ### Step 2 — Send a request

    ```bash theme={null}
    curl -X POST https://api.prompt-wall.com/v1/events \
      -H "Authorization: Bearer pk_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "prompt": "What is the capital of France?",
        "answer": "Paris is the capital of France.",
        "model": "gpt-4o-mini",
        "prompt_tokens": 8,
        "completion_tokens": 6,
        "user_id": "user_42",
        "session_id": "sess_abc123",
        "metadata": {"app_version": "2.1.0", "feature": "qna"}
      }'
    ```

    ### Step 3 — Expected response

    ```json theme={null}
    {
      "ok": true,
      "request_id": "req_8f2d4a9b1c3e7f5a",
      "received_at": "2026-05-03T12:34:56Z"
    }
    ```

    The endpoint returns **202 Accepted** if the body is well-formed. The
    analysis happens asynchronously and lands in the dashboard within a few
    seconds.

    ### Step 4 — Required vs optional fields

    <Note>
      **You only send what you have.** Do **not** invent fields like
      `request_id`, `cost_usd`, `latency_ms`, `risk_score`, or
      `final_decision` — those are computed by PromptWall on the server
      and stored on the trace. Sending them yourself does nothing.
    </Note>

    #### Fields YOU send (request body)

    | Field               | Required | Where it comes from                                                                                            |
    | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
    | `prompt`            | ✅        | The user's input — the same string you passed to the LLM.                                                      |
    | `answer`            | ✅        | The LLM's response — `completion.choices[0].message.content` (OpenAI) / `message.content[0].text` (Anthropic). |
    | `model`             | ✅        | The model name your LLM SDK returned (e.g. `completion.model`).                                                |
    | `prompt_tokens`     | ✅        | From `completion.usage.prompt_tokens` — your LLM SDK already gave you this.                                    |
    | `completion_tokens` | ✅        | From `completion.usage.completion_tokens` — same.                                                              |
    | `user_id`           | optional | Your stable user ID (UUID, email, anything consistent across requests).                                        |
    | `session_id`        | optional | Your conversation ID — same value across all turns of one chat.                                                |
    | `metadata`          | optional | Free-form JSON for filtering (`{"feature": "...", "tier": "..."}`).                                            |

    #### Fields PromptWall RETURNS (response body) — do not send these yourself

    | Field            | What it is                                                                          |
    | ---------------- | ----------------------------------------------------------------------------------- |
    | `request_id`     | Unique trace ID we generate. Use it in support tickets.                             |
    | `cost_usd`       | Computed at the per-mode rate (\$30/M for Events) × tokens.                         |
    | `latency_ms`     | Time from when we received your request to when we finished classification.         |
    | `received_at`    | ISO timestamp.                                                                      |
    | `risk_score`     | 0–1, computed by our classifiers. Visible in /traces.                               |
    | `final_decision` | `pass` for Events (Events never blocks). For Verify it's `allow`/`block`/`rewrite`. |
  </Tab>

  <Tab title="📦 Other languages">
    Any language that can `POST` JSON over HTTPS works. The endpoint is
    identical to the cURL example.

    ### Go

    <Note>
      **Create new file: `internal/promptwall/client.go`**.
    </Note>

    ```go internal/promptwall/client.go theme={null}
    package promptwall

    import (
        "bytes"
        "encoding/json"
        "net/http"
        "os"
        "time"
    )

    var httpClient = &http.Client{Timeout: 5 * time.Second}

    type Event struct {
        Prompt           string                 `json:"prompt"`
        Answer           string                 `json:"answer"`
        Model            string                 `json:"model"`
        PromptTokens     int                    `json:"prompt_tokens"`
        CompletionTokens int                    `json:"completion_tokens"`
        UserID           string                 `json:"user_id,omitempty"`
        SessionID        string                 `json:"session_id,omitempty"`
        Metadata         map[string]interface{} `json:"metadata,omitempty"`
    }

    func Send(e Event) error {
        body, err := json.Marshal(e)
        if err != nil { return err }
        req, _ := http.NewRequest("POST",
            "https://api.prompt-wall.com/v1/events",
            bytes.NewReader(body))
        req.Header.Set("Authorization", "Bearer "+os.Getenv("PROMPTWALL_API_KEY"))
        req.Header.Set("Content-Type", "application/json")
        resp, err := httpClient.Do(req)
        if err != nil { return err }
        resp.Body.Close()
        return nil
    }
    ```

    ### Ruby

    <Note>
      **Create new file: `app/services/prompt_wall.rb`** (Rails) or
      `lib/prompt_wall.rb` (anything else).
    </Note>

    ```ruby app/services/prompt_wall.rb theme={null}
    require 'net/http'
    require 'json'

    class PromptWall
      ENDPOINT = URI('https://api.prompt-wall.com/v1/events')

      def self.send_event(prompt:, answer:, model:, prompt_tokens:,
                           completion_tokens:, user_id: nil, session_id: nil,
                           metadata: nil)
        http = Net::HTTP.new(ENDPOINT.host, ENDPOINT.port)
        http.use_ssl = true
        http.read_timeout = 5

        req = Net::HTTP::Post.new(ENDPOINT.path,
          'Authorization' => "Bearer #{ENV['PROMPTWALL_API_KEY']}",
          'Content-Type'  => 'application/json',
        )
        req.body = {
          prompt: prompt, answer: answer, model: model,
          prompt_tokens: prompt_tokens, completion_tokens: completion_tokens,
          user_id: user_id, session_id: session_id, metadata: metadata,
        }.compact.to_json
        http.request(req)
      rescue StandardError => e
        Rails.logger.warn "[promptwall] failed: #{e.message}" if defined?(Rails)
      end
    end
    ```

    ### Java (Spring / plain)

    <Note>
      **Create new file: `src/main/java/com/yourapp/promptwall/PromptWallClient.java`**.
    </Note>

    ```java theme={null}
    package com.yourapp.promptwall;

    import java.net.URI;
    import java.net.http.HttpClient;
    import java.net.http.HttpRequest;
    import java.net.http.HttpResponse;
    import java.time.Duration;
    import com.fasterxml.jackson.databind.ObjectMapper;

    public class PromptWallClient {
        private static final HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5)).build();
        private static final ObjectMapper mapper = new ObjectMapper();
        private static final String API_KEY = System.getenv("PROMPTWALL_API_KEY");

        public static void send(java.util.Map<String, Object> event) {
            try {
                String body = mapper.writeValueAsString(event);
                HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create("https://api.prompt-wall.com/v1/events"))
                    .header("Authorization", "Bearer " + API_KEY)
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(body))
                    .build();
                client.sendAsync(req, HttpResponse.BodyHandlers.discarding());
            } catch (Exception e) {
                System.err.println("[promptwall] " + e.getMessage());
            }
        }
    }
    ```

    ### .NET (C#)

    <Note>
      **Create new file: `Services/PromptWallClient.cs`**.
    </Note>

    ```csharp Services/PromptWallClient.cs theme={null}
    using System.Net.Http.Json;

    public class PromptWallClient {
        private static readonly HttpClient http = new() {
            BaseAddress = new Uri("https://api.prompt-wall.com"),
            Timeout = TimeSpan.FromSeconds(5),
        };
        private static readonly string ApiKey =
            Environment.GetEnvironmentVariable("PROMPTWALL_API_KEY")!;

        public static async Task SendAsync(object evt) {
            try {
                var req = new HttpRequestMessage(HttpMethod.Post, "/v1/events") {
                    Content = JsonContent.Create(evt),
                };
                req.Headers.Add("Authorization", $"Bearer {ApiKey}");
                await http.SendAsync(req);
            } catch (Exception ex) {
                Console.Error.WriteLine($"[promptwall] {ex.Message}");
            }
        }
    }
    ```

    ### PHP

    <Note>
      **Create new file: `src/PromptWall.php`** (or place in your existing
      `Services/` namespace). Requires `guzzlehttp/guzzle`:
      `composer require guzzlehttp/guzzle`.
    </Note>

    ```php src/PromptWall.php theme={null}
    <?php
    namespace App;

    use GuzzleHttp\Client;

    class PromptWall {
        private static ?Client $client = null;

        public static function send(array $event): void {
            try {
                self::$client ??= new Client([
                    'base_uri' => 'https://api.prompt-wall.com',
                    'timeout'  => 5.0,
                ]);
                self::$client->post('/v1/events', [
                    'headers' => [
                        'Authorization' => 'Bearer ' . getenv('PROMPTWALL_API_KEY'),
                        'Content-Type'  => 'application/json',
                    ],
                    'json' => $event,
                ]);
            } catch (\Throwable $e) {
                error_log('[promptwall] ' . $e->getMessage());
            }
        }
    }
    ```
  </Tab>
</Tabs>

***

## Common patterns

### Multi-turn conversations

Pass a stable `session_id` on every event in a single conversation so
PromptWall can replay the entire thread on
[/sessions](https://www.prompt-wall.com/sessions):

```python theme={null}
log_interaction(
    prompt=user_message,
    answer=assistant_message,
    model="gpt-4o",
    prompt_tokens=250,
    completion_tokens=80,
    session_id=conversation.id,   # same UUID on every turn
    user_id=current_user.id,
)
```

### Per-environment splitting

Create one **App** per environment (dev / staging / prod) in
[Settings → Apps](https://www.prompt-wall.com/settings). Each gets its
own API key. Use the right key per environment so traces don't bleed
together.

```bash theme={null}
# .env.production
PROMPTWALL_API_KEY=pk_live_prod_xxxxxxxx

# .env.staging
PROMPTWALL_API_KEY=pk_live_stg_yyyyyyyy
```

### Custom metadata for filtering

Anything you put in `metadata` is searchable in the dashboard:

```python theme={null}
log_interaction(
    prompt=...,
    answer=...,
    model=...,
    prompt_tokens=...,
    completion_tokens=...,
    metadata={
        "feature": "summarize-pdf",
        "tier":    "enterprise",
        "region":  "eu-west",
        "version": "2.4.1",
    },
)
```

Then on /traces filter by `metadata.feature = "summarize-pdf"` to see
only those traces.

***

## What you'll see in the dashboard

Within seconds of your first event:

* **/observability** — KPIs (requests, tokens, cost), decisions chart, breakdown table
* **/traces** — drill-down on individual prompts + answers
* **/sessions** — multi-turn replay (if `session_id` is set)
* **/billing** — credit consumed at \$30/M tokens for events

Events traces are flagged with the **Events** mode badge so they're
easy to distinguish from `/v1/verify` and `/v1/chat` traffic.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Add Verify mode" icon="badge-check" href="/modes/verify">
    Get a real allow/block decision before returning the answer to your
    user. Same SDK, one extra method call.
  </Card>

  <Card title="Configure policies" icon="shield" href="https://www.prompt-wall.com/policies">
    Define what counts as PII / brand-safety / off-topic for your
    tenant. Policies apply across all modes.
  </Card>
</CardGroup>
