Chapter 19

HTTP In, HTTP Response, HTTP Request, and API security

An HTTP endpoint brings untrusted network input into a flow, while HTTP Request sends data to another service. You must account for route exposure, live request objects, input contracts, a response from every branch, timeouts, credentials, authentication, authorization, and direct access—not just a single successful response. This chapter covers offline checks only: do not connect or deploy anything.

Start by Defining the Four HTTP Boundaries

Add-on 22.0.1 embeds Node-RED 5.0.2. HTTP In creates a server-side route, and HTTP Response completes the same request. HTTP Request acts as a client. The API node in the HA WebSocket package provides yet another path, one that carries Home Assistant privileges. Although all of these involve APIs, their trust sources, credentials, and failure semantics differ.

CapabilityData DirectionPrimary RisksApproach in This Chapter
HTTP In → HTTP ResponseA network request enters a flow, which then responds to the callerUnauthorized access, oversized bodies, path-parameter and header injection, missing responsesFixed method and path, allowlist validation, a response on every path, and disabled nodes
HTTP RequestA flow sends a request to an external HTTPS serviceSSRF, credential disclosure, redirects, unbounded responses, and timeoutsFixed destination, credential storage, bounded timeout, and placeholders only in examples
HA API nodeCalls an HTTP or WebSocket API through an existing HA server configurationReading or changing HA with the integration's privileges; dynamic methods, paths, and data broaden its capabilitiesOnly for advanced, approved, allowlisted use cases; this chapter neither creates nor executes calls
Webhook/WebSocket/TCP/UDPLong-lived connections or other network protocolsEach has distinct authentication, source, message-size, and lifecycle concernsDo not extrapolate HTTP rules directly; for webhooks, see Chapter 12

The Add-on declares host_network: true, giving the container broader access to the local network. This is a capability grant; it does not mean that any flow has established a connection. All outbound HTTP, WebSocket, TCP, and UDP destinations must be explicitly allowlisted. First use the bounded testing strategy from Chapter 18 to validate pure data logic, and only then consider integration testing.

HTTP In and Response Form One Indivisible Exchange

In Node-RED 5.0.2, HTTP In emits a message when it receives a request. For GET, msg.payload is the query object. For POST, PUT, PATCH, and DELETE, the payload is the parsed request body, or the raw data if parsing was disabled. The same message also carries msg.req and msg.res. HTTP Response needs msg.res to reply over the original connection; without it, the node only warns that no response object exists.

Live-object boundary: msg.req is the current Express request, and msg.res wraps the current response. They are not ordinary JSON objects that can be safely persisted or cloned at will. Do not place them in context, files, queues, flows that span restarts, or full Debug output, and never return either complete object to the caller.
FieldSource or PurposeSecurity Interpretation
msg.reqLive request; exposes headers, params, query, cookies, and moreTreat all of it as untrusted; project only allowlisted fields and do not retain the object
msg.resLive response wrapper passed along the original path to HTTP ResponsePreserve it on every success, rejection, and error branch; respond only once
msg.payloadThe GET query or the body of another supported methodSuccessful parsing does not prove a valid shape; also validate types, lengths, ranges, and unknown fields
msg.statusCodeDynamic status code when HTTP Response does not specify a fixed statusGenerate it only from a controlled mapping; never trust caller input directly
msg.headers/msg.cookiesHTTP Response can merge response headers and cookies from these fieldsAllowlist names and values; never reflect hop-by-hop, authentication, or unsanitized content

The HTTP In editor supports GET, POST, PUT, DELETE, and PATCH. POST can enable file uploads, while POST, PUT, PATCH, and DELETE can skip body parsing. Uploads are stored in memory, so the route must impose limits on both size and file count. Node-RED's JSON and URL-encoded parsers use apiMaxLength; when it is not configured, the core default is 5mb. That is only a parser limit, not a sensible limit for an application's payload, and it does not cover every proxy or multipart limit.

CSV, HTML, JSON, XML, and YAML parser nodes handle syntax only. They cannot determine field-level authorization, resource cost, or whether content is trustworthy. For an external body, limit bytes and content type first, then parse it, and finally validate its shape. See Chapter 5 for the general message model.

