# Request format

This page documents what your prebuilt-action webhook receives. The shape depends on **which surface in the widget fired the action**, not on the action name. There are two surfaces:

| Surface                                                                                  | Routing                                                                                                                                                                                       | Examples                                                                                                                                      |
| ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Server-proxied** — request goes through the `tab-fetch` or `tab-action` edge function. | Identity is verified server-side, `endUserId` / `endUserEmail` are appended to the URL, credential connectors are attached as auth headers, and `widget_issues` rows are recorded on failure. | `get_items`, `item_clicked`, the messaging composer (`create_item`), and any `update_item` / `delete_item` fired from inside `MessagingView`. |
| **Direct from browser** — widget calls your webhook with `fetch()`.                      | No identity is forwarded server-side, the credential connector is **not** applied, and your webhook must allow CORS from the widget origin.                                                   | `update_item` and `delete_item` when fired from the per-row menu or the generic detail screen.                                                |

The rest of this page documents the server-proxied surface, which is the well-behaved one. The direct surface uses a different body shape — see [Direct-from-browser body shape](#direct-from-browser-body-shape) at the bottom.

## Common query parameters

Every request — `GET` or `POST` — carries the following safe (non-PII) query parameters. These are appended by Oaura, not by you.

| Param          | Always present?           | Meaning                                                                                  |
| -------------- | ------------------------- | ---------------------------------------------------------------------------------------- |
| `action`       | Yes                       | One of `get_items`, `item_clicked`, `create_item`, `update_item`, `delete_item`.         |
| `agentId`      | Yes                       | UUID of the agent the visitor is talking to.                                             |
| `agentName`    | When set                  | The agent's display name.                                                                |
| `tabId`        | Yes                       | UUID of the tab row.                                                                     |
| `tabName`      | When set                  | The tab's display name.                                                                  |
| `sessionId`    | When the widget knows it  | The visitor's current widget session. Stable across reloads (localStorage).              |
| `endUserId`    | When identity is verified | The `sub` claim of the visitor's verified JWT. Stable across sessions for the same user. |
| `endUserEmail` | When the JWT contains it  | The visitor's verified email.                                                            |

For `get_items`, two extra params are appended:

| Param | Meaning                                                                                                               |
| ----- | --------------------------------------------------------------------------------------------------------------------- |
| `_ts` | Cache-buster, `Date.now()`. Forces URL-keyed caches (Cloudflare, nginx, n8n's own response cache) to miss every call. |

For POST actions (`create_item`, `update_item`, `delete_item`), one extra is added when the widget knows it:

| Param            | Meaning                                                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
| `conversationId` | The id of the conversation the action is happening inside, when relevant (e.g. sending a reply to thread X). |
| `firedAt`        | Optional ISO timestamp from the widget — when the user clicked. Useful for chronology in append-only stores. |

## What you do *not* get on the URL

PII never goes on the URL. The verified identity payload is forwarded by the widget as the `x-end-user-token` header to the edge function, but that token is **not** forwarded onward to your webhook in any form. The edge function decodes it server-side and only passes through `endUserId` and `endUserEmail` on the URL — both of which are values *you originally minted* in your own JWT (or that the JWKS issuer minted), so they aren't new information.

If your webhook needs more identity attributes than `endUserId` / `endUserEmail`, mint them into the JWT yourself; Oaura's edge functions verify them but don't propagate them.

## Method-by-method

### GET — `get_items`, `item_clicked`

No request body. Everything is on the URL.

```
GET https://your-webhook/path?action=get_items
  &agentId=…
  &agentName=…
  &tabId=…
  &tabName=…
  &sessionId=…
  &endUserId=…
  &endUserEmail=…
  &_ts=1715000000000
```

For `item_clicked`, `id` is also appended when the visitor clicked a row that has one:

```
GET https://your-webhook/path?action=item_clicked
  &agentId=…
  &tabId=…
  &id=conversation-or-item-uuid
  &sessionId=…
  &endUserId=…
  &endUserEmail=…
```

### POST — `create_item`, `update_item` (server-proxied path)

URL carries the metadata (same as above plus `conversationId`, `firedAt`). Body carries the `entry` the widget sent. The exact body shape depends on which internal call site fired the action:

| Caller                                               | Body shape                                                                                                                                                                                            |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Messaging composer (the only path for `create_item`) | `{ "kind": "message", "content": "<text the visitor typed>", "createdAt": "<iso>", "author": { "userId": "<jwt sub>", "partyId": "<viewer.partyId>", "display": "<name>", "email": "<jwt email>" } }` |
| `MessagingView` auto mark-read (`update_item`)       | `{ "type": "mark_read", "seenAt": "<iso>" }`                                                                                                                                                          |

`Content-Type: application/json`, `Accept: application/json`. Identity (`endUserId`, `endUserEmail`) is on the URL.

### POST — `delete_item` (server-proxied path)

Fired from inside `MessagingView` to delete a single message. URL carries the metadata; body is always:

```json
{ "id": "<entry-uuid>" }
```

## Authentication headers

If the action's `connector_id` is set, Oaura attaches a credential header before firing your webhook. Two credential shapes are supported:

| Credential type         | Header attached                                                                                                            |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `bearer_token`, `token` | `Authorization: Bearer <secret>`                                                                                           |
| `header_auth`           | `<custom-name>: <secret>` — the header name comes from the credential's `config.name`, defaulting to `X-API-Key` if unset. |

The actual secret is read from Supabase Vault at request time. It is never sent to the browser, never logged.

If no `connector_id` is set, no auth header is attached. Your webhook is expected to be either public or to use an opaque-URL shared-secret token in its path.

## Other headers Oaura always sends

| Header          | Value                                            |
| --------------- | ------------------------------------------------ |
| `Accept`        | `application/json`                               |
| `Cache-Control` | `no-cache, no-store, max-age=0` (on `get_items`) |
| `Pragma`        | `no-cache` (on `get_items`)                      |
| `Content-Type`  | `application/json` (on POST actions)             |

## Echoing identity into your tools

For most webhooks the URL params are enough. If you call downstream services that require a signed identity assertion, look at the `X-Oaura-Identity` header forwarded by the LLM connector path — but that header is sent to **tool calls**, not to prebuilt-action webhooks. See [Forwarded context](https://github.com/tillforty/oaura-documentation/blob/main/identity/forwarded-context.md) for the full distinction.

For prebuilt actions, trust `endUserId` from the query string. The edge function already verified it.

## Direct-from-browser body shape

When the action is fired by the per-row menu or the generic detail screen, the widget POSTs directly to your webhook with no edge function in between. The URL is exactly what you saved on the tab — no query parameters are appended. The body is a single JSON object:

```json
{
  "action":   "update_item" | "delete_item",
  "tabId":    "<uuid>",
  "tabName":  "<string>",
  "sessionId": "<widget session id>",
  "item":     { /* the full list row, or the item_clicked detail object */ },
  "firedAt":  "<iso timestamp from widget>",
  "id":       "<picked from item.id / item.uuid when available — detail-screen path only>"
}
```

Key differences from the server-proxied path:

* **No verified `endUserId` or `endUserEmail`.** The browser has no way to safely forward the signed JWT. If you need identity for authorisation, derive it from `item.id` (lookup-then-verify in your store) or from a session cookie your visitor's browser already carries to the webhook host.
* **No credential connector.** Any `connector_id` set on the action is ignored on this path. Your webhook is reached unauthenticated unless you front it with an auth that the browser can satisfy.
* **CORS required.** Your webhook must respond to `OPTIONS` and allow the widget iframe origin on the response's `Access-Control-Allow-Origin`.
* **No `widget_issues` recorded** on failure. The edge function never sees the call. Watch your own webhook logs.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.tillforty.com/oaura/tabs/request-format.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
