When a view's URL is configured under **View settings → Configure URL** and **URL Active** is on, the rendered view loads custom data from your endpoint. This page lists exactly what your endpoint receives and is allowed to return.

## Request

Interface Builder calls your endpoint **server-side** through its own backend, not directly from the user's browser. The request always goes out as the OneClick app, signed with three reserved system headers.

### Method

| Method | Body sent? | Use when |
|---|---|---|
| **GET** *(default)* | No | The endpoint expects context as URL query parameters only. |
| **POST** | Yes | The endpoint expects a JSON body with view context. |

Configure under **HTTP Method** in the **URL Configuration** dialog.

### URL

The URL you write in the **End-point URL** field is processed before the request goes out:

1. **Placeholder substitution.** Any `{{deal.title}}`, `{{person.name}}`, `{{user.id}}`, `{{variable.api_key}}`, or `{{searchparams.tier}}` token is replaced with its resolved value. If the URL contains Pipedrive field placeholders, the builder waits for the Pipedrive fetch to finish first; otherwise it processes the URL immediately.
2. **Query parameters appended.** Any **Query parameters** rows you added in the dialog, plus every search parameter from the user's current view URL, are appended as `?key=value` pairs.
3. **Whitespace warning.** If the resolved URL still contains `%20` in the path (typically because a placeholder resolved to an empty value), the builder shows a `URL contains whitespace in path` toast and still sends the request.

### Headers

Every request carries:

| Header | Source | Notes |
|---|---|---|
| `x-oc-user-id` | The Pipedrive user who opened the view | Always set, cannot be overridden. |
| `x-oc-company-id` | The Pipedrive company | Always set, cannot be overridden. |
| `x-oc-event-id` | A short unique ID for this view-open event | Always set, cannot be overridden. Use it to correlate logs. |
| OneClick-level headers | The OneClick button's own **Headers** toggle (Pipedrive's Edit a Button page) | Sent on every request when the toggle is on. |
| Per-call headers | The dialog's **Manage Headers** rows on this view | Values accept `{{variable.*}}` placeholders only. |