Complete the Offline Design in Six Steps Without Starting a Route

  1. Write a contract for one route.

    Specify one method, one relative path, the allowed content type, maximum byte count, required fields, success status, rejection status, and timeout. Use only /PLACEHOLDER_HTTP_PATH for the path; do not enter a real host or secret.

  2. Map every terminal path.

    Trace the normal, missing-field, wrong-type, unauthorized, internal-error, and timeout branches from HTTP In. Every branch must reach an HTTP Response node. Never place live msg.req/msg.res objects in Delay, context, or an unbounded path waiting for an external event.

  3. Review the safe example offline.

    This chapter's exercise is read-only: open 12-http-endpoint.json and confirm that it contains exactly 4 nodes, that HTTP In and HTTP Response both have d:true, and that the path is entirely a placeholder. Do not import, deploy, enable, or send a test request. The README's isolated import procedure is a separate operation and never constitutes approval to deploy or connect.

  4. Check the exposure and authentication layers.

    Document Ingress and the direct host port separately. If the route might be reached through direct /endpoint/ access, plan to use http_node.username/password or a controlled reverse proxy. This Basic Auth mechanism is not fine-grained authorization.

  5. Review the outbound design.

    Read 13-outbound-http-request.json offline. Confirm that its URL is PLACEHOLDER_HTTPS_URL, HTTP Request has d:true, there are no authentication, TLS, or proxy configuration references, and Inject will not run at startup.

  6. Complete a failure-acceptance table.

    For each case, record the expected status, minimal body, permitted headers, external timeout, and stop condition. Network and HA nodes must remain disabled, and values must remain placeholders. Actual activation requires a change procedure outside this chapter.

Offline contract template (not executable configuration)
method: PLACEHOLDER_METHOD
path: /PLACEHOLDER_HTTP_PATH
request content type: PLACEHOLDER_MEDIA_TYPE
request max bytes: PLACEHOLDER_LIMIT
success: PLACEHOLDER_STATUS + fixed shape
reject: PLACEHOLDER_STATUS + no internal details
external I/O: do not execute

End-of-Chapter Offline Acceptance

DeliverableExpected ContentStop If
Route contractOne method and relative path, plus media type, byte limit, input shape, authentication, authorization, success and rejection statuses, and timeoutAny field is still a guess, a real host or secret appears in the document, or interpretation requires sending a request
Terminal-path/status matrixNormal, missing-field, wrong-type, unauthorized, internal-error, and timeout cases each have exactly one HTTP Response, a fixed minimal body, and a stop conditionAny path omits or duplicates a response, loses msg.res, or requires enabling the route for verification

/endpoint, Ingress, and the Direct Port Are Three Different Concepts

The Add-on wrapper fixes the Node-RED backend at 127.0.0.1:46836, sets userDir=/config/, nodesDir=/config/nodes, and flowFile=flows.json, and sets httpNodeRoot to /endpoint. Therefore, if you enter /PLACEHOLDER_HTTP_PATH in HTTP In, the resulting Add-on path is /endpoint/PLACEHOLDER_HTTP_PATH. Do not repeat /endpoint in the node's path.

Entry PointPinned-Source BehaviorAuthentication and TLS Boundary
IngressThe manifest specifies ingress=true, dynamic ingress_port=0, and ingress_stream=true; Ingress NGINX accepts only the Supervisor ingress source before proxying to the backendThe Supervisor path handles it; this does not imply that a direct port or custom route receives the same protection
Direct listenerThe container's 80/tcp can map to host port 1880; whether it is exposed depends on the Supervisor port settingsssl, certfile, and keyfile control TLS for the direct listener only; they do not issue or renew certificates automatically
Direct /By default, Supervisor /auth protects the editor pathDo not enable leave_front_door_open; editor authentication is not endpoint authentication
Direct /endpoint/NGINX explicitly proxies directly to the backend, bypassing the editor's Supervisor auth_requestProtect it with Add-on http_node Basic Auth or a controlled proxy; authorization is still required
Static contentServed by the Node-RED static routehttp_static.username/password protects only static content—not the editor, HTTP nodes, or Dashboard

When http_node.username/password includes a username, the wrapper hashes the password with bundled [email protected] and passes it to Node-RED as httpNodeAuth. bcryptjs is a library supported by the wrapper, not a Palette node. Basic Auth answers only whether a caller holds the shared credentials. If users may access different resources, the flow must also authorize them from a trusted identity source, and error responses must not expose authorization details.

Every Branch Must Respond, and Must Do So Exactly Once

Every Switch output, every bounded Catch error branch, every validation-failure branch, and the normal branch must reach HTTP Response. If a branch drops the message, the caller waits until an upstream proxy or client times out. If two branches respond through the same msg.res, they cause a duplicate send. A safe structure creates a fixed error envelope first, then sets controlled msg.statusCode and header values at one response convergence point.

