Skip to main content
Version: 3.0 (next)

CoAP CoAP Integration Guide

GET, PUT, POST and DELETE resources on constrained devices and gateways over UDP, and start a pipeline on every notification of an observed resource.

Protocol
CoAP client over UDP (RFC 7252)
Default port
5683
Operations
GET · PUT · POST · DELETE
Triggers
Observe, once per notification

CoAP (Constrained Application Protocol) is the web transfer protocol for constrained devices: sensors, actuators, gateways and building controllers on lossy or low-power networks. It looks like HTTP (resources addressed by path, the methods GET, PUT, POST and DELETE, response codes such as 2.05 Content and 4.04 Not Found), but it runs over UDP with compact binary messages. MaestroHub connects as a CoAP client to a device or gateway (the CoAP server). This guide covers connection setup, how messages are sent and retransmitted, how payloads are encoded and decoded, the five function types, and how the matching nodes and trigger slot into the Pipeline Designer.

Overview​

The CoAP connector provides:

  • GET, PUT, POST and DELETE on any resource path, with query options
  • Observe trigger (RFC 7641): one pipeline event per notification, registered again on its own after an outage
  • Confirmable or non-confirmable messages, with RFC 7252 retransmission for confirmable ones
  • Block-wise transfer (RFC 7959) for payloads larger than one datagram
  • Content-Format aware payloads: text/plain, application/json, application/cbor and application/octet-stream sent; text, JSON and CBOR decoded into values on the way back
  • Client only: no listening port, and cloud-metadata and loopback destinations refused by default

A client that never listens​

MaestroHub never binds port 5683, or any other known port. Each connection opens one UDP socket with an ephemeral local port chosen by the operating system, connected to the device's address. Every request leaves from that socket, Observe registrations included, and replies and Observe notifications return to it: a CoAP server answers the endpoint a request came from. The kernel only delivers datagrams from the connected device address to that socket.

So a firewall only has to allow outbound UDP to the device's port and the replies to it. A device that sends requests of its own to MaestroHub gets 4.04 Not Found: the connector serves no resources.

