Skip to Content

Where reading stops and doing begins

the node that can flip a switch
Node-RED Guide · Part 6

Where reading stops and doing begins

Part 5 left you with flows that ask Home Assistant questions — what state is this entity in, has it changed. Every node in that part reads. This part introduces the node that writes: the Action node, in the pinned build this guide follows (HA WebSocket integration 0.80.3, node version 7), and the four different ways a flow can be woken up before it ever reaches one. Node behavior here can change between builds of the HA integration, so treat every specific field name and default as tied to that pinned version, not as a permanent promise. Because a mistake in this part is not a wrong number on a Debug panel — it is a light switching on, a notification going out, a helper changing state in a house nobody asked to change — every example below stays disabled and every target stays an obvious placeholder. This is the anatomy lesson, not the live wire.

5 target types
entity, device, area, floor, and label — the scopes the Action node's target field accepts. Widen from entity to floor and the same call reaches every light in the house
4 queue modes
none, first, last, all — what happens to a call made while Home Assistant is unreachable. None of the four is a substitute for a real work queue
4 trigger models
event bus, entity state, device automation, time scheduling. Same word "trigger" in the palette, four different sources and four different failure modes
Where the danger actually is

Everything before this part could only look. This part can touch.

The Events: state and Current State nodes from Part 5 primarily read data. An Action can control devices, change helpers, send notifications, or call other Home Assistant functionality — and a single wrong target, an allowed payload override, or mistaking the reconnect queue for a reliable work queue can turn one test into several real operations.

Every Action call answers exactly three questions, and the three answers belong in three different fields. Action says which Home Assistant operation to run. Target says which entity, device, area, floor, or label it reaches. Data carries whatever arguments that specific action needs. Node-RED will not stop you from putting an entity ID in the data field or message text in the target field — it will just send something that does not do what you meant.

In plain terms

It is ordering at a restaurant. The action is what you want done — "bring the check." The target is which table gets it. The data is anything extra you tack on — "no ice, and a receipt." Say "bring the check" to the wrong table and the food still comes; it just comes to the wrong people. Nothing about the sentence being grammatically correct saves you from that.

In the source material's own worked example, the Action node ships disabled — "d": true in the flow JSON — with the Server, action, and entity all set to obvious placeholders. That is not a rough draft waiting to be finished. That is the state it is supposed to stay in until a security review, the target owner's approval, and a recovery plan all exist. This part explains the contract well enough that you could pass that review. It does not hand you anything ready to Deploy.

Nothing in this part is executable, and it should stay that way while you are only reading it. Every code sample below uses a placeholder in the conventional style — PLACEHOLDER_HA_ACTION, PLACEHOLDER_ENTITY_ID, and the like — because that is what the underlying source material itself uses, and because a placeholder that obvious cannot be mistaken for something you can paste into a live automation. If you build a real Action afterward, every placeholder gets replaced only after the review this part describes, not before.
Action, target, and data

One label in the palette, a different name in the file

In the pinned build, the palette shows Action and the node type key is Action — but the type recorded in exported flow JSON is still api-call-service. "Call Service" in older flows and documentation is the same node under its legacy name. Do not revert the current terminology, and do not hand-edit the JSON type to action; the persisted type is the correct one, not a bug to fix.

After it parses the action string, the node splits it at the first period into a domain and a service — light.turn_on becomes domain light, service turn_on. If either half is missing, the action format is invalid. An action taken from configuration is rendered with Mustache first and then lowercased. The v7 migration keeps its older compatibility fields, but a new design should use the single action field rather than separate domain and service fields supplied by a message.

The target combines the five configured selectors with an allowed msg.payload.target; data is merged according to the JSON-or-JSONata, context, and payload rules covered in the next section. Once everything is merged, the node calls the Home Assistant WebSocket call_service operation.