// Pure-data illustration; do not connect to an enabled HTTP In node
// Precondition: the upstream path still retains the original live msg.res
const value = msg.payload && msg.payload.value;
if (typeof value !== "string" || value.length < 1 || value.length > 64) {
    msg.statusCode = 400;
    msg.payload = { code: "INVALID_INPUT" };
    return msg;
}
msg.statusCode = 200;
msg.payload = { accepted: true };
return msg;

This example illustrates only a shape guard; it does not handle identity or define a real route. A status code fixed in the HTTP Response node takes precedence. Otherwise, the node reads msg.statusCode, finally defaulting to 200. For every non-Buffer object payload, it calls Express res.jsonp(msg.payload), so a JSONP callback in the query can change the response envelope. If the contract requires invariant JSON, reserve the callback query name for security checks and reject any caller that supplies it. Alternatively, serialize trusted JSON first and set a fixed JSON content type, thereby avoiding the JSONP semantics applied to objects.

HTTP Request: Fix the HTTPS Destination; Never Let the Message Choose the Network Target

HTTP Request 5.0.2 exposes editor fields for Method (GET, POST, PUT, DELETE, HEAD, or specified by msg.method), URL, GET payload handling, TLS configuration, authentication credentials (Basic, Digest, or Bearer), keep-alive, HTTP proxy configuration, error output, the insecure HTTP parser, return type, and headers. If the node URL contains Mustache tags, the runtime renders them against the entire message, so a URL that appears in the node is not necessarily fixed. The example uses only a complete, non-templated placeholder URL without {{...}}; a production destination requires separate approval.

FieldRecommended BoundaryReason
Method/URLFix the method and an approved, complete HTTPS URL in the node; do not use Mustache in the URL or accept untrusted msg.url/msg.methodPrevents SSRF, internal-network probing, and unintended write methods
PayloadIgnore the payload for GET by default. If a query or body is needed, allowlist keys and limit lengths. For other methods, fix the content type and shapeAvoids serializing and sending a complete message or unvalidated object
AuthenticationUse the node's credential fields; never embed credentials in a URL, Function, header constant, or exported JSONBasic, Digest, and Bearer secrets must not enter flow source or Debug output
TLSUse a controlled TLS configuration that validates the server certificate and hostname; never let a message disable validationWithout a selected TLS configuration, msg.rejectUnauthorized can still control validation, including setting it to false
ProxyUse an HTTP Proxy configuration node and fixed, permitted egress; also review proxy credentials and the NO_PROXY boundaryA proxy changes the actual destination, DNS behavior, and trust-termination point
Parser/redirectSet insecureHTTPParser=false; disable redirects with controlled msg.followRedirects=false, or have an egress proxy/upstream service enforce destination policyThe node has no redirect-host allowlist; following a cross-host redirect expands the SSRF surface
ReturnSelect only the required form: text, binary buffer, or parsed JSON. Enforce a response-size limit at the egress proxy/upstream serviceThe node has no per-response byte limit; large responses consume memory

In addition to its configured fields, the runtime consumes msg.headers, msg.cookies, and msg.followRedirects, as well as msg.method, msg.url, and msg.requestTimeout for method, URL, and timeout control. When no TLS configuration is selected, it also consumes msg.rejectUnauthorized. Immediately before the node, therefore, create a new allowlisted message containing only the approved payload, required headers, and fixed control values. Never pass an HTTP In, Dashboard, or MQTT message through unchanged, and do not merely delete one or two known fields.

If httpRequestTimeout is not configured, the core runtime's HTTP Request timeout is 120000 ms; a positive msg.requestTimeout can override it. The implementation also permits up to 21 redirects. These are Node-RED 5.0.2 behaviors, not policies suitable for every service. Set a narrower, justified timeout for each destination, and bound both the retry count and total deadline upstream. Disable redirects, or require an egress proxy/upstream service to enforce both a destination-host allowlist and a response-size limit. Never compensate for a timeout with unlimited retries.

The core runtime first loads the entire response buffer into memory. If parsed JSON is selected, it then runs JSON.parse before emitting the message. HTTP Request itself has neither a per-response byte limit nor a redirect-host allowlist. Checking msg.responseUrl, the content type, or body size only after the node cannot prevent SSRF, cross-host redirects, or memory exhaustion that has already occurred. Still allowlist msg.statusCode, msg.headers, msg.responseUrl, msg.payload, msg.redirectList, and any msg.responseCookies on the successful output—but understand that this protects only downstream data use. Do not pass headers or cookies unchanged to HTTP Response, and do not send the complete message to Debug.

Safe-example status: The URL in 13-outbound-http-request.json contains only PLACEHOLDER_HTTPS_URL, embeds no authentication, and fixes HTTP Request at d:true. This is a schema for reading, not a connectable service. Do not replace the placeholder, enable the node, or deploy it.

