# HTTP Request

Call an outside API over HTTPS and keep its response for later steps and conditions. Two things to know before using it today: placeholders in the URL and body are not resolved, and production had no host on its outbound allow-list at the time of writing — see Limits.

## Getting here

**Automate → Workflows**, open a workflow, click **+** on the canvas, then pick **HTTP Request** under **Add Steps**.

## Before you start

- An **owner**, **admin** or **agent** can add this step and save the workflow. Publishing that workflow — the thing that makes it run on live conversations — is an **owner** or **admin** action.
- Building and running workflows depends on your plan; see [pricing](/pricing).
- The destination must be a public HTTPS endpoint whose full URL you can type in — not a template, since placeholders here are not resolved; see Limits. In production, that host also needs to already be on the platform's outbound allow-list, which was empty at the time of writing (see Limits) — file a ticket under **Settings → Help & support** to ask for a host to be added.

## Set up

1. Open the workflow and click **+** where the call belongs.
2. Pick **HTTP Request**.
3. Choose **GET** or **POST**, and set the request **URL** — the drawer offers the variable picker here, but see Limits before relying on it.
4. Optional: add **Headers**, and, on POST, a JSON **Body** — the drawer offers placeholders here too, with the same caveat.
5. Set **Save response as** to name where the answer lands (default `http`).
6. Save the step, then publish the workflow. A saved but unpublished workflow calls nothing.

## Fields

| Field | What it means | Default | Limits |
| --- | --- | --- | --- |
| Method | GET or POST. | POST | — |
| Request URL | The endpoint to call. | — | Must start with `https://` (checked when you save) and use the default port (checked only when the step runs). The variable picker is offered but `{{…}}` text is sent exactly as typed — see Limits. |
| Headers | Extra request headers, as name/value pairs. | — | — |
| Body (JSON · sent as application/json) | The POST body, written as JSON. | — | Shown only for POST. Same placeholder caveat as the URL. |
| Save response as | The name later steps use to read the response, e.g. `{{http.status}}`, `{{http.data.tracking_no}}`. | http | Give a second lookup on the same run its own name, or it overwrites the first. |

## How it works

- What leaves Inchat: the method, URL, headers and body exactly as saved on the step — with one significant exception. `{{…}}` placeholders in the URL and the body are NOT resolved before the request is sent: they go out as the literal text `{{answer.text}}`, not the customer's actual answer. This is a product bug, not a design choice — the drawer's variable picker still offers them on both fields, implying they work.
- A fixed `user-agent: InchatWorkflow/1.0` header is always attached, in addition to anything set under Headers.
- When it leaves: the instant this step runs in the flow, synchronously — the flow waits for the response (or a 10-second timeout) before moving to the next step.
- The response — its status code, its parsed JSON body under `.data` when the response declares JSON, and the raw text under `.text`, capped at 32 KB — is written into the run's context under the name you gave **Save response as** BEFORE the step is judged to have succeeded or failed. A later step can read `{{http.status}}` or branch on `http.ok` regardless of which way this step went.
- A non-2xx response does not throw the response away: the step is recorded failed, but `{{http.status}}` and whatever body came back are still there for the next step to read — so a workflow can ask a courier for a tracking number, get a 404, and have the next step say "we couldn't find that order" itself instead of the whole run dying silently.
- A redirect (3xx) is refused rather than followed, and treated the same as a failed call — the URL must already be the final one; a hop this code did not check itself would defeat the checks below.

## When it fails

- The URL does not start with `https://`: the drawer refuses to save it with **HTTP request URL must be https://…**. A URL on a non-default port saves, then fails the run with **non-default HTTPS ports are blocked**.
- The hostname literally matches a private or loopback pattern (`localhost`, `10.x`, `192.168.x`, `172.16–31.x`, `169.254.x`, `0.x`, `::1`): the step fails immediately, nothing is sent. This is a pattern match on the hostname text, not a DNS lookup — a public domain name that happens to resolve to a private address is not caught here.
- The host is not on Inchat's outbound allow-list, which production requires: the step fails before anything is sent, with **host <host> is not on WORKFLOW_HTTP_ALLOWLIST** or, while the list is empty, **WORKFLOW_HTTP_ALLOWLIST is required for workflow HTTP requests**. At the time of writing, the list was empty in production. Getting a host allowed needs the platform side changed — file a ticket under **Settings → Help & support**; there is no workspace setting for it.
- The endpoint answers with a redirect: the step fails with **the endpoint redirected (<code>); point the action at the final URL**.
- The endpoint answers with a 4xx or 5xx status: the step fails with **HTTP <status>**, and the response is still captured — see How it works.
- The request times out (10 seconds) or the endpoint cannot be reached at all: the step fails with the transport error.
- A failed call does not stop the run: the step is recorded failed and the flow moves straight to the next one, which can still read whatever the response bound into the context.

## Limits

- **`{{…}}` placeholders in the URL and the body are not resolved.** Whatever you type there — including a variable picked from the picker — is sent character-for-character. A URL written as `…/track/{{answer.text}}` calls that literal, percent-encoded string, not the customer's answer. There is no way around this today; do not rely on this step for anything that needs a per-conversation value in the URL or body.
- **At the time of writing, production's allow-list was empty**, so every production run failed before sending anything. File a ticket under **Settings → Help & support** to ask for the allow-list to be populated.
- Only **GET** and **POST** ever reach a saved step — the schema that gates every save (drawer or AI builder alike) accepts nothing else, even though the underlying runtime also understands PUT and PATCH.
- The response is capped at 32 KB. Whether anything is kept past the cap depends on how the response arrived: if the server declares a Content-Length above the cap up front, nothing is read at all and both `.data` and `.text` are empty; if the body is read as it streams in and only crosses the cap partway through, the text read so far is kept in `.text` — only the parsed `.data` is withheld either way.
- It never retries. One call, once, when the step runs — a flaky endpoint needs its own retry step (e.g. a **Wait** and a second **HTTP Request**) built into the flow.
- It carries no authentication of its own beyond what you put in **Headers** — there is no stored-credential picker the way Google Sheets has one.
- A redirect is always refused, even a same-host one — the endpoint must answer directly at the URL given.

## Use cases

- Look up a courier's tracking status by a fixed, non-templated identifier, then read the status from `{{http.data.status}}` — once its host is on the allow-list.
- Post a fixed-shape notification to an outside system right after **Move Deal to Stage** marks a deal won — again, once the destination host is allow-listed.

## Related

- [Send Conversions API Event](https://inchat.inseller.my/help/workflows/send-conversions-api-event)
- [Add Google Sheets Row](https://inchat.inseller.my/help/workflows/add-google-sheets-row)
- [Conditions and variables](https://inchat.inseller.my/help/workflows/conditions-and-variables)
- [Workflows: how a flow runs](https://inchat.inseller.my/help/workflows/workflows-overview)

---

Source files: `src/lib/workflows/ai-builder/spec.ts`, `src/lib/workflows/canvas-model.ts`, `src/components/workflows/WorkflowStepConfigDrawer.tsx`, `src/lib/workflows/http-request.ts`, `src/lib/workflows/engine.ts`, `src/lib/workflows/engine-condition.ts`, `src/lib/workflows/types.ts`, `src/lib/workflows/workflow-input-schema.ts`, `src/lib/workflows/form-payload.ts`, `docs/runbooks/secrets-rotation.md`, `src/app/help/content.ts`