The socket carries one request at a time (RFC 7252's NSTART = 1, a client keeps at most one outstanding interaction with a server), so per-connection concurrency is 1. Observe notifications arrive on the same socket and do not hold it.

Confirmable and non-confirmable messages​

Every request function has a Message Type:

Message TypeWhat happensUse it for
confirmable (default)The server acknowledges the message. Until it does, MaestroHub sends it again every ACK Timeout, with the same message ID, up to Max Retransmissions times. The server's duplicate detection makes the copies one request.Writes, and reads on lossy networks
nonConfirmableSent once, with no acknowledgement and no retransmission. MaestroHub still waits for the response until the timeout.Frequent reads on a reliable network, where a lost reading is replaced by the next one

A request still unanswered when its function's Timeout ends fails, as a transient error. The default Timeout of 2 minutes covers RFC 7252's worst case for the default transmission parameters (MAX_TRANSMIT_WAIT, 93 seconds with ACK Timeout 2s and Max Retransmissions 4).

Block-wise transfer​

A payload larger than one block is sent and received in blocks of Block Size bytes (RFC 7959), and reassembled before it reaches the pipeline. It is transparent to the function: a GET of a 20 KB document returns the whole document. A payload sent or reassembled cannot exceed the connection's Max Payload Size; a larger response fails instead of filling memory.

Content-Format: how payloads are read​

A CoAP response declares its payload's Content-Format. MaestroHub decodes the payload by that declaration and never guesses:

Declared Content-Formatvalue isvalueEncoding
text/plain, application/link-format, other text/*A stringtext
application/json, and +json formats such as application/senml+jsonThe parsed JSON valuejson
application/cbor, and +cbor formats such as application/senml+cborThe CBOR value, converted to JSONjson
application/octet-stream, any other format, or no Content-Format at allThe bytes, as base64base64
  • valueEncoding always says which of the three value is, so a pipeline never has to inspect the value to know how to read it.
  • An empty payload has no value and no valueEncoding.
  • A payload that does not parse as its declared format (invalid UTF-8 for text, malformed JSON, malformed CBOR) is an error, never a best-effort value.
  • CBOR conversion: byte strings become base64, integer map keys become their decimal text (the shape SenML and LwM2M use), a time tag becomes an RFC 3339 UTC string, other tags become {"tag": n, "value": v}. NaN and the infinities, which JSON cannot carry, make the payload an error rather than an invented stand-in.

Outcomes: what counts as success​

Response or failureOutcomeRetried by the node settings?
2.xx (2.01 Created, 2.02 Deleted, 2.04 Changed, 2.05 Content, …)Success-
4.xx: the server refused the request (4.00 Bad Request, 4.04 Not Found, 4.05 Method Not Allowed, 4.15 Unsupported Content-Format, …)Permanent: it did not act, and the same request will be refused againNo
4.08 Request Entity Incomplete (a block was lost) and 4.29 Too Many RequestsTransientYes
5.xx (5.00 Internal Server Error, 5.03 Service Unavailable, 5.04 Gateway Timeout, …)TransientYes
A timeout after the request was sent, a lost connection, an ICMP unreachable, an undecodable responseTransient: the server may have acted on the request, or on an earlier retransmitted copyYes for GET, PUT and DELETE; never for POST
A refused destination (egress guard), a host name that does not resolve, a function parameter that fails validationPermanent: nothing was sentNo

Connection Configuration​

Creating a CoAP Connection​

Navigate to Connections → New Connection → CoAP and configure the form. The settings are grouped into Connection, Transmission and Security; the Functions, Scaling and Health tabs unlock after the connection is saved.

1. Profile Information​

FieldDefaultDescription
Profile Name-A descriptive name for this connection profile (required). Must be unique across all connections.
Description-Optional description for this CoAP connection
Labels-Key-value pairs to categorize and organize the connection, e.g. site: building-a, device: gateway-1

2. Connection​

FieldDefaultDescription
Host-Device or gateway host name or IP address (required). Example: 10.0.5.30. A host name, not a URI: coap://… is refused, and the port goes in Port.
Port5683UDP port of the CoAP server, 1-65535. 5683 is the CoAP default. 5684 is CoAP over DTLS, which this version does not support.
Connect Timeout10sHow long Connect waits for the server to answer a CoAP ping (an empty confirmable message every server must answer with a Reset), 1s-5m

3. Transmission​

FieldDefaultDescription
ACK Timeout2sRFC 7252 ACK_TIMEOUT: how long to wait for the acknowledgement of a confirmable message before sending it again, 100ms-1m. Raise it on slow or duty-cycled networks.
Max Retransmissions4RFC 7252 MAX_RETRANSMIT: how many times a confirmable message is sent again, with the same message ID, while it is not acknowledged, 0-10
Block Size1024RFC 7959 block size in bytes for payloads larger than one datagram: 16, 32, 64, 128, 256, 512 or 1024. Lower it for devices with small buffers.
Max Payload Size1048576 (1 MiB)Largest payload, in bytes, sent or reassembled from blocks, 1024-16777216. Must be at least the Block Size. A larger response fails instead of filling memory; a larger payload to send is refused before anything is sent.

4. Security: Egress Guard​

Both toggles are off by default.

FieldDefaultDescription
Allow Loopback DestinationsfalsePermit 127.0.0.0/8 and ::1. Off by default: a loopback address is almost never the intended target of an outbound device connector.
Allow Cloud-Metadata DestinationsfalsePermit 169.254.169.254 (AWS) and metadata.google.internal (GCP). Off by default: these endpoints return cloud credentials without authentication.

The guard runs on every connect: the host name is resolved once, and both the name and the address chosen are checked before the socket is opened, so a name cannot resolve to an allowed address for the check and a blocked one for the socket. A host re-pointed at a blocked address is caught on the next reconnect. A refusal is a permanent error that names the fix:

connect to CoAP server localhost:5683: coap: destination blocked by egress policy: "localhost" resolves to loopback IP 127.0.0.1; set allowLoopback=true to permit

Testing the Connection​

Click Test Connection to try the current settings before saving. Connect opens the socket and sends a CoAP ping: an empty confirmable message that every CoAP server must answer with a Reset. It sends no request, so testing never reads or changes a resource. A server that does not answer within Connect Timeout fails the test.

Once the connection runs, its health check sends the same ping and reports the round trip. A ping that is not answered drops the socket, and the connection reconnects on a fresh one.

Function Builder​

Creating CoAP Functions​

After the connection is saved:

  1. Open the connection and navigate to the Functions tab
  2. Click New Function to open the function type selection dialog
  3. Choose GET, PUT, POST, DELETE, or Observe
  4. Fill the Basic tab (name, description, labels) and the Configuration tab (operation-specific parameters)
  5. Use the Test button to run the function against the live device before saving

Every parameter is checked before anything is sent: a bad path, query, Content-Format or payload is refused with the reason, and nothing reaches the device.

Resource Path takes a path, not a URI: the host and port come from the connection, and the query has its own field. Each /-separated segment becomes one Uri-Path option, with percent-escapes decoded (/a%20b is the segment a b). / alone is the root resource. A scheme (coap://), ? or #, an empty segment (//), a . or .. segment, or a segment longer than 255 bytes is refused.

Query takes key=value parts separated by & (a leading ? is ignored). Each part becomes one Uri-Query option, percent-decoded, at most 255 bytes.

GET (coap.get)​

Purpose: Reads a resource and returns the response code, the decoded payload and its Content-Format. Large representations are fetched block by block.

ParameterTypeRequiredDefaultDescription
Resource PathStringYes-Path of the resource, e.g. /sensors/temp. Supports ((parameter)) syntax.
QueryStringNo-Optional query, e.g. unit=C&avg=60. Supports ((parameter)) syntax.
AcceptSelectNoserver defaultAsk the server for this Content-Format: text/plain, application/json, application/cbor, application/octet-stream or application/link-format. Leave empty to take its default.
Message TypeSelectNoconfirmableconfirmable or nonConfirmable; see Confirmable and non-confirmable messages
TimeoutDurationNo2mBound on this request, including retransmissions and every block, 1s-1h

Examples: read a temperature sensor at /sensors/temp; list a device's resources from /.well-known/core (returned as application/link-format text); read a gateway status document as JSON.

PUT (coap.put)​

Purpose: Replaces the state of a resource, such as an actuator's set-point, with the given payload in the chosen Content-Format. PUT is idempotent.

ParameterTypeRequiredDefaultDescription
Resource PathStringYes-Path of the resource, e.g. /actuators/valve. Supports ((parameter)) syntax.
QueryStringNo-Optional query, e.g. mode=fast. Supports ((parameter)) syntax.
PayloadStringYes-What to send, as text; see Payload by Content-Format. Supports ((parameter)) syntax. A PUT without a payload is refused.
Content-FormatSelectNotext/plainContent-Format of the payload, sent as the Content-Format option: text/plain, application/json, application/cbor or application/octet-stream
AcceptSelectNoserver defaultAsk the server for this Content-Format in its response
Message TypeSelectNoconfirmableconfirmable or nonConfirmable
TimeoutDurationNo2mBound on this request, including retransmissions and every block, 1s-1h

Examples: set a dimmer to 75 at /lights/1/level; write a JSON configuration document to /config.

POST (coap.post)​

Purpose: Asks the resource to process the payload: create a child resource, run an action, or accept a record. A 2.01 Created response names what it created in location.

ParameterTypeRequiredDefaultDescription
Resource PathStringYes-Path of the resource, e.g. /actuators/pump/start. Supports ((parameter)) syntax.
QueryStringNo-Optional query, e.g. id=7. Supports ((parameter)) syntax.
PayloadStringNo-What to send, as text; see Payload by Content-Format. Leave empty to POST without a payload. Supports ((parameter)) syntax.
Content-FormatSelectNotext/plainContent-Format of the payload, sent as the Content-Format option when there is a payload
AcceptSelectNoserver defaultAsk the server for this Content-Format in its response
Message TypeSelectNoconfirmableconfirmable or nonConfirmable
TimeoutDurationNo2mBound on this request, including retransmissions and every block, 1s-1h

Examples: start a pump cycle with POST /actuators/pump/start; create a schedule entry under /schedules.

A POST is sent at most once

POST is not idempotent: sending it again can run the action again. MaestroHub sends it once per call. If its outcome is unknown (no response before the timeout, a lost connection), the node fails with coap.post command outcome unknown and the POST is never sent again automatically: not by the connector, not by a node's Retry on Fail or Retry on Timeout, and not by a ForEach retry. A POST the server refused (4.xx) fails with the device refused the coap.post command and is not retried either. Check the device before running it again.

Within one call, a confirmable POST may be retransmitted with the same message ID until it is acknowledged; the server's duplicate detection treats the copies as one request.

DELETE (coap.delete)​

Purpose: Deletes a resource and returns the server's response code. DELETE is idempotent. A refusal such as 4.04 Not Found or 4.05 Method Not Allowed is reported as the device's answer.

ParameterTypeRequiredDefaultDescription
Resource PathStringYes-Path of the resource to delete, e.g. /schedules/7. Supports ((parameter)) syntax.
QueryStringNo-Optional query. Supports ((parameter)) syntax.
Message TypeSelectNoconfirmableconfirmable or nonConfirmable
TimeoutDurationNo2mBound on this request, including retransmissions, 1s-1h

Examples: remove a schedule entry at /schedules/7; clear a stored alarm at /alarms/42.

Observe (coap.observe)​

Purpose: Starts a pipeline for every notification of an observed resource (RFC 7641): the current value at registration, then each change. This function type is a pipeline trigger: see Pipeline Integration and the CoAP Observe Trigger node.

ParameterTypeRequiredDefaultDescription
Resource PathStringYes-Path of the observable resource, e.g. /sensors/temp
QueryStringNo-Optional query sent with the registration
AcceptSelectNoserver defaultAsk the server for this Content-Format in its notifications

How an observation is kept alive:

  • Registration is a GET with the Observe option from the connection's own socket, and notifications return to that socket. A path that does not exist or is refused fails the subscription with the server's code, and it is retried. A resource that answers without the Observe option is not observable: it sent its value once but will not notify, and the subscription fails with that reason.
  • Re-registration: the observation is registered again when the socket is lost (a reconnect), when the server ends it (an error notification, or a final notification without the Observe option), and when a notification is overdue: no notification for the last one's Max-Age (60 seconds when the server sends none) plus 10 seconds, the point at which RFC 7641 lets a client assume the observation was lost. Attempts back off from 0.5 seconds to 30 seconds while the device stays away.
  • No repeat on re-registration: a re-registration answers with the current value. When it is the value already delivered, it is not emitted again, so a quiet resource does not start a run every minute.
  • One Bad event per outage: when the observation fails (the link stays lost, a re-registration is refused or unanswered, the server ends it with an error code, or a notification does not parse as its Content-Format), the trigger fires once with quality bad and the cause, carrying the last value delivered. A reconnect that restores the observation within half a second raises no event at all. The first good notification afterwards is the recovery event, with quality good, delivered even when its value has not changed.

Each event's payload:

{
"path": "/sensors/temp",
"code": "2.05",
"status": "Content",
"contentFormat": "application/json",
"value": { "t": 21.5 },
"valueEncoding": "json",
"bytes": 10,
"observe": 17,
"etag": "a1b2",
"maxAge": 30,
"serverTimestamp": "2026-10-20T10:14:03.240Z"
}

See the CoAP Observe Trigger node for every field and the _metadata.

Payload by Content-Format​

PUT and POST take the payload as text, in the form the Content-Format names:

Content-FormatPayload textSent on the wire
text/plain (default)Any UTF-8 textThe text's bytes
application/jsonJSON text; must parseThe JSON text as given
application/cborOne JSON value, as JSON textThat value encoded as CBOR; a whole number stays a CBOR integer
application/octet-streamBase64 (padded or not)The decoded bytes

A payload that does not match its Content-Format (payload is not valid JSON, application/octet-stream takes the bytes as base64, and the payload is not valid base64) is refused before anything is sent. A payload larger than the connection's Max Payload Size is refused the same way.

Using Parameters​

Resource Path, Query and Payload on the GET, PUT, POST and DELETE functions support ((parameterName)) placeholders. Parameters are detected as you type and appear in the Function Parameters panel on the Configuration tab, where you set each one's type, requiredness, default, and description. At execution the pipeline node binds them, so one function serves many devices or set-points: for example Resource Path /lights/((lightId))/level and Payload ((level)). A value that still holds a placeholder is checked again, in full, when the call runs.

Per-call timeouts​

The Timeout on a function is one layer of the deadline MaestroHub composes for every call; the pipeline, node, function, and connection layers each contribute, and the tightest bound wins. See Connection Timeouts for the full hierarchy. It bounds the whole request: every retransmission and every block. Keep it longer than ACK Timeout × (Max Retransmissions + 1), so a confirmable request has time for all its retransmissions.

Pipeline Integration​

Use the CoAP functions you configure here as nodes inside the Pipeline Designer. GET, PUT, POST and DELETE are exposed as matching connector nodes, and Observe functions drive the CoAP trigger node:

  • CoAP GET (connected.coap.get): executes a coap.get function
  • CoAP PUT (connected.coap.put): executes a coap.put function
  • CoAP POST (connected.coap.post): executes a coap.post function
  • CoAP DELETE (connected.coap.delete): executes a coap.delete function
  • CoAP Observe Trigger (trigger.coap): starts a pipeline for each notification of a coap.observe function

For node-level details (output structure, execution settings), see the CoAP Nodes and CoAP Observe Trigger pages.

Writes act on the device now, or not at all

PUT, POST and DELETE are realtime device writes:

  • They are never buffered by Store & Forward. A write issued while the device is unreachable fails; it is not replayed later, when the value may no longer be right.
  • A pipeline run started by a late (replayed) value refuses them, unless the node allows late input.
  • PUT and DELETE are idempotent: the same PUT leaves the resource in the same state, and a second DELETE leaves it deleted (the device may answer 2.02 or 4.04). A node's retry settings may send them again after a transient failure.
  • POST is a control command: it is sent at most once and never re-sent automatically; see POST.

For orchestration strategies that mix CoAP with other data sources, see the Nodes page.

Common Use Cases​

Sensor Telemetry to the UNS​

Author an Observe function on each sensor resource (for example /sensors/temp), then use the CoAP Observe Trigger node to route every notification to the Unified Namespace. The device pushes each change, so there is no polling; an outage arrives as one bad event that a UNS Publish node stores with its quality.

Polling Devices That Do Not Notify​

For resources that are not observable, drive a GET from a Schedule trigger. Use nonConfirmable on a reliable network to keep traffic down, and branch on result.valueEncoding when a device may answer in more than one Content-Format.

Set-Points and Actuators​

Receive a set-point from an upstream system or operator UI, validate it with a Condition node, then PUT it to the actuator resource (/lights/1/level, /actuators/valve). Because PUT is idempotent, a retry after a transient failure leaves the device in the same state.

Actions and Records​

Use POST for what the device does once per request: start a pump cycle, acknowledge an alarm, add a schedule entry. Read result.location to learn the path of an entry the device created, and DELETE it later by that path. Since a POST whose outcome is unknown is never re-sent, read the device state with a GET before running it again.

Resource Discovery​

GET /.well-known/core returns the device's resource list in CoRE Link Format as text, which a JavaScript node can parse to find the paths to observe.

Troubleshooting​

Error Reference​

What you seeMeaningRetried?
coap.get /sensors/x: server answered 4.04 NotFoundThe server refused the request (also 4.00, 4.01, 4.03, 4.05, 4.15, …)No, it will not change on retry
server answered 4.29 TooManyRequests / 5.03 ServiceUnavailableThe server is busy or failedYes
response payload: payload declared application/json is not valid JSONThe response arrived but does not parse as its declared Content-FormatYes for GET, PUT and DELETE (the request may have acted); never for POST
coap.post command outcome unknownA POST failed after it may have reached the deviceNever, whatever the retry settings: check the device
the device refused the coap.post commandThe server answered a POST with 4.xxNo
not connected to CoAP server host:portThe connection is downYes, after reconnect
coap: destination blocked by egress policyThe host resolves to loopback or a cloud-metadata addressNo; see Security: Egress Guard
the server answered without an Observe option: the resource is not observableAn Observe function names a resource that does not notifyNo; poll it with a GET instead
payload is N bytes, over the connection's max payload size of MA PUT or POST payload is larger than Max Payload SizeNo; shorten it or raise the limit

Connection Issues​

SymptomPossible CauseSolution
Test Connection fails with did not answer a pingWrong host or port, the device's CoAP service is not running, or a firewall drops UDPCheck the port (5683 by default), and that outbound UDP to it and its replies are allowed.
The device only listens on 5684The device requires CoAP over DTLS (coaps)Not supported in this version. Enable plain CoAP on the device, or use a gateway that does.
host … must be a host name or IP address, not a URIHost holds coap://…Enter only the host name or IP address; the port goes in Port and the path in each function.
Requests time out on a slow or sleepy networkACK Timeout too short for the network, so retransmissions run outRaise ACK Timeout (and the function's Timeout with it).
Large responses failThe device's buffers cannot take the Block Size, or the response exceeds Max Payload SizeLower Block Size, or raise Max Payload Size.
The device logs a new Observe registration about every 70 secondsExpected for a quiet resource: no notification for Max-Age (60 seconds when the device sends none) plus 10 seconds, so the observation is registered again. An unchanged value is not emitted again.Nothing to fix. A device that sends a longer Max-Age with its notifications is re-registered less often.
The Observe trigger reports bad and no value followsThe observation could not be restored: the device is unreachable, or it refuses the re-registrationThe cause is in error on the bad event. Registration is retried with backoff up to every 30 seconds; the next good notification is the recovery event.
value is base64 when text or JSON was expectedThe device sent no Content-Format, or one the connector does not decodeSet Accept to the format you want, or decode the base64 in a JavaScript node. valueEncoding says base64.

Not in this version​

  • DTLS (coaps://, port 5684) and other security modes
  • CoAP over TCP or WebSockets (RFC 8323)
  • OSCORE (RFC 8613) object security
  • Multicast requests and multicast discovery
  • A Resource Directory (RFC 9176)
  • Acting as a CoAP server: MaestroHub serves no resources and does not accept requests from devices