Validate the Method, Path, Body, and Headers in Layers

Selecting POST in HTTP In performs only the router layer's method match. Complete validation has at least transport, syntax, shape, authorization, and business layers. A failure at any layer must return a bounded, stable error without a stack trace, token, internal host, or raw body.

Input SurfacePositive AllowlistExamples to Reject
MethodEach route accepts only the method declared by its nodeA query field requesting a switch to PUT/DELETE; retries of a non-idempotent method
Path/paramsFixed segments; parameters constrained by character set, length, and authorized resource scope.., encoded separators, empty segments, overlong IDs, unauthorized object IDs
Query/bodyRequired keys, exact types, ranges, array limits, and rejection of unknown keysnull, strings instead of numbers, deeply nested objects, huge arrays, duplicate keys
HeadersA fixed content type and bounded Accept values; trust identity headers only after a controlled proxy has overwritten and sanitized themConflicting Content-Length values, dynamic Authorization, forged forwarding headers
ResponseA fixed status mapping, content type, cache policy, and minimal bodyReflected request headers or bodies, returned exceptions, a Location derived from caller input

CORS is not authentication. Node-RED can add CORS middleware to HTTP nodes through the global httpNodeCors setting and configure handling for OPTIONS; the HTTP In dialog has no per-route CORS field. Permit only explicit origins, methods, and headers. Do not combine a permissive wildcard with credentials. The browser same-origin policy also does not stop non-browser clients.

A reverse proxy must limit request-body size, header bytes, connection count, and upstream timeout, and it must trust only forwarding headers that it overwrites itself. Document where TLS terminates, whether the proxy-to-backend connection remains protected, and how the client IP is obtained. Add-on direct TLS covers only the direct listener; Ingress and an external proxy use different TLS sessions.

Bounded Error Model

400 INVALID_INPUT    Field or type does not match the contract
401 AUTH_REQUIRED    Authentication is missing or invalid
403 NOT_ALLOWED      Identity is valid but lacks permission for this resource
404 NOT_FOUND        Do not reveal details about absent or invisible resources
413 BODY_TOO_LARGE   Reject an over-limit body before parsing
415 MEDIA_TYPE       Unsupported Content-Type
429 RATE_LIMITED     Bounded rate limit; no internal counter details
500 INTERNAL_ERROR   Fixed minimal body; details go only to a controlled log
504 UPSTREAM_TIMEOUT Outbound deadline expired; do not retry indefinitely

The HA API Node Is an Advanced Privileged Entry Point, Not a General HTTP Shortcut

In HA WebSocket 0.80.3, the registered persisted type is ha-api. It supports WebSocket or HTTP; the HTTP methods are GET, POST, PUT, and DELETE, and the path has its leading /api/ removed before being passed to the existing HA server configuration. WebSocket data must include type. Data may use JSON or JSONata, and the output location and type are customizable. These dynamic capabilities let one node reach many HA APIs; they do not make every call read-only.

Version 0.80.3 has no Block Input Overrides option: ha-api reads overrides from msg.payload.protocol, msg.payload.method, msg.payload.path, msg.payload.data, msg.payload.dataType, the output location in msg.payload.location, the output-location type in msg.payload.locationType, the response type in msg.payload.responseType, and output properties in msg.payload.outputProperties. Fixed editor fields do not form an allowlist boundary. Immediately before the API node, delete and rebuild the entire msg.payload, adding only approved values. If that is impossible, completely isolate the node from all untrusted input. Deleting only the method and path does not block the other overrides.
CapabilityRiskRequired Restriction
Protocol/HTTP method/pathA message can change the protocol, read data, and potentially write, delete, or trigger a state changeRebuild the complete msg.payload, retaining only one approved protocol, method, and path; otherwise isolate all untrusted input
WebSocket type/dataLow-level commands have broad scope, and a message can change both data and dataTypeFix the command type and exact schema; never pass a caller object directly
Output controlsA message can change location/locationType, responseType, and outputProperties, broadening data writes or disclosureInclude the output destination and type in the same allowlist; do not retain caller-supplied output controls
HA server configurationInherits the integration connection and token privilegesNever export the token; separate production and test configurations, and apply least privilege
Debug enabled/resultsMay expose the method, path, data, or HA responseKeep Debug disabled; emit only allowlisted results and sanitize environmental IDs such as entity, device, and area IDs

If a Current State, Get Entities, or Action node already serves a clearly defined purpose, prefer that semantically narrower node. For the boundaries around an Action node's target and data, see Chapter 11. Use the API node only for an advanced case with no dedicated node and a fully established HA API contract. This chapter provides no executable HA API flow, method, path, or data, and does not instruct you to enable any node.