flowchart LR
  A["action: domain.service"] --> M["Merged into one call"]
  T["target: entity / device /
area / floor / label"] --> M D["data: arguments for
that specific action"] --> M M --> C["HA WebSocket call_service"]
Three arrows in, one outAction names the operation, target names its scope, data carries the arguments, and all three feed the same merge box before the single call leaves it. A value placed in the wrong one of the three is still accepted by Node-RED and still sent — it just sends the wrong thing.
LayerPersisted fieldResponsibilitySafe default
Operation typeactionAn HA action in domain.service formPLACEHOLDER_HA_ACTION; reject input overrides
TargetentityId, deviceId, areaId, floorId, labelIdSelect the scope of the callOne minimal placeholder; no broad default scopes
Argumentsdata / dataTypeAction-specific argumentsFixed schema and types; reject extra fields
Dynamic mergingmergeContext, mustacheAltTagsContext, or a JSON template / JSONataLeave blank unless needed; restrict keys and owners
Execution boundaryqueue, blockInputOverridesDisconnected behavior and message overridesnone, true
OutputoutputPropertiesMap sent data, results, or other valuesOnly fields you need — never secrets or full attributes

The safe baseline in the review draft uses exactly that: v7, queue set to none, Block Input Overrides set to true, debug logging off, placeholders as the only targets, and the whole node disabled. Its data field is a static illustrative string. That does not mean the action actually accepts that argument — and importing the flow does not send it.

Who gets the last word

Block Input Overrides decides whether a message can rewrite the call

The node can read msg.payload.action, msg.payload.target, and msg.payload.data from an incoming message; older flows also recognize a payload domain and service pair. When Block Input Overrides is true, all three of those overrides are disabled: action comes from configuration, target comes from the five configured selectors, and payload data does not take part in data merging at all. Every Action should keep this setting on.

If it is false, a payload action can replace the configured action, a payload target is deep-merged with the configured target, and payload data wins the merge outright. Even if the node upstream only meant to change one text field, a faulty node — or a malicious one — could change the action and its target together. For anything arriving over HTTP, MQTT, a webhook, a Dashboard, Assist, or another flow, type checking alone is not enough. You need an action allowlist, a target allowlist, and a data schema, and you need to build a new, clean msg object from them rather than trust the one that arrived.

flowchart TD
  A["A message reaches the Action node"] --> B{"Block Input Overrides"}
  B -->|"true, the safe default"| C["action, target, and data all
come from configuration.
msg.payload plays no part"] B -->|"false"| D["msg.payload.action can replace the
configured action. msg.payload.target
is merged in. msg.payload.data
wins the data merge"]
One switch, two outcomesBoth branches out of the diamond end in a description box, so this setting has no undefined middle state. The true branch is the one this guide asks you to keep; false is what changes the moment you turn it off.

Data can be entered as JSON or JSONata. JSON mode runs a Mustache render before parsing, and the optional alternate-tags setting only changes the template delimiters, nothing else. JSONata runs through the Node-RED evaluator. Neither one is Home Assistant Jinja, and neither one is JavaScript — do not carry syntax across from one to the other. Never treat an untrusted string as a template, and never place a token, a header, or a credential inside data or context.

mergeContext names one flow or global context key. The node reads the global object first, overlays the flow object on top of it, and — only when input overrides are allowed — overlays payload data last; configured data sits at the bottom of that stack with the lowest precedence. Setting Block Input Overrides to true only removes the payload layer from this stack; a flow or global value can still override your configuration. Before you turn mergeContext on for anything, decide who is allowed to write that context key, define its shape and how long it is kept, and write down how it gets cleared.

In plain terms

Block Input Overrides is the difference between a fixed menu and a customer who can swap any ingredient. With it on, the kitchen cooks exactly what is printed on the card, no matter what the runner shouts through the hatch. With it off, the runner's shout wins — table number included, whether or not the runner heard the order correctly.

Blocking overrides is not a complete policy. It stops a message from rewriting the call, but it does not catch a real target picked wrong in the editor itself, it does not stop contamination through mergeContext, it imposes no rate limit, and it asks for no second confirmation. A genuinely high-risk target still needs human approval, a narrow allowlist, a cooldown, and a stop path someone can actually see.
Five ways to aim a call

Only one of the five target types stays the size you picked

The Target selector supports five ID types, and each configuration field becomes an HA target key on the wire: floor_id, area_id, device_id, entity_id, and label_id. An empty configuration array leaves that key out of the call entirely. At runtime a single entry can render as a plain string while several entries stay a collection. Every one of these values can render environment variables or Mustache, so environment and context data belong inside the same review boundary as the target itself.

Target typePlaceholderScope riskReview question
entityPLACEHOLDER_ENTITY_IDMost precise, but renaming or swapping the ID breaks itIs this a dedicated test entity, and does it permit this action?
devicePLACEHOLDER_DEVICE_IDOne device can hold several entities and capabilitiesIs the action's actual scope on the device clear?
areaPLACEHOLDER_AREA_IDMembership changes as entities and devices are reassignedCould new members be swept in without review?
floorPLACEHOLDER_FLOOR_IDBroader than an area, and may span several spacesIs a whole floor really necessary? Deny it by default.
labelPLACEHOLDER_LABEL_IDLabel membership can grow dynamicallyWho can change the label, and is that change watched?

Being selectable in the editor does not mean every action accepts every target type. The Target field is filtered or hidden by Home Assistant's own service metadata, and an unfamiliar action may not carry reliable metadata at all. Check the action's documentation and services metadata for your Home Assistant version, and put the entity ID exactly where that action's schema says it belongs — do not invent a field from a screenshot, and do not lean on a compatibility shortcut you have not confirmed.

0.80.3 version detail. Some legacy actions define entity_id in their data fields rather than in target. When the service metadata shows fields.entity_id, data does not already contain entity_id, and the merged target.entity_id is a single string, the node warns and quietly moves that value into data for you. This compatibility path never moves an array target, and it is documented as scheduled for removal in a future 1.0 release — do not build a design that depends on it, and fix any flow that currently relies on it before upgrading.

Several target types can be selected at once, and combining them can widen the scope rather than narrow it — it is a union, not an intersection. A safe draft picks one, most precise target type at a time. Treat an area, a floor, or a label as a group whose membership can change, and recheck it before every call rather than trusting what it held last time you looked. Debug output, documentation, and issue reports should show placeholders only, never a real ID.

Queues, responses, and retries

What happens to a call when Home Assistant is not there to take it

Queue settings only matter while Home Assistant is disconnected. none raises a connection error immediately. first keeps only the first message that arrives while the queue is empty. last replaces the retained message with whatever arrives next. all appends every message to an in-memory array. Once the connection is ready again, the node retrieves entries with pop(), so a queue of several messages is drained from the end of the array — do not assume first-in, first-out order. None of the four modes is durable, none has a hard limit this guide can verify, and none guarantees a message is delivered exactly once.

flowchart TD
  Q["A call is made while
Home Assistant is unreachable"] --> N["queue: none
raises a connection error immediately"] Q --> F["queue: first
keeps only the first message"] Q --> L["queue: last
keeps replacing it with the newest"] Q --> AL["queue: all
appends every message in memory"]
Four branches off one settingEach branch leads to its own outcome and none of them merges back into another — whichever mode is configured is the only path a disconnected call takes. Only the "all" box mentions memory growing without a limit.
QueueWhile disconnectedRecommendation
noneRaises an error immediatelySafe default; do not defer control
firstKeeps the first message, discards the restOnly if the first intent is still guaranteed timely at reconnect
lastKeeps overwriting with the newest messageNeeds a version or timestamp check before it is acted on
allKeeps every message, unbounded, in memoryDo not use for control Actions
In plain terms

It is the difference between four receptionists. "None" hangs up on the caller and tells them to try again themselves. "First" takes exactly one message and refuses every other call until that one has been delivered. "Last" keeps tearing up the old note and writing down only the newest one. "All" pins every note to a spike in the order they land — and hands them back in whatever order they come off the spike, not the order they were written.

If your flow genuinely needs a reliable work queue, that calls for a dedicated design with persistence, ordering, deduplication, deadlines, cancellation, and a dead-letter path — the Action node's four queue options are not a substitute for any of that. After a reconnection, query Current State again rather than blindly replaying whatever is sitting in the queue.

Response support depends on the action, not on the node

Do not assume every action returns data, or that a result always lands in msg.payload. A destination property is written only when Output Properties explicitly maps a value to it.

0.80.3 version detail. The Home Assistant WebSocket wrapper checks the connected Home Assistant version and the action's services metadata before it calls out. On Home Assistant 2023.12 or later, if the action's metadata declares a response, the outgoing call_service request sets return_response to true; without response metadata it sets the flag to false. On older Home Assistant it does not set the value at all. This guide's own baseline still assumes Home Assistant 2024.3 or newer — this detail only explains the runtime boundary underneath that assumption.

Once the call resolves, the node exposes response?.response as the Output Properties source named results, and it combines the domain, service, data, and target actually sent as the source named sentData.

Output sourceContentsRecommended mapping
sentDataDomain, service, data, and target actually sentNever output the whole object; extract only a sanitized correlation field
resultsThe response part of the HA call, if anyWrite to msg.actionResult, then validate its shape
msgThe input message, still available for mappingKeep only necessary, allowlisted fields
No output properties setThe original message is sent on after successDo not assume the unchanged payload is a response

return_response is a WebSocket call flag the wrapper chooses from the action's own metadata; it is not a general-purpose field you put into data yourself. Verify actual behavior against the official documentation and the services metadata for whichever Home Assistant version you are running.

Common failures include being disconnected with queue set to none, an invalid action format, unparseable JSON, a schema-invalid input, a rejected HA call, or a connection failure mid-call — none of these produce a successful output mapping. Route them through Catch and watch Status, and end the error branch in a bounded Debug or alert rather than an automatic retry. Never change the target automatically and try again, and never write full data, responses, tokens, or real IDs to a log.

A retry policy has to account for whether the action is idempotent. A network timeout can happen after Home Assistant has already executed the action but before Node-RED receives the response, so an immediate retry can duplicate the side effect. A safe design needs a request or correlation ID, an action-specific way to verify the outcome, a retry limit, backoff, and a manual recovery path — not just a check for whether any output arrived.

Four trigger models, not one

The word "trigger" hides four genuinely different sources

Judged by node names alone, it is easy to assume Events: all, Device, and Time all just "send a message when something happens." In practice, Events: all subscribes to Home Assistant events or to the WebSocket client's own lifecycle events; Device can run in Trigger or Action mode; Time schedules messages from the state or an attribute of one specified entity; and Calendar polls a calendar and schedules timers from it. Picking the wrong entry point can flood a flow with events, repeat an operation after reconnection, or quietly turn a read-only flow into one that touches devices.

ModelObserved sourceSuitable usePrimary boundary
Event busAn HA event type and its event dataDiscrete events that are not one entity's stateA blank event type can receive a very high volume; event data may carry identifying information
Entity stateThe old and new state of state_changedDoors, windows, lights, sensor readings, and similar transitionsAttribute updates fire too; unknown and unavailable need separate handling
Device automationA trigger or action the device's own integration suppliesButton gestures or capabilities that integration explicitly exposesCapabilities depend on the integration and device — not every device offers the same options
Time schedulingA time value in an entity, a calendar item, or a Node-RED timerProducing a message at a future timeTime zones, DST, restarts, expired times, and data updates can all change the result
flowchart TD
  S["Something happens"] --> E["Event bus:
a Home Assistant event type"] S --> ST["Entity state:
old_state / new_state of state_changed"] S --> DV["Device automation:
a trigger the integration defines"] S --> TM["Time scheduling:
an entity's time, a calendar item,
or a Node-RED timer"]
Four branches from one wordEach of the four leads to a different family of Home Assistant node, and none of the branches feeds back into another. Reaching for Events: all when what you actually want is entity state is how a flow ends up subscribed to the wrong branch entirely.

An event name is not an entity ID. An event type selects event-bus messages, an entity ID identifies a state, a device ID identifies an entry in the device registry, and tag IDs and webhook IDs live in their own separate namespaces again. Do not interchange identifiers because the strings happen to look similar, and do not treat a display name in the UI as a stable ID.

Receiving and sending carry different risk. Events: all, Tag, and Zone are input nodes. Fire Event sends an event to Home Assistant. Device in Action mode sends a device action. Sentence in response mode — and a trigger-mode Sentence configured with a fixed response or a timeout fallback — answers the voice assistant. Time entity in set mode writes an entity value. In any test flow, every sending or responding configuration should stay disabled with nothing wired into it.

Events: all — only with an explicit event type

Events: all accepts one Event Type, or, left blank, receives every event on the bus. Both the editor and the official documentation warn explicitly that a blank value can overwhelm the WebSocket message queue. Event data has to be a JSON object, and at runtime it is matched as a subset of the actual event content — useful for narrowing a known event further, but not a substitute for validating the input downstream.

The special event type home_assistant_client carries client lifecycle events — a data-loading or connection phase, not a business condition being met. Do not build a gate that waits for a connected event.

In plain terms

The store's lights turning back on does not mean the shelves are stocked. "Running" and "ready" are the lights coming on. They do not tell you whether the sensor reading you cared about this morning is still what it was — that is a separate trip to go check, not something you get to assume from the lights alone.

0.80.3 version detail. The controller's code includes handlers for connecting, connected, disconnected, and error events, but actual event registration only wires up states_loaded, services_loaded, running, and ready. The unwired connected handler is not part of this node's real output contract. "Output only after Home Assistant is running" suppresses ordinary events until then, but client events are emitted on a separate path, so a reconnection branch needs its own handling regardless.
sequenceDiagram
  participant HA as Home Assistant
  participant N as Node-RED (Events: all)
  participant F as Your flow
  HA->>N: states_loaded
  HA->>N: services_loaded
  HA->>N: running
  HA->>N: ready
  N->>F: flow health signal only
  F->>HA: Current State query, made separately
  HA->>F: the actual value, checked fresh
Four lifecycle events, no shortcut to stateFour events reach the node in this pinned build, and none of them is named "connected" — only these four are actually wired. The query to Current State at the bottom is a separate message your flow has to send; nothing above it triggers that query automatically.
A reconnection gate is not a catch-up trigger. Even after using the actually-wired running or ready events as a health signal, query the entities you need with Current State, check their timestamps and a deduplication key, and stop if the state is missing. Lifecycle events may update your flow's own health status only — they must never directly fire a catch-up Action.

Sentence — triggers and responses travel in opposite directions

Sentence supports trigger and response modes, and both require the separately installed hass-node-red custom integration on the Home Assistant side (this guide makes no assumption about which version of that integration you run). In trigger mode, Home Assistant sends the sentence, the match result, a device ID, and a response ID; the output message carries an internal _sentence_response_id. Response mode reads that ID back and sends a dynamic response to the integration.

Sentences can reveal what is happening in a household and where devices are, so Debug output should retain a classification result only — never the original sentence or the device ID. A trigger does not need a separate response node to produce an external response of its own: a fixed response is sent directly, and dynamic mode can send a fallback after a timeout. For observation only, leave the fixed response and fallback blank or set to not respond, and do not wire a response-mode node in at all. Mode being set to trigger does not by itself make a node purely input. If a response ID expires or is lost, end that invocation there — never retry with the old ID.

Fire Event — an output, not a test button

When Fire Event receives a message, it sends fire_event over WebSocket. The event type can come from configuration or from the message, and its data can be built with a template or JSONata. Other Home Assistant automations may be listening for that event type, so even a "just a custom event" can trigger an alarm, a notification, or a device action somewhere else. Keep the node disabled, do not wire an Inject node into it, and do not Deploy it "just to try the button."

Device — check the integration and the capability first

Device offers Trigger and Action modes, and this device-automation path also requires the hass-node-red custom integration to be loaded in Home Assistant. Trigger mode registers through the integration and emits a message when the device's own trigger fires. Action mode, after receiving a Node-RED message, sends the configured action and capabilities to Home Assistant. Available triggers, actions, and capabilities come from the integration that owns the device — they are not a common list this guide can promise applies to every device. One sensor's "single press" is not guaranteed to exist on another brand's equivalent.

If the editor does not show a capability you expected, confirm the device registry entry and the integration's support for it in Home Assistant first — do not hand-write a guessed object. A test flow should carry only a disabled Device Trigger, with Device Action left unwired; for real control, the review in this part's earlier sections on action, target, and data still applies.

Tag — both the tag and the scanner can identify a person

Tag can listen for one tag or for all of them, and it can restrict which scanning devices are accepted. When no devices are specified, this pinned build accepts a scan from any device. Its output can include the tag name, tag ID, device ID, and user ID — fields that can reveal people and locations. Apply the narrowest filter you can, emit only an anonymous purpose code, and never leave "all tags" enabled in a production flow.

Webhook — knowing the ID can be enough to invoke it

Webhook also requires the hass-node-red custom integration. It registers a webhook ID and its permitted POST, PUT, GET, and HEAD methods with that integration, then receives the payload, headers, and params of whatever calls it. It is not a Node-RED HTTP In node, and it does not imply authenticated user access on its own — real reachability depends on how Home Assistant is exposed to the network and on the webhook mechanism itself. Do not expose Home Assistant for convenience, and never put a webhook ID in a tutorial, a screenshot, a repository, or a Debug output.

This guide creates no endpoint. PLACEHOLDER_WEBHOOK_ID is a non-resolvable marker only. Keep the Webhook node disabled, enter no real value, run no test request against it, and display no external URL for it. If a real requirement is later approved, that calls for its own threat model — method allowlists, source validation, rate limits, and a revocation procedure, worked out separately.

Zone — crossing a line is not the same as being there

Zone compares the old and new coordinates of a person or tracked entity against a zone's own coordinates to detect entering, leaving, or both. It produces no output without old_state/new_state, location data, and zone data all present. Location latency, GPS drift, and radius boundaries can all cause flapping, and location data is highly sensitive. A safe flow emits only "anonymous zone state changed," adds a duration threshold or cooldown, and never stores raw coordinates.

Time and Time entity — scheduling, and a writable time

Time reads one property (the default is state) from a specified entity. This guide only vouches for a previously validated date string — ideally ISO 8601 with an offset — or an HH:MM[:SS] time. If an upstream value is a numeric epoch, convert it to an ISO string explicitly before it reaches Time; never pass the raw number in. The node applies a positive or negative offset, can optionally randomize within that offset range, and can repeat daily on selected weekdays. It rebuilds the timer whenever the source entity's state or attribute changes.

Input conditionSafe interpretationResponse
A one-time value is already in the pastMust not run immediately as a catch-upRecord an expired status; never treat the value as a catch-up trigger
Daily repeat at HH:MMUses the runtime environment's local timeManually verify the next run before and after a DST transition
Negative offset plus randomizationMay be constrained to avoid landing earlier than nowDo not use for legally regulated or precision-critical timing
Entity update or reconnectionThe schedule may be recalculatedBuild a downstream dedup key from the event date and purpose
unknown / unavailableNot a valid time at allTake the error branch; do not keep the previous value

DST is not simply an hour added or subtracted. Some local times do not exist on the day of the transition; others happen twice. Test both directions in isolation, using the real time zones of Home Assistant, the Node-RED host, and whatever calendar is the source, and make every downstream action idempotent regardless of which way the clock moved. When you need an absolute time, keep an ISO timestamp with its offset and convert it only for display — never mix it with a time-zone-free string further down the flow.

Time entity is a separate node. It also needs the hass-node-red custom integration, paired with an Entity config, to expose a time value back to Home Assistant. Its modes are listen, get, and set. set is a write: in this pinned build it validates a 24-hour HH:MM[:SS] value and adds seconds automatically if you omit them. listen and get only read the existing value. It is not a replacement for the ordinary WebSocket Time trigger. Keep all three modes disabled while testing, and in particular, never wire set to anything untrusted.

Calendar — polling, not a guarantee

Events: calendar selects one calendar entity and fires at its start or end, adjusted by a positive or negative offset; Filter does a substring match against the event summary. This pinned build polls roughly every 15 minutes, with a one-minute overlap between query windows, then schedules precise timers in an in-memory queue and reduces duplicates with a cache of events it has already emitted. All-day events are aligned to midnight before the offset is applied.

Those mechanisms make ordinary scheduling more reliable — they do not mean "never miss, never duplicate." A calendar entry added or changed at the last minute between two polls may not be caught in time. Restarting Node-RED clears both the in-memory timer queue and the emitted-event cache, and what gets rescanned afterward depends on whatever query window happens to be current when it restarts. Network errors are retried, but if a whole polling round ultimately fails, no events survive it. Your own flow has to define the recovery policy here — this node does not guarantee catch-up delivery on its own.

A safe "meeting reminder" specification

  • Keep Calendar disabled and set its entity to PLACEHOLDER_CALENDAR_ENTITY. Use a filter term that names no person or location.
  • Output only an anonymized ID. Retain a hashed UID, the event start time, and a classification — never the description, location, or original summary.
  • Deduplicate by "anonymous UID + occurrence date + reminder type," and give that key a reasonable expiry rather than keeping schedule details indefinitely.
  • Route anything uncertain to a bounded diagnostic Debug, not to a live notification — events past their lateness tolerance, ones that fail time parsing, ones arriving just after a reconnection, or ones missing required data.
  • Keep the real notification node disabled until the rate-limit and error-branch checks that Part 7 covers have their own separate approval.

If what you actually need is to wait for a condition rather than for a calendar time, that is Wait Until territory — covered where motion lighting comes up next. Those two questions are genuinely different: "when is the scheduled time" and "when does the condition become true." Do not reach for a long Delay to guess at an external state you could just query.

When it does not behave

Disable first, then go read the source of the problem

SymptomLikely causeWhat to do
Palette shows Action, export says api-call-serviceExpected persisted type in this pinned buildDo not hand-edit the JSON type or rebuild the node over its legacy name
"The action format is invalid"The action string is not in domain.service formKeep the node disabled, confirm the draft still uses PLACEHOLDER_HA_ACTION; any later real value must use the dotted form. Do not guess with a real action to test it
The target is broader than expectedSeveral target types coexist, payload.target merged in because block=false, or area/floor/label membership changedAbort the change and return to one placeholder target type
Configured data was replaced by something elseA flow or global key reached through mergeContextClear the contaminated key, inventory what else writes it, and enforce a fixed schema
No response appears in msg.payload after successThis can be normalConfirm the action's metadata declares a response, then map it explicitly through Output Properties. Never fabricate a result
Stale operations appear after reconnectionQueue is set to something other than nonefirst, last, and all can all retain stale intent, and all is drained with pop(). Restore none for control Actions
Only a timeout is visible; HA's real state is uncertainThe response was lost, not necessarily the actionDo not retry immediately. Verify with an action-specific read-only query, keep a sanitized correlation value, and follow your idempotency and manual-recovery policy
Events: all produces a floodEvent Type is blank, or the event data JSON is not actually narrowing anythingDisable the node immediately, clear Debug, and review only the known PLACEHOLDER_EVENT_TYPE
Device is missing an expected trigger or actionThe integration or device does not expose that capability hereConfirm the HA device registry entry and integration support. Do not copy another device's configuration or guess
Time reports invalid, unavailable, or in the pastWrong property path, type, time zone, offset, or weekday selectionRoute to an error branch. Do not reuse an old value, and do not read "ignore past date" as permission to catch up
Calendar misses a last-minute changeThe change landed inside the roughly-15-minute polling gap, or a query exhausted its retriesIf polling latency is genuinely unacceptable for your use case, choose a different architecture rather than shortening an undocumented internal constant
Debug or logs expose a real target, tag, device, or webhook IDA live value slipped into an example, screenshot, or issue reportDisable the flow, delete the Debug message, rotate any exposed webhook ID, mask the affected identifiers, and follow your incident-reporting procedure — never paste the raw value into a public issue while explaining it
Why does the UI say Action when the JSON type is not action?
This pinned build keeps the persisted type api-call-service for compatibility with flows built before the rename. The palette label and the JSON type are allowed to differ; do not edit the JSON type by hand to match the label.
Does Block Input Overrides=true stop every dynamic influence on the node?
No. It blocks the payload-level overrides — action, target, and data — but a flow or global context key reached through mergeContext can still override configured data, and nothing stops the editor configuration itself from pointing at the wrong target. You still need allowlists, an inventory of who writes to that context key, and human review.
Can a target use entity, area, and label at the same time?
The runtime can combine several target keys at once, but that can widen the scope instead of narrowing it. A safe draft selects only one, most precise placeholder type at a time, and rechecks area, floor, or label membership before every call.
Is queue: all a reliable offline FIFO?
No. It only exists in memory, has no hard upper limit this guide can verify, and is drained with array pop() once the connection is ready — not necessarily in the order the messages arrived. It offers no exactly-once, deadline, or durability guarantee. Use none for control Actions.
Does every Action write its return value to msg.payload?
No. Response support depends on the action's own metadata; the node only exposes response?.response as a source named results, which still needs an explicit Output Properties mapping to land anywhere. Plan for it being undefined when the action has no response at all.
Can I retry an Action immediately after a timeout?
No. Home Assistant may already have executed it even though the response never made it back to Node-RED. Verify the outcome first with an action-specific read-only query, then follow your idempotency, retry-limit, and manual-recovery policy.
Can I leave Event Type blank in Events: all to explore what is out there?
Not recommended. This pinned build's editor and documentation both warn that receiving every event can overwhelm the WebSocket message queue, and event data can carry sensitive information. Identify the exact event type from Home Assistant's developer documentation, or in an isolated environment, before you observe it through a narrow field allowlist.
Why does my Device node lack an option shown in another tutorial?
Device capabilities come from the Home Assistant integration and the actual device; not every integration exposes the same triggers, actions, or capabilities. Do not invent a setting that is not there. When a capability is genuinely absent, fall back to a verifiable entity state, or a separately approved Action.
Can I use the home_assistant_client "connected" event to build a reconnection gate?
No. In this pinned build, Events: all only actually wires up states_loaded, services_loaded, running, and ready — the connected handler exists in code but is not wired to fire. Even after using running or ready as a health signal, query the state you need again and validate the timing and dedup key before you act on it.
Can Time's daily repeat guarantee exactly one run on a DST transition day?
No guarantee is made here. The schedule is built in the runtime environment's local time, and DST can produce a local time that either does not exist or happens twice on the transition day. Test both directions in an isolated environment, and deduplicate downstream by date and purpose rather than trusting the count of firings.
Can a webhook ID be part of an ordinary, public-facing URL?
Treat it as a secret, not as public information. Anyone who learns the ID may be able to trigger the flow behind it. Never put it in a repository, a screenshot, or a log. Reachability, authentication, and abuse prevention all need their own separate assessment.
Will Calendar deliver every missed event after a restart?
Do not assume that. This pinned build re-polls and schedules whatever still falls inside its current query window after a restart, but the in-memory timer queue and the emitted-event cache both disappear when the process ends. Your own flow has to define a lateness tolerance, a dedup strategy, and the conditions under which no catch-up happens at all.
Next

Where to go from here

now put it somewhere useful

You know how a call is built, and how a flow gets woken up. Next comes the safe way to wire the two together.

Part 7 takes both halves of this part into two concrete, everyday designs: motion-triggered lighting, built on the entity-state and time-scheduling models from this part plus the Wait Until pattern for a condition rather than a clock; and notifications and climate control, where the Action node's queue and idempotence rules from this part stop being theory and start deciding whether a reminder fires once or five times.

Open the full guide

Part 6 of the WoowTech Complete Node-RED Guide series on the Apporo blog.

Adapted from the WoowTech Complete Node-RED Guide, produced by WoowTech and released under CC BY 4.0. This adaptation is published by Apporo under the same licence.

Light · Air · Water · Control · apporo

Two ways to find out what Home Assistant just did, and what it is doing right now