Register
GET with Observe = 0. Server replies 2.05 Content plus an Observe option (starting sequence number).
An alarm notification that arrives late can be worse than no notification if the client treats it as current. Observe therefore needs more than “server push”: the client must know which registration a notification belongs to, whether the value is fresh, and what to do when UDP reorders updates.
This page follows the subscription through token binding, Max-Age, liveness checks, sequence comparison, cancellation, and stale-update rejection so the pushed value can be trusted.
After this page, you should be able to:
Observe: 0 through token-bound notifications and cancellation.CoAP Observe Server Push teaches the main Observe pattern: replace repeated polling with change-driven notifications. This page narrows in on the deeper operational contract that makes the pattern safe: which token identifies the subscription, how long a value remains fresh, how the server detects unreachable observers, and how the client rejects stale notifications when UDP reorders datagrams.
The CoAP Observe contract therefore keeps the parent idea intact while adding the evidence needed for token binding, freshness, liveness, cancellation, and stale-update rejection.
Use it when an observed resource controls alarms, industrial telemetry, building automation, battery gateways, NAT-crossing clients, or any application where a stale pushed value could be mistaken for current state.
Observe (RFC 7641) adds a lightweight publish/subscribe relationship on top of plain CoAP request/response, using a single option numbered 6. A client registers by sending an ordinary GET with the Observe option set to 0. The server records the client — keyed by its source endpoint, its Token, and the requested resource — and answers 2.05 Content that itself carries an Observe option holding a sequence number. From that moment the client is an observer.
Whenever the resource's representation changes, the server sends a fresh 2.05 Content notification carrying an incremented Observe value and, crucially, the same Token as the original registration. That Token is how the client knows an unsolicited notification belongs to a subscription it opened. To stop, the client either sends a GET with Observe set to 1 (explicit cancel) or rejects a Confirmable notification with an RST.
A concrete trace shows the contract. If a dashboard registers GET /sensors/temp with Token 0x4a01, the server might answer 2.05 Content, Observe 12, and Max-Age: 60. Fourteen seconds later the pushed update uses a new Message ID but the same Token 0x4a01 and Observe 13. The client must keep that Token reserved while the observation is active; reusing it for another outstanding exchange would make later pushes ambiguous.
sequenceDiagram
participant C as Client
participant S as Server
C->>S: GET (Observe: 0, Token)
S-->>C: 2.05 Content (Observe: 12, Token)
Note over S: Resource value changes
S->>C: 2.05 Content (Observe: 13, same Token)
S->>C: 2.05 Content (Observe: 14, same Token)
GET with Observe = 0. Server replies 2.05 Content plus an Observe option (starting sequence number).
On each change the server sends 2.05 Content with a higher Observe value, reusing the registration Token.
GET with Observe = 1, or reject a CON notification with RST, to end the relationship.
The Token ties every later notification to the subscription; without it the client cannot match a push.
Observe promises eventual consistency, not guaranteed delivery. Notifications are usually Non-confirmable, so any single one may be lost; the model only guarantees that the client eventually converges on the current state through later notifications. That has two practical consequences you must design for: freshness and liveness.
Freshness is governed by Max-Age. Each notification says how long its value can be considered current. A well-behaved server sends the next notification before the previous one's Max-Age expires, so a live resource never looks stale; if the client's Max-Age lapses with no new push, the client should re-GET (re-register) rather than trust an aged value. Liveness is checked with the occasional Confirmable notification: if its retransmissions all go unanswered, the server concludes the observer is gone and removes it, which is how "ghost observers" behind a closed NAT binding get cleaned up.
For example, a battery gateway observing /tank/level might send ordinary NON notifications only when the level changes by at least 2% or every 60 seconds, whichever comes first, with Max-Age: 120. The client treats silence for 120 seconds as stale and re-registers instead of assuming the last level is still safe. The server might make every tenth notification Confirmable, or use a five-minute CON probe, so closed NAT bindings and rebooted clients are detected without turning every update into an ACK exchange. If the sensor chatters around the 2% threshold, the server should coalesce changes and publish the newest value at the pacing limit; Observe reduces polling traffic only when push frequency is also controlled.
Because CoAP runs over UDP, notifications can arrive out of order: a client may receive an older reading after a newer one. If it simply trusted arrival order it would display stale data. The Observe option value — a number up to 24 bits wide that the server increments per notification — exists to solve exactly this, and RFC 7641 defines a precise comparison so wraparound does not fool the client.
Let V1 be the value of the notification the client currently accepts, received at time T1, and V2 the value of a newly arrived notification at time T2. The new notification is treated as fresher only if (V1 < V2 and V2 − V1 < 2^23) or (V1 > V2 and V1 − V2 > 2^23) — the two cases that correctly handle counter wraparound — or if more than 128 seconds have elapsed (T2 > T1 + 128 s), after which the later arrival simply wins regardless of value. Anything that fails all three tests is a reordered straggler and is discarded.
Suppose the client has accepted Observe 120 and then receives 124; because the difference is small and positive, 124 becomes the new displayed state. If 122 arrives two seconds later, it is discarded as a late datagram. Near wraparound, a server can move from 16777214 to 1; the large reverse numeric difference tells the client that 1 is actually newer, not older. After a long quiet period above 128 seconds, the client accepts the later arrival as a new baseline because old ordering evidence has expired.
This is why the Observe value must be read as a reordering sequence number, not as a reliable event counter. Lost Non-confirmable notifications mean the numbers a client sees can skip; the guarantee is ordering and eventual convergence, not that every increment is delivered.
Up to 24 bits, incremented per notification, used purely to decide which of two notifications is newer.
The 2^23 threshold distinguishes a genuine increment from a wrapped-around older value.
After 128 s, the later arrival is accepted regardless of the numeric comparison.
A notification failing all freshness tests is a reordered duplicate and is silently discarded.
Before shipping a CoAP Observe contract, verify these records:
Return to CoAP Observe Server Push, then continue to CoAP Advanced Features for block-wise transfer and large-payload considerations.