ha-webhook is an HA event entry point, not an alias for Node-RED HTTP In. A webhook URL or ID is a sensitive entry-point value and must not appear in screenshots, flow exports, or general logs. You must still constrain external sources, replay, payload size, and downstream side effects. Other WebSocket, TCP, and UDP nodes have their own configuration and framing semantics; the paired HTTP Response model does not apply to them.

For more complete operational boundaries around credentials, proxies, TLS, host networking, and least privilege, see Chapter 21. When timeout, memory, or connection problems arise, follow Chapter 22 to collect bounded diagnostic data rather than enabling complete Debug output first.

Troubleshooting

  • HTTP In emits a message, but the caller keeps waiting: Trace every Switch output, validation-rejection branch, Catch branch, and normal path. Each must preserve the original msg.res and reach HTTP Response. Do not extend the wait with a Delay node. Keep the route disabled and validate its terminal paths on a pure-data copy first.
  • HTTP Response reports that there is no response object: The message did not come from the same HTTP In node, or rebuilding it along the way discarded msg.res. Do not fabricate msg.res. Preserve the original message and overwrite only controlled fields.
  • Ingress works, but the direct endpoint returns 401 or is unreachable: They use different listeners and authentication boundaries. Check the host-port mapping, direct TLS, and http_node settings. Never enable leave_front_door_open for troubleshooting.
  • The endpoint is reachable without an editor login: Direct NGINX intentionally does not apply the editor's Supervisor authentication to /endpoint/. Configure endpoint-specific authentication and authorization; do not treat network location as access control.
  • HTTP Request times out or redirects repeatedly: Keep the node disabled and check whether the placeholder is still present, along with DNS, the proxy, the TLS chain, the final host, a bounded request timeout, and redirect policy. Do not disable certificate validation or configure unlimited retries.
  • Parsed JSON initially succeeds but later processing fails: A JSON parser guarantees syntax only. Add checks for required fields, types, ranges, depth, array length, and unknown fields; the error path must still respond.
  • A CORS error appears only in a browser: Check the origin, preflight method and headers, and the proxy response. Do not use a wildcard with credentials, and do not treat passing CORS checks as authentication.
  • The HA API node reports “requires path/type”: This is an input-contract error in an advanced node. Do not improvise values from an external payload. Keep the node disabled and return to an allowlist design with a fixed HTTP path or WebSocket type.

Pinned Version Sources and Official Documentation

This chapter uses exact commits to establish Add-on routing and Node-RED/HA node behavior. Rolling documentation provides general usage guidance only. This chapter draws no inference about proxy or environmental policy that the pinned sources do not establish.

Frequently Asked Questions

Can I use HTTP In without connecting it to HTTP Response?
You should not. HTTP In creates a request that waits for a response. Every normal, rejection, and error terminal path must preserve the same live msg.res and send it to HTTP Response, exactly once. For long-running work, define an explicitly asynchronous API contract instead of leaving the connection waiting without a bound.
If I open the editor through Home Assistant Ingress, is /endpoint automatically protected by the same login?
No. Ingress, the direct editor, and direct /endpoint/ access follow different paths. The pinned direct NGINX configuration does not apply the editor's Supervisor auth_request to endpoints. You need http_node or a controlled proxy, plus separate authorization.
Can I put a complete URL in msg.url so one HTTP Request node can call multiple services?
Not when input is untrusted. A dynamic URL creates an SSRF and internal-network probing surface. Fix an approved HTTPS destination in each node, or map a bounded identifier to a server-side allowlist immediately before the call. The URL must never contain a username, password, token, or other credentials.
Once CORS permits a website, is API authentication complete?
No. CORS is a browser cross-origin read policy, not authentication or authorization, and it cannot stop non-browser clients. You still need endpoint credentials, resource authorization, TLS, rate limiting, and input validation.
Why not replace every HA node with the HA API node?
ha-api can dynamically select HTTP or WebSocket and its data, giving it a broader privilege and input surface. Dedicated nodes usually have narrower semantics and are easier to validate. Consider the advanced API node only when no dedicated capability exists and the API contract is fixed. This chapter provides no executable call.
If a third party might resend the same POST, what must the offline contract decide first?
First decide whether the operation is safe to repeat, which controlled idempotency key to use, how long to retain that key, and which fixed status and body to return for duplicate requests. If the side effect cannot be repeated safely and there is no deduplication boundary, stop the design. QoS, a timeout, or the assumption that the caller “should send only once” cannot fill that gap.