Per-call header keys cannot collide with the three reserved keys above (`x-oc-user-id`, `x-oc-company-id`, `x-oc-event-id`) — those are dropped before the request goes out. Other `x-oc-*` keys are not reserved and pass through. See [OneClick-Level Headers](/docs/buttons/interface-button/interface-builder/explanation/architecture#oneclick-level-headers) for the merge order.

### POST body

```json
{
  "data": {
    "view_name": "Account Summary"
  },
  "meta": {
    "button_id": "btn-uuid",
    "company_id": 6789,
    "event_id": "aBcDeF",
    "interface_view_id": "view-shortid",
    "resource": "deal",
    "resource_id": 1234,
    "user_id": 12345
  }
}
```

- `data.view_name` — the view's name. Useful when one endpoint serves several views and needs to switch on which view is asking.
- `meta` — the same context block used by form submissions, so a single endpoint can read both flows the same way.

### POST body from a floating window button

A [floating window button](/docs/buttons/interface-button/interface-builder/explanation/floating-window-buttons) has no Pipedrive record, so `resource` and `resource_id` are sent as `null`. Both keys stay in place, which keeps the body shape identical on both kinds of button:

```json
{
  "data": {
    "view_name": "Account Summary"
  },
  "meta": {
    "button_id": "btn-uuid",
    "company_id": 6789,
    "event_id": "aBcDeF",
    "interface_view_id": "view-shortid",
    "resource": null,
    "resource_id": null,
    "user_id": 12345
  }
}
```

A missing record is `null` — never `0`, never `""`.

### POST body with **Omit oneclick metadata** on

The body collapses to an empty object:

```json
{}
```

Use this only when the endpoint rejects unknown fields — e.g. Pipedrive API v2.

### GET request

No request body. Instead, the same context that POST sends in the body is split between the URL and the headers:

| Source | Goes to | Format |
|---|---|---|
| `data.<key>` | URL query string | `?data_<key>=<value>` |
| `meta.<key>` | Request headers | `x-oc-meta-<key>: <value>` (kebab-cased) |
| User / company / event IDs | Request headers | `x-oc-user-id`, `x-oc-company-id`, `x-oc-event-id` |

Example URL after substitution:

```
https://api.example.com/dashboard?data_view_name=Account%20Summary
```

Example headers — every `meta` key becomes its own header, so all seven are there:

```
x-oc-user-id: 12345
x-oc-company-id: 6789
x-oc-event-id: aBcDeF
x-oc-meta-button-id: btn-uuid
x-oc-meta-company-id: 6789
x-oc-meta-event-id: aBcDeF
x-oc-meta-interface-view-id: view-shortid
x-oc-meta-resource: deal
x-oc-meta-resource-id: 1234
x-oc-meta-user-id: 12345
```

### GET headers from a floating window button

Headers cannot carry `null`, and the string `null` would be a lie. So on a [floating window button](/docs/buttons/interface-button/interface-builder/explanation/floating-window-buttons) the two record headers are **not sent at all** — five `x-oc-meta-*` headers arrive instead of seven:

```
x-oc-user-id: 12345
x-oc-company-id: 6789
x-oc-event-id: aBcDeF
x-oc-meta-button-id: btn-uuid
x-oc-meta-company-id: 6789
x-oc-meta-event-id: aBcDeF
x-oc-meta-interface-view-id: view-shortid
x-oc-meta-user-id: 12345
```

Read the absence of the header as "there is no record". Nothing is sent as `0` or an empty header value.

With **Omit oneclick metadata** on, the `data_*` query params and the `x-oc-meta-*` headers are skipped — only the three reserved system headers remain.

## Response

The endpoint's response is parsed as JSON and stored under the active event ID. The view's runtime polls for it and renders once the parsed object is available.

### Plain data (typical case)

Return any JSON object. Every top-level field becomes a `{{custom.<field>}}` placeholder in the Data Picker's **Custom** tab.

```json
{
  "invoice_count": 3,
  "balance_due": 4250.5,
  "next_invoice": { "id": "INV-123", "due_date": "2026-06-01" }
}
```

The Data Picker exposes `{{custom.invoice_count}}`, `{{custom.balance_due}}`, `{{custom.next_invoice.id}}`, and so on.

### Error message

Return an `oneclick_data.error_message` envelope and the view shows a full-page message view instead of rendering blocks:

```json
{
  "oneclick_data": {
    "error_message": {
      "title": "Account not found",
      "message": "We couldn't find that account. <a href=\"/accounts\">Choose another</a>."
    }
  }
}
```

The string form `"error_message": "Something went wrong"` works too. For a note that is not an error, send the same object as `message_view`. See [HTTP Response Format → Error response](/docs/buttons/interface-button/interface-builder/reference/data-binding/http-response-format#error-response) for list messages, the optional `variant`, `message_view`, and the `hideRetry` and `hideBack` flags.

### Redirect

Send the user to a different view of the same interface:

```json
{
  "oneclick_data": {
    "redirect_view": {
      "id": "target-view-id",
      "params": { "deal_id": "1234" }
    }
  }
}
```

The view shows a "Redirecting…" toast and navigates to the target. If `id` does not exist, it falls back to a full-page message view with `View with ID … was not found`.

See [HTTP Response Format](/docs/buttons/interface-button/interface-builder/reference/data-binding/http-response-format) for the full schema.

## Timeout and status handling

| Condition | What the user sees |
|---|---|
| Endpoint returns a parsed JSON object inside ~20 s | View renders with the data, `{{custom.*}}` placeholders resolved. |
| Endpoint returns HTTP status `400` or higher | Toast: `Data loading failed (status N)` plus whatever response body landed (rendered against the partial data). |
| Endpoint takes longer than ~20 s | Toast: `Request timed out. No custom data received.` View renders without the custom data. |
| Endpoint throws a network error | Full-page message view with the underlying error message. |

The 20-second budget is a polling cap on the builder side, not a server-side fetch timeout. The actual server-side request to your endpoint runs as long as your endpoint takes; the polling layer just stops listening for a response after ~20 seconds.

## Where this runs

The fetch is **only** triggered when the view runs in live rendering (what real Pipedrive users see) or in the builder's **Test** mode. **Preview** mode does not call the data source URL — it renders against the saved **Sample data** instead. See [External Data Flow](/docs/buttons/interface-button/interface-builder/explanation/external-data-flow) for the load order, the parallel fetches, and the Test-mode overrides.
