<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fuad Husnan</title>
    <description>The latest articles on DEV Community by Fuad Husnan (@fuadhusnan_f44f3e13).</description>
    <link>https://dev.to/fuadhusnan_f44f3e13</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3914266%2F094c796a-3db7-45ac-abff-19a1b85ff8a7.png</url>
      <title>DEV Community: Fuad Husnan</title>
      <link>https://dev.to/fuadhusnan_f44f3e13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fuadhusnan_f44f3e13"/>
    <language>en</language>
    <item>
      <title>Event-Driven Design: Building Real-Time Streams with WebSockets and Webhooks</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sat, 05 Sep 2026 11:51:38 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/event-driven-design-building-real-time-streams-with-websockets-and-webhooks-4a2m</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/event-driven-design-building-real-time-streams-with-websockets-and-webhooks-4a2m</guid>
      <description>&lt;p&gt;Event-driven design turns your backend from a system that waits to be asked into one that reacts as things happen. Instead of clients polling an endpoint every few seconds to check "did anything change yet," the server pushes updates the moment they occur. Two mechanisms do most of the heavy lifting here: WebSockets and webhooks. They solve different halves of the same problem, and most production systems that claim to be "real-time" are actually running both at once.&lt;/p&gt;

&lt;p&gt;This guide breaks down how each one works, when to reach for it, and how to wire them together without turning your event pipeline into a source of on-call pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Polling Doesn't Scale
&lt;/h2&gt;

&lt;p&gt;Before event-driven patterns took over, the default was polling: a client hits an API every few seconds, checks a timestamp or a status field, and does nothing 95% of the time. It works, but it wastes requests, adds latency proportional to your polling interval, and gets expensive fast once you have thousands of clients doing it simultaneously.&lt;/p&gt;

&lt;p&gt;Event-driven architectures flip this. A backend receives an event, validates it, stores it, and hands it off to whatever needs to act on it. Polling still has a place as a fallback or reconciliation mechanism, but it's rarely the first choice once a system can push events instead of waiting to be asked.&lt;/p&gt;

&lt;p&gt;The distinction that actually matters when picking your mechanism isn't "is this real-time" — both WebSockets and webhooks are real-time in the sense that they avoid polling delay. The distinction is &lt;em&gt;who initiates the connection&lt;/em&gt; and &lt;em&gt;whether that connection stays open&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebSockets: Persistent, Bidirectional Connections
&lt;/h2&gt;

&lt;p&gt;A WebSocket starts as an ordinary HTTP request. The client sends an &lt;code&gt;Upgrade: websocket&lt;/code&gt; header, the server responds with &lt;code&gt;HTTP 101 Switching Protocols&lt;/code&gt;, and from that point on the connection stops speaking HTTP entirely. What's left is a raw TCP socket with a thin framing layer — no headers per message, no request-response cycle, just a persistent, full-duplex pipe. Removing HTTP overhead from every message is what makes WebSockets so much cheaper per message than repeated HTTP calls.&lt;/p&gt;

&lt;p&gt;That persistence is the whole value proposition. Once the handshake completes, either side can send data at any time without renegotiating a connection. This is why WebSockets are the natural fit for chat applications, live dashboards, multiplayer games, and collaborative editors — anywhere the client is a browser or mobile app that needs to both receive pushes and send messages back on the same channel.&lt;/p&gt;

&lt;p&gt;Here's a minimal WebSocket server in Node.js using the &lt;code&gt;ws&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Client connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Received event:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Broadcast to all connected clients&lt;/span&gt;
    &lt;span class="nx"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;broadcast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Client disconnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a corresponding browser client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://your-server.com:8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subscribe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-updates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Update received:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade-off is that "always on" comes with real operational cost. A WebSocket server has to hold connection state in memory for every connected client, which means scaling horizontally requires a pub/sub layer so that a message published on one node reaches a client connected to a different node. You also have to handle reconnects, heartbeats to detect dead connections, and uneven client network conditions. Keeping state coherent across all of that under load is the actual hard part of "real-time" — the streaming itself is the easy half.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks: Stateless, One-Way Notifications
&lt;/h2&gt;

&lt;p&gt;A webhook is the opposite shape. It's a plain HTTP POST request sent from one application to another when a specific event happens. There's no persistent connection and no handshake to maintain — each event is an independent, stateless HTTP call. The source system decides something happened, fires a request at a URL you registered in advance, and moves on. Your server receives it, acknowledges it, and does whatever it needs to do with the payload.&lt;/p&gt;

&lt;p&gt;This makes webhooks the right tool whenever the receiving side doesn't need to talk back on the same channel — payment confirmations, repository push notifications, CRM record updates, or any server-to-server event where "tell me when X happens" is the entire requirement. Webhooks don't help when the client is a browser that can't expose a public endpoint of its own, and they're inherently one-directional, which is exactly why they pair so well with WebSockets rather than replacing them.&lt;/p&gt;

&lt;p&gt;A basic webhook receiver in Express looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WEBHOOK_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Acknowledge immediately, process asynchronously&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;processOrderEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to process webhook:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// route to retry queue or dead-letter storage&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details in that snippet matter more than they look. First, signature verification with &lt;code&gt;timingSafeEqual&lt;/code&gt; prevents timing attacks against your secret comparison — a plain &lt;code&gt;===&lt;/code&gt; check leaks information about how many characters matched. Second, the handler responds before processing finishes. Webhook senders enforce timeouts and will retry on anything that looks like a failure, so slow synchronous processing inside the request handler is a common cause of duplicate deliveries.&lt;/p&gt;

&lt;p&gt;That duplication risk is structural, not a bug you can code around. If your server doesn't respond fast enough, or the response is lost in transit, the sender assumes failure and retries — which means your endpoint needs to be idempotent. Track processed event IDs and skip anything you've already handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fragmentation Is the Real Cost of Webhooks
&lt;/h2&gt;

&lt;p&gt;The retry problem is solvable with idempotency keys. The harder problem is that every webhook provider has historically implemented its own signature scheme, retry policy, and payload shape. Handling ten providers has meant writing ten different verifiers and ten different retry assumptions.&lt;/p&gt;

&lt;p&gt;The Standard Webhooks specification exists specifically to close that gap. It defines a common signing scheme, delivery format, and verification approach so that consumers don't have to relearn webhook handling for every new integration. As of 2026, it has been adopted by a range of companies including OpenAI, Anthropic, Google Gemini, Twilio, PagerDuty, and Supabase, among others — worth checking for before you write yet another one-off signature verifier from scratch.&lt;/p&gt;

&lt;p&gt;On the payload side, CloudEvents has become the closest thing to a vendor-neutral standard for structuring the event body itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"specversion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.example.order.created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/orders/service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A234-1234-1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-25T17:31:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"datacontenttype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're building a system that emits webhooks to your own customers, aligning early with Standard Webhooks for delivery and CloudEvents for payload shape saves you from designing a bespoke format that every integrator has to learn from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining Both in One Architecture
&lt;/h2&gt;

&lt;p&gt;Most systems that need to feel real-time end up running WebSockets and webhooks side by side rather than choosing one. A typical e-commerce flow illustrates why: a payment provider fires a webhook when a charge succeeds, your backend updates the order record, and then that update needs to reach the customer's browser instantly. That last leg is a WebSocket push, not another webhook, because the browser has no public endpoint to receive one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clientsByOrder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// orderId -&amp;gt; Set of sockets&lt;/span&gt;

&lt;span class="nx"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orderId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;clientsByOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;clientsByOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;clientsByOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;clientsByOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;updateOrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscribers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clientsByOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern — webhook in, database update, WebSocket push out — is the backbone of most "live" dashboards you've used. The webhook handles the reliable, asynchronous, server-to-server leg. The WebSocket handles the low-latency leg to a connected browser. Neither one replaces the other; they're doing different jobs in the same pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Realities Worth Planning For
&lt;/h2&gt;

&lt;p&gt;Building the happy path for either mechanism takes an afternoon. Making it production-grade is where the real time goes. For webhooks, budget for retry queues, dead-letter handling, and a delivery dashboard so you can see what failed and why — this consistently takes longer than teams expect, which is exactly why platforms like Svix, Hookdeck, and Convoy exist to take it off your plate if you're sending webhooks to your own customers, or Nango and similar tools if you're receiving them from external providers.&lt;/p&gt;

&lt;p&gt;For WebSockets, the equivalent tax is connection lifecycle management: heartbeats to detect half-open connections, reconnection logic on the client, and a pub/sub layer (Redis, NATS, or a managed service) so that broadcasts reach clients regardless of which server instance they're connected to. None of this is optional past a handful of concurrent users — it's the difference between a demo and a system that survives a network blip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Between Them
&lt;/h2&gt;

&lt;p&gt;The decision comes down to three questions. Does the receiving side need to send data back on the same channel, or is a one-way notification enough? Can the receiver expose a public HTTP endpoint, or is it a browser/mobile client sitting behind NAT? And does the interaction represent a continuous session, or a series of discrete events with gaps between them?&lt;/p&gt;

&lt;p&gt;A trading terminal or multiplayer game is a continuous session — WebSockets. A payment confirmation or repository update is a discrete, one-way event — a webhook. Most real systems have both kinds of interaction happening simultaneously, which is why the architectures that hold up under load treat WebSockets and webhooks as complementary tools rather than competing choices.&lt;/p&gt;

&lt;p&gt;If you're starting from scratch, don't build the retry infrastructure or the connection-scaling layer yourself before you need to. Start with the standard patterns above, adopt Standard Webhooks and CloudEvents where they fit, and reach for dedicated infrastructure once your event volume justifies the operational overhead of running it in-house.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>GraphQL vs. gRPC: Choosing Your Modern API Stack</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sat, 05 Sep 2026 11:35:31 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/graphql-vs-grpc-choosing-your-modern-api-stack-34g</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/graphql-vs-grpc-choosing-your-modern-api-stack-34g</guid>
      <description>&lt;p&gt;A fintech team spends eight months migrating its entire backend to GraphQL because it "sounded modern," then watches performance degrade under load because every mobile client is now issuing deeply nested queries the resolver layer was never built to handle. Meanwhile, a logistics company bolts gRPC onto its public-facing customer API, only to discover that partner developers can't test an endpoint without generating client stubs first. Both teams solved a problem they didn't have and created one they didn't expect. GraphQL and gRPC are not competing answers to the same question — they optimize for different consumers, different network conditions, and different failure modes, and mixing up which is which is what causes the expensive rewrites.&lt;/p&gt;

&lt;p&gt;This guide breaks down what each technology actually does well, where the tradeoffs bite, and how to decide between them — or combine them — for a real production system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GraphQL Actually Solves
&lt;/h2&gt;

&lt;p&gt;GraphQL is a query language for APIs, developed to let clients ask for exactly the fields they need in a single request, no more, no less. It replaces the common REST pattern of hitting five endpoints to assemble one screen with a single POST to a &lt;code&gt;/graphql&lt;/code&gt; endpoint carrying a query document.&lt;/p&gt;

&lt;p&gt;The core motivation is over-fetching and under-fetching. A mobile app rendering a product card doesn't need the full product record REST would return, and a dashboard aggregating data from three domains shouldn't need three round trips. GraphQL's schema-first design also gives frontend teams a strongly typed contract they can introspect, generate types from, and build tooling around without waiting on backend changes for every new view.&lt;/p&gt;

&lt;p&gt;Here's a minimal schema and resolver in Node.js using Apollo Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
  type Product {
    id: ID!
    name: String!
    price: Float!
    reviews: [Review!]!
  }

  type Review {
    author: String!
    rating: Int!
  }

  type Query {
    product(id: ID!): Product
  }
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dataSources&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dataSources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dataSources&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dataSources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reviewAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReviewsForProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`GraphQL server ready at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A client can now request just the product name and its reviewers' ratings in one call, and the resolver layer handles fetching from whatever underlying services back each field. That flexibility is the entire value proposition, and it's real: teams building for multiple client platforms — iOS, Android, web — with different data needs per screen benefit from not maintaining parallel REST endpoints for each variant.&lt;/p&gt;

&lt;p&gt;The tradeoffs show up in production, not in the demo. Caching is one of the sharpest: because every GraphQL request goes to the same endpoint with a different query body, the HTTP caching layer that works so well for REST — ETags, &lt;code&gt;Cache-Control&lt;/code&gt;, CDN edge caching by URL — is essentially broken due to the single endpoint architecture. Query complexity is another. A poorly constrained schema lets a client request nested relationships that fan out into dozens of downstream calls, and without depth limiting or cost analysis, a single query can accidentally DoS your own database. Adoption data reflects this maturing understanding: GraphQL adoption sits at roughly 25% among enterprise teams, down from a peak near 40%, concentrated in organizations with complex frontend data requirements across multiple client platforms. That's not decline so much as correction — teams that adopted GraphQL for simple CRUD backends are moving back to REST, while teams with genuinely complex data-fetching needs are staying.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gRPC Actually Solves
&lt;/h2&gt;

&lt;p&gt;gRPC is a remote procedure call framework built by Google on top of HTTP/2 and Protocol Buffers. Instead of a client asking "give me this JSON resource," it calls a method on a service as if it were a local function, and the framework handles serialization, transport, and streaming underneath.&lt;/p&gt;

&lt;p&gt;The design center is service-to-service communication inside a system you control end to end — typically microservices in the same cluster or mesh. Protocol Buffers serialize to a compact binary format instead of text-based JSON, which cuts payload size and parsing overhead substantially. HTTP/2 gives gRPC native support for bidirectional streaming, multiplexed requests over a single connection, and built-in flow control — capabilities REST over HTTP/1.1 never had and that GraphQL, running over HTTP/1.1 POST in most implementations, doesn't get either.&lt;/p&gt;

&lt;p&gt;A basic &lt;code&gt;.proto&lt;/code&gt; definition and Python server implementation illustrate the contract-first approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;InventoryService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;GetProduct&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProductRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProductResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;StreamStockUpdates&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StockRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;StockUpdate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ProductRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ProductResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;StockRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;warehouse_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;StockUpdate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;grpc&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;futures&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;inventory_pb2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;inventory_pb2_grpc&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryServicer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory_pb2_grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InventoryServiceServicer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;GetProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch_product_from_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inventory_pb2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProductResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;StreamStockUpdates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;subscribe_to_warehouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warehouse_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;inventory_pb2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StockUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;inventory_pb2_grpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_InventoryServiceServicer_to_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InventoryServicer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_insecure_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[::]:50051&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_termination&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The performance case for gRPC is well documented and consistent across independent benchmarks. Enterprise deployments show gRPC outperforming REST by 5–10x in throughput for internal microservice-to-microservice interactions, and at the latency level, published comparisons put gRPC's p50 latency at roughly 0.1ms versus REST's 0.3ms, with p99 at 12ms versus 45ms. At companies operating hundreds of internal services, gRPC now handles billions of internal RPCs per day, with 7-10x performance gains over JSON-based REST for serialization-heavy workloads. That's the number that matters for infrastructure teams: at scale, the difference between binary Protobuf and JSON parsing compounds across every hop in a call chain.&lt;/p&gt;

&lt;p&gt;The costs are on the developer-experience and interoperability side. You cannot &lt;code&gt;curl&lt;/code&gt; a gRPC endpoint the way you can a REST or GraphQL one; debugging requires &lt;code&gt;grpcurl&lt;/code&gt; or a generated client, and inspecting traffic in a tool like Wireshark shows binary noise without the corresponding &lt;code&gt;.proto&lt;/code&gt; file. Browser support is also incomplete — native gRPC requires HTTP/2 trailers that browsers don't expose directly, which is why gRPC-Web exists as a translation layer, adding a proxy hop for any browser-facing use case. This is precisely why gRPC rarely appears as a public, partner-facing API: onboarding a third-party developer to a binary RPC protocol with generated stubs is a much higher bar than handing them a REST endpoint and a Postman collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Actual Decision
&lt;/h2&gt;

&lt;p&gt;The honest framing is that GraphQL and gRPC rarely compete for the same slot in an architecture. GraphQL competes with REST at the client-facing edge, where flexible data-fetching for a variety of frontends is the priority. gRPC competes with REST (and with things like Kafka for async cases) in the service mesh, where raw throughput and strict contracts between services you control matter more than developer accessibility.&lt;/p&gt;

&lt;p&gt;The pattern showing up repeatedly in 2026 architecture writeups is a layered one: internal services communicate over gRPC for speed and type safety, and a GraphQL layer sits in front as a client-facing gateway that aggregates those services into flexible queries for web and mobile apps. This is described as the most common current pattern, and it lets each protocol do the job it's actually good at instead of forcing one technology to cover both the internal and external surface.&lt;/p&gt;

&lt;p&gt;A few concrete questions cut through most of the ambiguity. If the consumer is a third-party developer or a partner you don't control, gRPC is the wrong choice regardless of its performance advantages — the integration friction will show up in support tickets, not benchmarks. If the client is a single-purpose mobile app hitting two or three well-known endpoints, plain REST is often sufficient, and GraphQL adds schema and resolver overhead for no real benefit. If you're aggregating data from many internal services into varied frontend views, GraphQL's flexibility earns its complexity. If you're building latency-sensitive internal service-to-service calls — recommendation engines, real-time inventory checks, anything where milliseconds compound across a call chain — gRPC's binary serialization and HTTP/2 streaming are worth the tooling cost.&lt;/p&gt;

&lt;p&gt;Team expertise deserves more weight in this decision than it usually gets. A well-implemented REST API consistently outperforms a poorly implemented GraphQL or gRPC service in real production incidents, because the failure modes of an unfamiliar protocol — unbounded query depth in GraphQL, misconfigured deadlines in gRPC — tend to surface under load, not in code review. Migration cost is also not trivial: teams moving an existing protocol to either alternative should plan for meaningfully more build effort than the greenfield estimates suggest, and an incremental rollout behind an &lt;a href="https://openlibrary.telkomuniversity.ac.id/pustaka/158416/implementasi-dan-analisis-api-gateway-sebagai-middleware-pada-platform-as-a-service-studi-kasus-sistem-layanan-laboratorium-praktikum-.html" rel="noopener noreferrer"&gt;API&lt;/a&gt; gateway is safer than a full cutover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leaves You
&lt;/h2&gt;

&lt;p&gt;Neither technology deprecates REST, and neither is a default choice you reach for because it's the newer name in the room. GraphQL earns its place when the problem is genuinely about flexible, client-driven data shaping across multiple frontends. gRPC earns its place when the problem is internal service throughput, and you control both ends of the wire. If your system needs both — a fast internal mesh and a flexible public-facing surface — running gRPC underneath a GraphQL gateway is a proven pattern, not a compromise.&lt;/p&gt;

&lt;p&gt;Before committing either way, map your actual traffic: who's calling this API, how many different data shapes do they need, and where does latency currently hurt? That answer, not the technology's reputation, should decide your stack.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Building a Modern API: Best Practices for 2026 and Beyond</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sat, 05 Sep 2026 11:30:53 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/building-a-modern-api-best-practices-for-2026-and-beyond-4gfg</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/building-a-modern-api-best-practices-for-2026-and-beyond-4gfg</guid>
      <description>&lt;p&gt;A team shipping a "quick" endpoint today is making a promise they'll have to keep for years. Building a modern &lt;a href="https://bis-sby.telkomuniversity.ac.id/tag/cara-kerja-api/" rel="noopener noreferrer"&gt;API&lt;/a&gt; in 2026 means designing for change from the first commit, not patching predictability after a breaking release that burns your integration partners. This guide walks through the architectural decisions, patterns, and code-level practices that separate APIs teams are still trusted in year five from those that get rewritten in year two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick the Right Protocol for Each Boundary, Not for the Whole System
&lt;/h2&gt;

&lt;p&gt;The old debate — REST versus GraphQL versus gRPC — has mostly resolved itself. The mature answer isn't "which one wins," it's "which protocol fits which boundary in your system." A typical modern stack uses gRPC for service-to-service calls where latency and type safety matter, REST for public-facing partner APIs where broad compatibility wins, and GraphQL as a backend-for-frontend layer when different clients need different slices of the same data.&lt;/p&gt;

&lt;p&gt;REST remains the default for anything a browser or third-party developer calls directly. It's simple, cacheable over standard HTTP, and every developer already knows how to consume it. GraphQL earns its added complexity when a mobile app and a web dashboard need very different fields from the same underlying resources, and making multiple round trips to assemble a screen becomes wasteful. gRPC, built on Protocol Buffers and HTTP/2, delivers markedly lower latency and smaller payloads, which is why it dominates internal microservice communication where both sides of the wire are under your control.&lt;/p&gt;

&lt;p&gt;The practical takeaway: don't force one protocol to solve every problem in your architecture. Match the tool to the constraint at each boundary — internal speed, external reach, or per-client data shaping — rather than picking a single technology as an organizational identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Resources and Endpoints Around Nouns, Not Actions
&lt;/h2&gt;

&lt;p&gt;For REST APIs specifically, resource modeling still trips up more teams than any other decision. Endpoints should represent things (&lt;code&gt;/orders&lt;/code&gt;, &lt;code&gt;/customers/42/invoices&lt;/code&gt;), not verbs (&lt;code&gt;/getOrder&lt;/code&gt;, &lt;code&gt;/createInvoice&lt;/code&gt;). HTTP methods already carry the verb.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FastAPI example: resource-oriented routing
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;total_cents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="n"&gt;orders_db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/orders/{order_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders_db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orders_db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;orders_db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep nesting shallow. &lt;code&gt;/customers/42/invoices/17/line-items&lt;/code&gt; is technically valid but painful to maintain and version. Two levels of nesting is usually the practical ceiling; beyond that, expose a flatter resource with a filter parameter instead, such as &lt;code&gt;/line-items?invoice_id=17&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paginate Before It's a Problem
&lt;/h2&gt;

&lt;p&gt;Returning an entire collection in one response works fine in a demo and falls over in production. Offset-based pagination (&lt;code&gt;?page=3&amp;amp;limit=50&lt;/code&gt;) is easy to implement but degrades as tables grow, since the database still has to scan and discard all the skipped rows. Cursor-based pagination avoids that by using an opaque pointer to the last seen record, which keeps query performance roughly constant regardless of table size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cursor-based pagination with a Postgres-backed API (Node/Express)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM orders WHERE id &amp;gt; $1 ORDER BY id ASC LIMIT $2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM orders ORDER BY id ASC LIMIT $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextCursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;next_cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nextCursor&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a sensible cap on &lt;code&gt;limit&lt;/code&gt; server-side. Letting clients request unbounded page sizes is a common, self-inflicted denial-of-service vector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version for Change, Not Just for Launch
&lt;/h2&gt;

&lt;p&gt;Every API you ship is a promise to the people who integrate with it. The version scheme you choose determines how painful the next breaking change will be — and there will be a next breaking change. Semantic versioning combined with automated breaking-change detection in CI catches accidental contract violations before they reach a partner's production system, rather than after a support ticket arrives.&lt;/p&gt;

&lt;p&gt;URL-based versioning (&lt;code&gt;/v1/orders&lt;/code&gt;, &lt;code&gt;/v2/orders&lt;/code&gt;) is the most common approach because it's visible and simple to route. Header-based versioning is more elegant but harder for developers to debug when something silently changes. Whichever you choose, commit to a deprecation policy in writing: how long old versions stay live, how you notify integrators, and what constitutes a breaking versus non-breaking change. Adding an optional field is non-breaking; renaming or removing one is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Authentication and Authorization as Separate Concerns
&lt;/h2&gt;

&lt;p&gt;Authentication answers "who is this"; authorization answers "what can they do." Conflating the two is a common source of security bugs. OAuth 2.0 with short-lived JWTs remains the standard for user-facing APIs, while service-to-service calls typically rely on mutual TLS or signed service tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FastAPI dependency separating auth from authz
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jose&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JWTError&lt;/span&gt;

&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loaded-from-environment-not-hardcoded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_current_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauth2_scheme&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sub&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;JWTError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid or expired token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;require_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_current_user&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Insufficient permissions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;checker&lt;/span&gt;

&lt;span class="nd"&gt;@app.delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/orders/{order_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;
    &lt;span class="n"&gt;orders_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deleted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation also makes audit logging cleaner: you can log every authorization decision independently of how the caller was authenticated, which matters when a compliance review asks who had access to what and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate Limit and Cache With Intention
&lt;/h2&gt;

&lt;p&gt;Rate limiting protects your infrastructure from both abuse and honest mistakes, like a client stuck in a retry loop. Token bucket algorithms are the common choice because they allow short bursts while enforcing a steady average rate. Return rate-limit headers (&lt;code&gt;X-RateLimit-Remaining&lt;/code&gt;, &lt;code&gt;Retry-After&lt;/code&gt;) so well-behaved clients can back off gracefully instead of hammering a &lt;code&gt;429&lt;/code&gt; response.&lt;/p&gt;

&lt;p&gt;Caching deserves equal attention. REST's stateless nature makes HTTP caching (&lt;code&gt;ETag&lt;/code&gt;, &lt;code&gt;Cache-Control&lt;/code&gt;) nearly free to implement and dramatically reduces load for read-heavy endpoints. For GraphQL, where a single endpoint serves many different queries, caching is harder and usually requires persisted queries or a dedicated caching layer like a CDN-aware GraphQL gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for Machine Consumption, Not Just Human Developers
&lt;/h2&gt;

&lt;p&gt;A growing share of API traffic now comes from AI agents rather than humans reading documentation in a browser. Serving a machine-readable OpenAPI specification at a predictable path like &lt;code&gt;/openapi.json&lt;/code&gt;, and keeping it in sync with the actual implementation through contract-driven codegen, lets both human developers and AI tooling integrate without guessing at behavior. Generating a plain-text summary file for agent consumption is also gaining traction, since it reduces the token overhead of parsing full HTML documentation pages.&lt;/p&gt;

&lt;p&gt;The deeper principle here isn't new: documentation that drifts from the real API is worse than no documentation, because it actively misleads. Generating docs and client SDKs from the same source of truth as your route definitions is the only approach that scales past a handful of endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle Errors Like a First-Class Feature
&lt;/h2&gt;

&lt;p&gt;A good error response tells the caller exactly what went wrong and what to do next. A vague &lt;code&gt;500 Internal Server Error&lt;/code&gt; forces the integrating developer to open a support ticket; a structured error body lets them fix it themselves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Consistent error shape across an Express API&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INTERNAL_ERROR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something went wrong&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Include a request ID in every error response and log it server-side. When a partner reports an issue, that ID turns a vague "it didn't work yesterday around 3 pm" into a five-second lookup in your logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Contract, Not Just the Code
&lt;/h2&gt;

&lt;p&gt;Unit tests verify your logic works. Contract tests verify your API still honors the promise made to consumers. Tools that validate requests and responses against your OpenAPI schema in CI catch a whole category of bugs — an accidentally renamed field, a type that quietly changed from string to integer — before they reach anyone outside your team.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simple schema validation test using pytest
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jsonschema&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_order_response_matches_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_schema&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/orders/1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="n"&gt;jsonschema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;order_schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a cheap habit that pays for itself the first time it stops a breaking change from shipping on a Friday afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing It Together
&lt;/h2&gt;

&lt;p&gt;None of these practices are exotic. Resource-oriented design, cursor pagination, clean separation of authentication and authorization, contract testing, and machine-readable documentation are all well-understood techniques. What separates APIs that age well from the ones teams end up rewriting is consistency: applying these practices from the first endpoint rather than retrofitting them after the first outage or the first partner integration that broke silently.&lt;/p&gt;

&lt;p&gt;If you're starting a new API today, resist the urge to optimize for the architecture you might need at scale. Start with REST and OpenAPI for anything public-facing, introduce gRPC only where you control both sides of a genuinely latency-sensitive call, and reach for GraphQL only when multiple clients demonstrably need different data shapes from the same resources. Build in versioning, rate limiting, and structured errors from day one — they're far cheaper to add now than to bolt on after your first integration partner depends on the old behavior.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Securing the Blockchain: A Deep Dive into Modern Encryption Protocols</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Wed, 02 Sep 2026 05:55:12 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/securing-the-blockchain-a-deep-dive-into-modern-encryption-protocols-3h8g</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/securing-the-blockchain-a-deep-dive-into-modern-encryption-protocols-3h8g</guid>
      <description>&lt;p&gt;&lt;a href="https://dte.telkomuniversity.ac.id/blockchain-revolusi-kepercayaan-di-era-digital/" rel="noopener noreferrer"&gt;Blockchain&lt;/a&gt; encryption protocols are facing their first real stress test since Bitcoin's launch in 2009. For over a decade, the cryptographic assumptions underpinning nearly every major chain—elliptic curve signatures, SHA-256 hashing, RSA key exchange—held steady because no adversary had the computing power to break them. That assumption is now expiring. Researchers estimate that a sufficiently powerful quantum computer could compromise Bitcoin's signature scheme with far fewer qubits than previously thought, and regulators in the US and EU are already requiring critical infrastructure to migrate to post-quantum algorithms by 2030.&lt;/p&gt;

&lt;p&gt;This article walks through how blockchain encryption actually works today, why it's vulnerable, and what protocols are replacing it. Along the way, we'll look at working code so the concepts aren't abstract.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Blockchain Encryption Works Right Now
&lt;/h2&gt;

&lt;p&gt;Every blockchain transaction depends on three cryptographic building blocks: hashing, asymmetric key pairs, and digital signatures. Hashing (usually SHA-256 or SHA-3) turns transaction data into a fixed-length fingerprint that changes completely if even one bit of input changes. Asymmetric cryptography gives each wallet a public key anyone can see and a private key only the owner holds. Digital signatures let a wallet prove it authorized a transaction without revealing the private key itself.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of how a transaction gets signed, using Python's &lt;code&gt;cryptography&lt;/code&gt; library with ECDSA, the elliptic curve scheme Bitcoin and Ethereum both rely on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.asymmetric&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ec&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashes&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.asymmetric.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;decode_dss_signature&lt;/span&gt;

&lt;span class="c1"&gt;# Generate a key pair using the secp256k1 curve (same curve as Bitcoin)
&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_private_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SECP256K1&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;public_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Sign a transaction payload
&lt;/span&gt;&lt;span class="n"&gt;transaction_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send 0.5 BTC to address_xyz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ECDSA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;

&lt;span class="c1"&gt;# Verify the signature using the public key
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ECDSA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Signature valid — transaction authorized&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Signature invalid — reject transaction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because factoring the elliptic curve discrete logarithm problem is computationally infeasible for classical computers. A private key derived from a 256-bit curve would take longer than the age of the universe to brute-force with current hardware. That's the whole security model: not unbreakable, just slow enough to break that nobody bothers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Quantum Computing Changes the Math
&lt;/h2&gt;

&lt;p&gt;Shor's algorithm, first published in 1997, gives a quantum computer a shortcut through exactly the kind of math ECDSA depends on. A classical computer needs exponential time to solve the elliptic curve discrete logarithm problem; a large enough quantum computer needs polynomial time. Any cryptographic protocol that relies on elliptic curves or RSA is vulnerable to Shor's algorithm, while hash functions like SHA-256 and SHA-3, along with symmetric encryption like AES, are expected to remain secure.&lt;/p&gt;

&lt;p&gt;That distinction matters for prioritizing what to fix. Signature schemes and key exchange are exposed; hashing and symmetric encryption mostly are not, at least not to Shor's algorithm specifically. Grover's algorithm does give quantum computers a quadratic speedup against hash-based mining and brute-force search, but doubling the key or hash length restores most of the lost margin.&lt;/p&gt;

&lt;p&gt;The more urgent risk isn't a quantum computer breaking Bitcoin tomorrow. It's what security researchers call "store now, decrypt later." Digital signatures typically used in blockchains are based on primitives vulnerable to quantum attacks—Bitcoin's elliptic curve scheme, for instance, could, by some optimistic estimates, be broken by a quantum computer as early as 2027. An adversary can harvest encrypted blockchain data and signed transactions now, then decrypt them once quantum hardware catches up. For any asset or credential meant to stay confidential for years, that clock is already running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Post-Quantum Cryptography: The Leading Candidates
&lt;/h2&gt;

&lt;p&gt;The National Institute of Standards and Technology has spent years running a public competition to standardize post-quantum cryptographic (PQC) algorithms, and a handful have emerged as the practical front-runners for blockchain use.&lt;/p&gt;

&lt;p&gt;Lattice-based schemes, particularly CRYSTALS-Kyber for key encapsulation and CRYSTALS-Dilithium for signatures, currently offer the best balance of security and performance. Lattice-based schemes such as Kyber and NTRU provide high resistance at practical key sizes, though they can be slower to verify transactions and have lower throughput than classical schemes. Hash-based signature schemes like SPHINCS+ trade some of that performance for stronger, more conservative security guarantees, since their safety rests entirely on well-understood hash function properties rather than newer lattice assumptions.&lt;/p&gt;

&lt;p&gt;Here's what a Kyber-style key encapsulation exchange looks like conceptually, using the &lt;code&gt;pqcrypto&lt;/code&gt; Python bindings as an example of the API shape (actual production use requires vetted, audited libraries):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pqcrypto.kem.kyber768&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;generate_keypair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;

&lt;span class="c1"&gt;# Node A generates a post-quantum key pair
&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_keypair&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Node B uses the public key to create a shared secret and ciphertext
&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared_secret_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Node A decrypts the ciphertext to recover the same shared secret
&lt;/span&gt;&lt;span class="n"&gt;shared_secret_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;shared_secret_a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;shared_secret_b&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Shared secret established without exposing the private key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mechanics differ from ECDH under the hood, but the goal is identical: two parties agree on a shared secret over an insecure channel without a quantum-capable eavesdropper being able to reconstruct it from the exchange.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration Strategies Chains Are Actually Using
&lt;/h2&gt;

&lt;p&gt;No major chain can flip a switch and swap its signature scheme overnight without breaking every wallet and smart contract built on top of it. Three migration patterns have emerged in practice.&lt;/p&gt;

&lt;p&gt;Hybrid signing runs classical and post-quantum signatures side by side during a transition window, so a transaction is only valid if both signatures check out. This buys time without abandoning battle-tested classical cryptography before PQC schemes have equivalent real-world track records. Some networks may employ hard forks to introduce PQC-based transaction formats for all future transactions, while others adopt hybrid models supporting both classical and PQC signatures during the migration period, reducing disruption to existing users.&lt;/p&gt;

&lt;p&gt;Commit-delay-reveal protocols address the specific problem of migrating already-exposed public keys. The protocol operates in three phases: the user commits a hash linking the existing public key with a quantum-resistant public key without revealing either; then funds remain locked for a security period to prevent quantum attackers from exploiting exposed keys, before the new key is finally revealed. This closes the gap between "vulnerable key visible on-chain" and "safe key active," which matters because public keys used in earlier transactions are often exposed on-chain and thus permanently harvestable.&lt;/p&gt;

&lt;p&gt;New consensus-layer research is also underway to move validator selection and threshold signing itself onto quantum-resistant foundations. Proposed solutions include threshold signatures combined with post-quantum cryptography, lattice-based verifiable random functions for validator selection, and hybrid consensus protocols that combine quantum-resilient primitives, though trade-offs in throughput and decentralization at scale are still being quantified.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero-Knowledge Proofs Need Their Own Upgrade Path
&lt;/h2&gt;

&lt;p&gt;Zero-knowledge rollups and privacy chains rely on cryptographic proof systems that have their own quantum exposure, separate from wallet signatures. Blockchains will need to use newer STARK and SNARG zero-knowledge systems that are quantum-resistant, at the cost of larger proofs and longer verification times, and networks like Starknet are already transitioning to the FRI protocol to get there. This is a useful reminder that "quantum-resistant blockchain" isn't a single upgrade — it touches signatures, key exchange, hashing assumptions, and proof systems separately, each on its own migration timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Developers Can Do Today
&lt;/h2&gt;

&lt;p&gt;Waiting for a chain-wide hard fork isn't the only lever available to teams building on blockchain infrastructure right now. A few practical steps reduce exposure well before any mandatory migration deadline.&lt;/p&gt;

&lt;p&gt;Auditing which cryptographic primitives a given application actually depends on is the starting point — most teams have never mapped which of their signing, hashing, and key-exchange calls touch vulnerable elliptic curve or RSA operations versus quantum-safe hash functions. Layering hybrid encryption into any new infrastructure, even before a chain formally requires it, limits future rework. TLS 1.3 already ships production-ready post-quantum key exchange support, and major providers like Google and AWS are quietly migrating their own services to it, which is a reasonable signal that the tooling has matured past the experimental stage.&lt;/p&gt;

&lt;p&gt;For anything storing long-lived sensitive data on-chain or in transit, treating "store now, decrypt later" as an active threat today — not a 2030 problem — is the more conservative and arguably correct posture, given how far encrypted data can be harvested and how permanently it sits exposed once captured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leaves the Industry
&lt;/h2&gt;

&lt;p&gt;The transition to post-quantum blockchain security won't happen through a single dramatic event. It's already underway in fragments: hybrid signature schemes in production, NIST-approved algorithms shipping in mainnets, TLS infrastructure quietly upgrading in the background, and academic research narrowing the remaining trade-offs between security, throughput, and decentralization. The chains and applications that treat this as a multi-year engineering migration — auditing dependencies, adopting hybrid models early, and tracking NIST standardization — will be in a materially better position than those waiting for a forcing event that, by definition, arrives without warning.&lt;/p&gt;

&lt;p&gt;The underlying lesson extends past cryptocurrency. Any system relying on RSA or elliptic curve cryptography for long-term security, not just blockchains, faces the same migration pressure on a similar timeline.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>encryption</category>
    </item>
    <item>
      <title>How End-to-End Encryption Protects Private Crypto Keys</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Wed, 02 Sep 2026 05:52:10 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/how-end-to-end-encryption-protects-private-crypto-keys-1ioe</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/how-end-to-end-encryption-protects-private-crypto-keys-1ioe</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plaintext key  →  AES-256-GCM encryption  →  Ciphertext stored/transmitted
     ↑                                              ↓
 Only exists in                              Unreadable without
 memory on device                            the decryption key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire point of end-to-end encryption applied to private keys: the raw key material never travels in a form anyone else can read, not even the service relaying it. For anyone managing cryptocurrency wallets, understanding how end-to-end encryption protects private keys is the difference between owning your assets and trusting someone else's server not to get breached.&lt;/p&gt;

&lt;p&gt;Private keys are the single point of failure in crypto custody. Whoever holds the key controls the funds, full stop. End-to-end encryption (E2EE) doesn't eliminate that risk, but it narrows the attack surface dramatically by making sure the key is encrypted before it leaves the device that generated it, and stays encrypted until it's decrypted on another device the user controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  What End-to-End Encryption Actually Means for Keys
&lt;/h2&gt;

&lt;p&gt;End-to-end encryption is often used loosely, so it's worth being precise. In a properly implemented E2EE system, encryption and decryption happen only at the endpoints. Any server, relay, or cloud backup sitting in between only ever sees ciphertext.&lt;/p&gt;

&lt;p&gt;Applied to a private key, this means the key is encrypted locally, typically with a symmetric cipher like AES-256-GCM, using a key derived from something the user controls, such as a password or biometric-unlocked secure enclave. The encrypted blob can then be backed up to a cloud service, synced across devices, or sent through a wallet provider's infrastructure without exposing the underlying key.&lt;/p&gt;

&lt;p&gt;Here's a simplified example of how a private key gets encrypted before storage, using Python's &lt;code&gt;cryptography&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.ciphersaead&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AESGCM&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives.kdf.pbkdf2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PBKDF2HMAC&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashes&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PBKDF2HMAC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;derive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encrypt_private_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;encryption_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;aesgcm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AESGCM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryption_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aesgcm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ciphertext&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nonce&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The password never leaves the device either. It's run through a key derivation function (PBKDF2 here, though Argon2 is increasingly preferred for its memory-hardness) to produce the actual encryption key. This means even if the ciphertext, salt, and nonce are all intercepted, an attacker still has to brute-force the password to recover the private key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters More for Crypto Than Other Data
&lt;/h2&gt;

&lt;p&gt;Most data breaches are recoverable. A leaked password can be reset. A stolen credit card can be canceled. A leaked private key cannot be revoked once funds have moved. There's no customer support line for a &lt;a href="https://bse.telkomuniversity.ac.id/pentingnya-skill-rekayasa-perangkat-lunak-dalam-pengembangan-blockchain-crypto/" rel="noopener noreferrer"&gt;blockchain&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That asymmetry is why wallet providers and custody platforms treat key encryption differently from ordinary application security. A non-custodial wallet's entire value proposition rests on the claim that the provider itself cannot access user funds, which only holds if the private key is encrypted before it ever touches the provider's servers.&lt;/p&gt;

&lt;p&gt;This is also why the industry has moved toward multi-party computation (MPC) as a complement to, and in some cases a replacement for, single-key encryption. Instead of encrypting one complete private key, MPC splits key material into multiple shares held by different parties, none of which ever reconstructs the full key during signing. Recent wallet security comparisons note that &lt;cite&gt;multi-party computation divides a private key into multiple encrypted parts stored separately, removing the need for a single recovery phrase and reducing hacking risk. E2EE and MPC solve overlapping but distinct problems: E2EE protects a key in transit and at rest, while MPC avoids ever having a single, complete key to protect in the first place.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Encryption Alone Falls Short
&lt;/h2&gt;

&lt;p&gt;End-to-end encryption protects data in transit and in storage, but it says nothing about what happens on the endpoint itself. If a device is compromised by malware, or if the user is tricked into approving a malicious transaction, E2EE offers no protection because the attacker is operating at the point where the key is legitimately decrypted for use.&lt;/p&gt;

&lt;p&gt;This is a real and current gap. Wikipedia's overview of the technology notes that even in a correctly implemented E2EE system, &lt;cite&gt;data may be held unencrypted on the user's own device or accessed through their own app if their credentials are compromised. For crypto wallets specifically, that translates into phishing attacks that trick users into signing malicious transactions, clipboard-hijacking malware that swaps a copied wallet address for an attacker's address, and fake wallet apps that request seed phrase input directly.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Hardware wallets exist largely to close this endpoint gap. By keeping the private key inside a dedicated secure element chip that never exposes raw key material to the connected computer or phone, they add a hardware boundary on top of software encryption. Industry guides describe how leading devices rely on &lt;cite&gt;a Secure Element chip with Common Criteria EAL6+ certification that encrypts all data stored on the chip, which is a meaningfully higher bar than software-only encryption running on a general-purpose operating system.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption in Transit vs. Encryption at Rest
&lt;/h2&gt;

&lt;p&gt;It's worth separating two things that often get bundled under the "E2EE" label: protecting a key while it moves between devices, and protecting a key while it sits in storage.&lt;/p&gt;

&lt;p&gt;In transit, the concern is a man-in-the-middle attack intercepting a key as it syncs between a phone and a desktop wallet, or as it's transmitted during wallet recovery. TLS handles the transport layer, but a properly E2EE system doesn't rely on transport security alone. It encrypts the key payload itself, so that even a compromised or malicious relay server can't read it.&lt;/p&gt;

&lt;p&gt;At rest, the concern is a breached database or a stolen device. A wallet provider's servers getting hacked should be a non-event for user funds if every stored key blob is ciphertext derived from a user-held secret. This is the guarantee non-custodial and self-custody products are built around: &lt;cite&gt;in non-custodial wallets, you control your private keys directly, and the provider's infrastructure never holds anything usable on its own.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Here's a minimal illustration of verifying that a stored key blob is genuinely unreadable without the user's password, using authenticated decryption to detect tampering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt_private_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;encryption_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encrypted_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;aesgcm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AESGCM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryption_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;aesgcm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;encrypted_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nonce&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;encrypted_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ciphertext&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# AEAD authentication failure: wrong password or tampered ciphertext
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Decryption failed — key may be corrupted or password incorrect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The use of an AEAD (authenticated encryption with associated data) cipher like AES-GCM matters here specifically because it detects tampering. If an attacker modifies even a single byte of the ciphertext, decryption fails loudly rather than silently returning corrupted key material.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cold Storage as the Practical Endpoint of This Model
&lt;/h2&gt;

&lt;p&gt;Cold wallets take the E2EE principle to its logical extreme by removing network connectivity from the equation entirely. A cold wallet &lt;cite&gt;keeps private keys completely offline, isolated from internet connectivity and potential cyber threats, which means there's no transit leg to encrypt in the first place because the key never leaves an air-gapped device.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;This is why serious long-term holdings tend to migrate toward hardware and cold storage rather than relying on software encryption alone. Encryption protects data that has to move or be stored somewhere accessible; air-gapping avoids the need for that movement altogether. The two approaches aren't competing so much as addressing different parts of the same threat model, and most security-conscious setups combine both: an encrypted software wallet for everyday transactions, and cold storage for the bulk of long-term holdings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Offs Nobody Advertises
&lt;/h2&gt;

&lt;p&gt;Strong encryption comes with a real cost: if the user loses the password or key derivation secret, the encrypted key is unrecoverable. There's no backdoor, because a backdoor would defeat the entire purpose. This is precisely why seed phrases exist as a separate recovery mechanism, and why losing both a password and a seed phrase means permanent loss of funds.&lt;/p&gt;

&lt;p&gt;There's also a policy dimension worth naming honestly. End-to-end encryption in consumer products has become genuinely contested outside of crypto specifically. In one prominent case, &lt;cite&gt;Meta ended support for end-to-end encryption on Messenger in May 2026, justified as a measure to mitigate fraudulent activity and facilitate detection of harmful content, a move that child protection organizations supported while privacy advocates argued it compromises user security. Crypto wallets sit further from that particular debate since there's no messaging content to moderate, but the underlying tension between strong encryption and third-party oversight isn't unique to messaging apps, and it's reasonable to expect similar pressure on custody platforms as regulatory scrutiny of crypto increases.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Choosing a Wallet
&lt;/h2&gt;

&lt;p&gt;Not every product marketed as "encrypted" implements E2EE correctly. The meaningful question to ask any wallet provider is not whether they encrypt data, but whether they can decrypt user funds themselves. If the answer is yes, under any circumstance, including a subpoena or a rogue employee, then encryption is happening somewhere other than the endpoint, and the E2EE label is being used loosely.&lt;/p&gt;

&lt;p&gt;Infrastructure providers building wallet tooling now describe this explicitly as a design requirement rather than a feature. Wallet infrastructure platforms increasingly advertise &lt;cite&gt;end-to-end private key generation, encryption, and access control within secure enclaves, built to be fully non-custodial so the provider itself has no access to user assets. That's the standard worth holding any wallet to, whether it's a consumer app or backend infrastructure a business is integrating.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;For anyone managing meaningful crypto holdings, the practical takeaway is straightforward. Use a wallet where key encryption happens on-device, verify the provider genuinely cannot decrypt your keys, treat hardware wallets as the default for anything beyond spending money, and never let a password or seed phrase exist in a place an attacker could realistically reach. Encryption is only as strong as the weakest point where a key briefly exists in plaintext, and that point should always be a device only you control.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>crypto</category>
      <category>encryption</category>
    </item>
    <item>
      <title>How Quantum Computing Threats Are Reshaping Cryptocurrency Security</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Wed, 02 Sep 2026 05:48:55 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/how-quantum-computing-threats-are-reshaping-cryptocurrency-security-592l</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/how-quantum-computing-threats-are-reshaping-cryptocurrency-security-592l</guid>
      <description>&lt;p&gt;Roughly 5.4 million bitcoin, worth hundreds of billions of dollars, sits in wallets whose public keys have already been exposed on-chain. Quantum computing is the reason that number matters. Once a sufficiently powerful quantum computer exists, exposed public keys stop being harmless strings of data and become the starting point for stealing funds outright, which is why cryptocurrency security is now being redesigned years before that computer is built.&lt;/p&gt;

&lt;p&gt;The threat isn't hypothetical hand-waving about far-off science fiction. NIST, IBM, Google, and PsiQuantum have each published timelines that converge on the 2030-to-2035 window for cryptographically relevant quantum computers. Blockchain protocols built on elliptic curve cryptography have to migrate before that window closes, not after, because the migration itself takes years and the assets at risk can't simply be recalled once the threat materializes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Elliptic Curve Cryptography Breaks Under Quantum Attack
&lt;/h2&gt;

&lt;p&gt;Bitcoin, Ethereum, and most major cryptocurrencies rely on the Elliptic Curve Digital Signature Algorithm (ECDSA) to prove ownership of funds. The security of ECDSA rests on the elliptic curve discrete logarithm problem: given a public key, it's computationally infeasible for classical computers to derive the corresponding private key. That infeasibility is the entire basis of the trust model.&lt;/p&gt;

&lt;p&gt;Shor's algorithm changes the math. Running on a fault-tolerant quantum computer, it solves the discrete logarithm problem in polynomial time instead of the exponential time classical computers require. A 2025 analysis from Google Quantum AI researcher Craig Gidney estimated that factoring a 2048-bit RSA key would require under a million noisy qubits, a dramatic reduction from earlier estimates that assumed tens of millions. Applied to elliptic curve keys, similar resource reductions mean the timeline for a practical break keeps compressing rather than expanding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified illustration of what ECDSA relies on:
# given P = k * G (public key = private key * generator point),
# recovering k classically is intractable.
# Shor's algorithm solves this class of problem efficiently on
# a fault-tolerant quantum computer, which is why exposed public
# keys — not just private keys — become the attack surface.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_public_key_exposed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;has_been_spent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Returns True if the public key for this address is already
    visible on-chain and therefore quantum-attackable once a
    cryptographically relevant quantum computer exists.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;always_exposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P2PK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P2TR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# public key visible by design
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;address_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;always_exposed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="c1"&gt;# P2PKH, P2WPKH, P2WSH hide the key behind a hash until spent
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;has_been_spent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why the risk isn't evenly distributed across a blockchain. Coins sitting in never-spent hashed addresses, like standard P2PKH or SegWit outputs, keep their public key hidden until the moment they're spent. Coins in P2PK addresses or reused P2PKH addresses have already broadcast their public key, which means they're exposed today and simply waiting for the hardware to catch up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scale of Bitcoin's Exposure Is Larger Than Most Holders Realize
&lt;/h2&gt;

&lt;p&gt;Multiple independent chain analyses have tried to quantify exactly how much Bitcoin sits in this exposed category, and the estimates have grown as measurement techniques improved. Deloitte's earlier scans put the figure at roughly 25% of circulating supply. More recent 2026 measurements from Glassnode found 6.04 million BTC, about 30.2% of issued supply and worth roughly $469 billion, with exposed public keys on-chain.&lt;/p&gt;

&lt;p&gt;That figure splits into meaningfully different risk categories. Around 2.3 million BTC, roughly 12% of supply, is dormant across every address type, including Satoshi-era coins whose owners can never move them to safety even with warning. Another 3.7 million BTC, about 19% of supply, is exposed but still spendable, meaning owners can sweep those funds into quantum-resistant outputs if they act before a quantum computer arrives. The remaining 65 to 70% of supply sits in fresh, never-reused hashed addresses, where the public key is only briefly revealed at the moment of spending.&lt;/p&gt;

&lt;p&gt;Ethereum's exposure looks structurally different. Because Ethereum was designed around persistent, reused addresses rather than one-time hashed outputs, a much larger share of its supply has already broadcast its public keys as a normal consequence of everyday use. That design choice, which made Ethereum more usable for smart contracts, also makes the network's quantum migration path more urgent and more complicated than Bitcoin's.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Harvest Now, Decrypt Later" Problem Compounds the Risk
&lt;/h2&gt;

&lt;p&gt;Even setting aside live quantum attacks on a currently exposed key, cryptocurrency networks face a subtler threat: adversaries can record and store today's exposed public keys and transaction data now, with the explicit plan of decrypting them once quantum hardware matures. Security researchers call this Harvest Now, Decrypt Later, or HNDL, and it applies to blockchains just as it applies to encrypted government communications and corporate data.&lt;/p&gt;

&lt;p&gt;For a bank record or a diplomatic cable, HNDL means confidentiality fails years later. For a cryptocurrency wallet, it's more direct: an adversary who has already harvested a public key doesn't need to break anything new when the quantum computer arrives. They just need to run the attack and move the funds before the legitimate owner does. This is one reason security researchers argue the migration clock started the moment Shor's algorithm was proven, not the moment a quantum computer capable of running it gets built.&lt;/p&gt;

&lt;p&gt;There's also a narrower, more time-sensitive exposure window that applies to every Bitcoin transaction, regardless of address type. When a transaction is broadcast, its public key becomes visible in the mempool before the transaction is confirmed on-chain. Confirmation currently takes around ten minutes. A sufficiently fast quantum attacker could theoretically intercept that window, derive the private key, and submit a competing transaction with a higher fee, a scenario sometimes called a transaction hijack or race attack. This is a distinct risk from address-level exposure because it threatens every future transaction, not just historically reused ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Post-Quantum Standards Currently Stand
&lt;/h2&gt;

&lt;p&gt;NIST finalized its first three post-quantum cryptography standards in August 2024: ML-KEM (formerly CRYSTALS-Kyber) for key encapsulation, ML-DSA (formerly CRYSTALS-Dilithium) for digital signatures, and SLH-DSA (formerly SPHINCS+) as a hash-based signature backup. A fourth algorithm, HQC, was added in 2025 to diversify the mathematical assumptions the standards rely on, reducing the risk that a single cryptanalytic breakthrough compromises everything at once.&lt;/p&gt;

&lt;p&gt;That diversification turned out to matter quickly. In July 2026, Anthropic disclosed that an AI model it developed had discovered a vulnerability in HAWK, a lattice-based signature algorithm that was under consideration for standardization. The HAWK team withdrew the algorithm, and NIST confirmed the finding doesn't affect the already-finalized ML-KEM or ML-DSA standards, which rest on different mathematical foundations. The episode is a useful reminder that post-quantum cryptography is still an active research field, not a solved problem with a single fixed answer, and that crypto-agility, meaning the ability to swap algorithms without rebuilding a system from scratch, is as important as picking the right algorithm today.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conceptual sketch of a hybrid signature scheme, combining a
# classical and post-quantum algorithm so that breaking either
# one alone is insufficient to forge a valid signature.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hybrid_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ecdsa_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dilithium_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;classical_sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ecdsa_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ecdsa_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pq_sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dilithium_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dilithium_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ecdsa_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;classical_sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ml_dsa_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pq_sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hybrid_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ecdsa_pub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dilithium_pub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;ecdsa_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ecdsa_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ecdsa_pub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;ml_dsa_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ml_dsa_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dilithium_pub&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NIST's IR 8547 sets a broader migration timeline: quantum-vulnerable algorithms should be deprecated by 2030 and removed from standards entirely by 2035, with high-risk systems expected to transition earlier. That timeline was written with government and enterprise systems in mind, but it's become a reference point for &lt;a href="https://bif-sby.telkomuniversity.ac.id/blockchain-di-luar-dunia-kripto-potensi-dan-implementasi-nyata/" rel="noopener noreferrer"&gt;blockchain&lt;/a&gt; governance discussions as well, since cryptocurrency networks face the same underlying hardware timeline without the benefit of centralized rollout authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Blockchain Networks Are Actually Responding
&lt;/h2&gt;

&lt;p&gt;Bitcoin and Ethereum face the migration problem differently because their governance models differ. Bitcoin's protocol changes require broad consensus among node operators, miners, and businesses, which makes any hard fork slow by design. BIP-360 is the primary proposal addressing quantum resistance, aiming to introduce a new address format that supports post-quantum signature schemes such as hash-based signatures, without requiring every wallet to migrate simultaneously.&lt;/p&gt;

&lt;p&gt;Ethereum's public roadmap treats quantum resistance as a defined workstream rather than a distant contingency. The Ethereum Foundation's post-quantum team has been developing proposals, including EIP-8141, drafted in January 2026, which explores account abstraction mechanisms that could allow wallets to adopt quantum-resistant signature schemes without requiring every user to generate an entirely new address from scratch. Because Ethereum already relies heavily on account abstraction infrastructure from EIP-7702, that flexibility gives it a somewhat smoother migration path than Bitcoin's UTXO model, even though its baseline exposure is higher.&lt;/p&gt;

&lt;p&gt;Smaller ecosystems are moving faster precisely because they carry less legacy weight. Postquant Labs launched Quip Network in April 2026, a Layer 2 Bitcoin wallet built on WOTS+ (Winternitz One-Time Signature) cryptography, running through the Arch Network smart contract layer. It's a narrower, opt-in solution rather than a base-layer protocol change, but it illustrates the kind of incremental migration path that doesn't require waiting on Bitcoin Core consensus.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example: checking whether a Bitcoin UTXO is in the
# "migratable but currently exposed" risk tier, combining
# address type and spend history the way chain analyses do.
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify_exposure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spend_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_dormant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_dormant&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;address_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P2PK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;irreducible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;          &lt;span class="c1"&gt;# owner can no longer act
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;address_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P2PK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;P2TR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;spend_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;migratable_exposed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# owner can still sweep funds
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;protected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;                &lt;span class="c1"&gt;# key hidden until first spend
&lt;/span&gt;
&lt;span class="c1"&gt;# Wallet software increasingly flags "migratable_exposed" UTXOs
# so holders can proactively move funds to fresh addresses ahead
# of any quantum-capable adversary.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What This Means for Developers and Holders Right Now
&lt;/h2&gt;

&lt;p&gt;For developers building on top of these chains, the practical starting point is crypto-agility: designing signature verification and key management so that a new algorithm can be added without a full rewrite. Hard-coding ECDSA assumptions throughout a codebase creates exactly the kind of migration debt that will be expensive to unwind later. Libraries like OpenSSL, BoringSSL, and Bouncy Castle have already begun adding support for ML-KEM and ML-DSA, giving teams a path to start experimenting with hybrid classical-plus-post-quantum schemes now, well before any hard deadline forces the issue.&lt;/p&gt;

&lt;p&gt;For individual holders, the practical guidance follows directly from the exposure tiers Deloitte, Glassnode, and other chain analyses have mapped out. Funds sitting in a never-reused address carry structurally lower risk than funds in a reused P2PKH address or an old P2PK output, and wallet software is beginning to surface that distinction directly rather than leaving users to interpret raw address formats themselves. Avoiding address reuse, a piece of advice that predates the quantum conversation entirely, turns out to double as quantum hygiene.&lt;/p&gt;

&lt;p&gt;None of this means a quantum attack is imminent. Every credible timeline still places a cryptographically relevant quantum computer somewhere in the 2030 to 2035 range, and the HAWK withdrawal is a reminder that even the replacement algorithms are still being stress-tested. But the migration work, in protocol design, in wallet software, and in developer tooling, takes years to roll out safely across a decentralized network with no central authority to force an upgrade. The organizations and protocols treating this as a 2026 problem rather than a 2032 problem are the ones setting the standard the rest of the ecosystem will eventually have to follow.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Keeping Your Smart Devices Safe from Hackers</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sun, 23 Aug 2026 05:50:36 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/keeping-your-smart-devices-safe-from-hackers-3ech</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/keeping-your-smart-devices-safe-from-hackers-3ech</guid>
      <description>&lt;p&gt;Smart devices are no longer a novelty confined to tech enthusiasts' living rooms. From thermostats and doorbell cameras to voice assistants and connected light bulbs, the average household now runs a small network of internet-connected gadgets around the clock. That convenience comes with a cost: connected households face nearly 30 IoT attack attempts every single day, according to a 2025 report from Netgear and Bitdefender that analyzed data from 6.1 million households. Keeping smart devices safe from hackers is no longer optional homework for IT professionals — it's a basic requirement for anyone who owns a smart speaker, camera, or lock.&lt;/p&gt;

&lt;p&gt;The good news is that most successful attacks on smart homes don't involve sophisticated exploits or nation-state hackers. They rely on the same handful of weaknesses: default passwords, outdated firmware, and networks with no internal boundaries. Understanding those weaknesses, and closing them, is well within reach for a non-technical homeowner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Smart Devices Are Such an Easy Target
&lt;/h2&gt;

&lt;p&gt;Manufacturers competing on price and speed to market often treat security as an afterthought. Many devices still ship with generic default credentials such as "admin" or "1234," and a large share of buyers never bother to change them. Attackers know this, and they maintain databases of default logins for thousands of device models, which lets them scan the internet and break into unsecured devices within seconds.&lt;/p&gt;

&lt;p&gt;The scale of the problem is easy to underestimate until you see it measured. A Which? investigation, run in partnership with the NCC Group and the Global Cyber Alliance, filled a test home with ordinary smart devices — TVs, thermostats, and security systems — and logged the incoming traffic. In a single week, the home was hit with over 12,800 unique scans and hack attempts, including more than 2,400 login attempts using weak default usernames and passwords. That's roughly 14 credential-stuffing attempts every hour, aimed at a house with nothing more exotic than a smart TV and a few sensors.&lt;/p&gt;

&lt;p&gt;Streaming devices and smart TVs are consistently among the most exposed categories, together accounting for close to half of all detected security flaws in connected homes, with IP cameras not far behind. These devices tend to run outdated software, get fewer security updates than phones or laptops, and are rarely monitored the way a work computer would be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real-World Consequences of a Compromised Device
&lt;/h2&gt;

&lt;p&gt;It's tempting to think of a hacked smart bulb or thermostat as a minor inconvenience. In practice, a single compromised device can become a foothold into the rest of your home network, exposing laptops, phones, and any files or accounts connected to them.&lt;/p&gt;

&lt;p&gt;The most infamous example remains the 2016 Mirai botnet attack, in which hundreds of thousands of poorly secured devices, including doorbell cameras, were hijacked and used to launch massive distributed denial-of-service attacks that knocked major websites offline. That single incident demonstrated how a garage full of smart plugs and cameras could be conscripted, without their owners' knowledge, into one of the largest cyberattacks in internet history.&lt;/p&gt;

&lt;p&gt;More recent incidents have moved beyond botnets and into direct surveillance. Security researchers have documented cases where compromised smart cameras allowed strangers to watch a family's daily routine for weeks, using nothing more advanced than a default password that was never changed after setup. Camera feeds, door lock activity, and even microphone access on voice assistants represent some of the most sensitive data a device can leak, which is why cameras and locks deserve the most scrutiny of any device category in the home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With Your Router, Not Your Gadgets
&lt;/h2&gt;

&lt;p&gt;Every smart device conversation tends to start with the devices themselves, but the router is the actual front door to your network, and it deserves attention first. Change the router's default admin password immediately after setup, since this credential controls every other device on the network. Enable WPA3 encryption if your router supports it, or WPA2 at a minimum, and retire the factory-set Wi-Fi password that often ships printed on the bottom of the device.&lt;/p&gt;

&lt;p&gt;Network segmentation is one of the more effective, if underused, protections available to ordinary households. Most modern routers support a guest network feature that can be repurposed to isolate IoT devices from the primary network used by computers and phones. If a smart plug or camera on the guest network is compromised, the attacker is contained there rather than gaining a path to your laptop, banking apps, or personal files. Security researchers and &lt;a href="https://bdb-pwt.telkomuniversity.ac.id/internet-of-things-iot-teknologi-yang-menghubungkan-dunia-fisik-dan-digital/" rel="noopener noreferrer"&gt;IoT&lt;/a&gt; security guides consistently point to this kind of network isolation, alongside strong authentication, as one of the most practical defenses against exactly this kind of lateral movement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firmware Updates Are Not Optional
&lt;/h2&gt;

&lt;p&gt;Roughly 70% of connected devices in circulation carry unpatched firmware vulnerabilities, according to IoT security research compiled from multiple threat intelligence sources. Firmware is the software that runs a device at the most basic level, and manufacturers regularly release updates to patch newly discovered security holes. Unlike a phone, though, most smart devices don't nag you with update reminders, which means outdated firmware quietly accumulates unless a homeowner goes looking for it.&lt;/p&gt;

&lt;p&gt;Set a recurring reminder, perhaps quarterly, to check each device's companion app or web interface for pending firmware updates. Many current-generation devices support automatic updates, and turning that setting on removes the burden of remembering entirely. When shopping for new smart devices, treat a manufacturer's update track record as a genuine buying criterion rather than an afterthought; a camera that's cheaper but hasn't received a security patch in two years is not actually the better deal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passwords, Two-Factor Authentication, and the Human Factor
&lt;/h2&gt;

&lt;p&gt;Default credentials remain the single most exploited weakness in smart home security, and the fix is straightforward even if it's tedious. Every device should get a unique password rather than a variation on the same phrase, and a password manager makes this realistic for a household running fifteen or twenty connected gadgets rather than the two or three of a decade ago.&lt;/p&gt;

&lt;p&gt;Two-factor authentication, where available, blocks the overwhelming majority of unauthorized login attempts even when a password has been guessed or leaked elsewhere. Voice assistants, smart lock apps, and camera platforms increasingly support this option, and enabling it takes a few minutes per account. It's one of the highest-leverage security steps available, precisely because it protects you even after a mistake has already happened elsewhere, such as reusing a password that later turns up in a data breach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Devices With Security in Mind
&lt;/h2&gt;

&lt;p&gt;Not every smart device deserves a place on your network. Emerging standards like Matter aim to create more consistent security baselines across brands, and devices built to that standard are generally a safer bet than obscure, ultra-cheap alternatives with no clear manufacturer support. Before buying, it's worth checking whether a company has a documented history of shipping timely security patches, since that pattern tends to predict how the device will be maintained after you bring it home rather than how it's marketed in the box.&lt;/p&gt;

&lt;p&gt;It also helps to be deliberate about which devices actually need internet connectivity. A smart lock or camera has an obvious reason to be online; a coffee maker's connected features are harder to justify against the added attack surface. Limiting the number of connected devices in a household is itself a form of protection, since fewer devices mean fewer potential entry points for an attacker to probe.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Realistic Way to Think About the Risk
&lt;/h2&gt;

&lt;p&gt;It's worth resisting both extremes here. Headlines about mass camera hacks can make smart home ownership feel reckless, but security researchers who study these incidents note that most breaches trace back to weak passwords or someone the victim already knew, not sophisticated strangers deploying advanced tools. At the same time, dismissing the risk entirely ignores a household network that, on average, now fields close to 30 attack attempts a day.&lt;/p&gt;

&lt;p&gt;The practical takeaway sits between panic and complacency: smart devices are safe enough to use, provided the basic hygiene outlined above is actually in place. A secured router, segmented network, current firmware, and unique passwords with two-factor authentication turn a smart home from an easy target into a genuinely difficult one, without requiring a background in cybersecurity to get there.&lt;/p&gt;

&lt;p&gt;Take an inventory of every connected device in your home this week, check each one for a default password or a pending firmware update, and fix what you find. That single afternoon of maintenance addresses the majority of what actually gets exploited in the real world, and it's a far better use of time than worrying about threats that are, statistically, much less common than the mundane ones sitting unpatched on your own network right now.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Tracking Heart Health with Wearable IoT Sensors</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sun, 23 Aug 2026 05:49:12 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/tracking-heart-health-with-wearable-iot-sensors-3ak9</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/tracking-heart-health-with-wearable-iot-sensors-3ak9</guid>
      <description>&lt;p&gt;Tracking heart health with wearable IoT sensors has moved from a niche research exercise to something a consumer smartwatch does before breakfast. A photoplethysmography sensor no bigger than a fingernail can now estimate heart rate, detect irregular rhythms, and flag potential atrial fibrillation, all while sipping power from a coin-cell-sized battery. That shift didn't happen because sensors got smarter on their own. It happened because the surrounding system — signal processing, connectivity, and cloud pipelines — matured enough to turn noisy raw data into something a doctor or a user can actually trust.&lt;/p&gt;

&lt;p&gt;This article walks through how these systems work in practice: the sensor hardware, the firmware that cleans up the signal, the protocols that move data off the wrist, and the backend code that turns a stream of numbers into a heart rate you can act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Wearable Heart Sensors Actually Measure Your Pulse
&lt;/h2&gt;

&lt;p&gt;Most consumer wearables rely on photoplethysmography, or PPG, rather than the electrocardiogram electrodes used in clinical settings. A PPG sensor shines light — usually green LEDs for wrist-worn devices — into the skin and measures how much of it bounces back. Blood volume in the capillaries changes with each heartbeat, which changes how much light is absorbed. The result is a waveform that rises and falls with the cardiac cycle.&lt;/p&gt;

&lt;p&gt;The tricky part isn't detecting the waveform. It's detecting it while the wearer is walking, typing, or lifting weights. Motion artifacts can swamp the actual pulse signal, which is why most modern wearables pair the PPG sensor with an accelerometer. The accelerometer data is used to identify and subtract motion-related noise before the heart rate is calculated, a technique often called motion artifact cancellation.&lt;/p&gt;

&lt;p&gt;Some higher-end devices, including several current smartwatches and dedicated chest straps, add a single-lead ECG sensor for spot checks. ECG measures the heart's electrical activity directly rather than inferring it from blood flow, which makes it more accurate for detecting irregular rhythms, though it typically requires the user to hold two fingers on the device to complete the electrical circuit, so it isn't continuous the way PPG is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Raw Sensor Data on an IoT Device
&lt;/h2&gt;

&lt;p&gt;On the firmware side, most PPG modules — the MAX30102 is a common example in hobbyist and prototype projects — communicate over I2C and return raw infrared and red LED readings that need to be filtered before they resemble a heartbeat.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"MAX30105.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"heartRate.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;MAX30105&lt;/span&gt; &lt;span class="n"&gt;particleSensor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;RATE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RATE_SIZE&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;rateSpot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;lastBeat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;beatsPerMinute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;beatAvg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;particleSensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;I2C_SPEED_FAST&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MAX30102 not found. Check wiring."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;particleSensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;particleSensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setPulseAmplitudeRed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x0A&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;particleSensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setPulseAmplitudeGreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;irValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;particleSensor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getIR&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkForBeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irValue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastBeat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;lastBeat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;beatsPerMinute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beatsPerMinute&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;beatsPerMinute&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rateSpot&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;beatsPerMinute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;rateSpot&lt;/span&gt; &lt;span class="o"&gt;%=&lt;/span&gt; &lt;span class="n"&gt;RATE_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="n"&gt;beatAvg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;RATE_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;beatAvg&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="n"&gt;beatAvg&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;RATE_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irValue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No finger detected"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BPM: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beatAvg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of code sits at the edge of the system, on the microcontroller itself. It handles peak detection and a rolling average, but it doesn't do anything with the data beyond printing it to a serial console. In a real product, that BPM value needs to travel somewhere: a phone app, a cloud dashboard, or a clinician's monitoring system. That's where connectivity choices start to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Data Off the Device: BLE, Wi-Fi, and Cellular
&lt;/h2&gt;

&lt;p&gt;Bluetooth Low Energy is the default choice for wrist-worn and chest-strap heart monitors because it's built for exactly this use case: small, infrequent bursts of data from a battery-constrained device to a nearby phone. The Bluetooth SIG even standardized a Heart Rate Service profile, so a compliant BLE heart rate monitor can be read by any compatible app without custom pairing logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bleak&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BleakScanner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BleakClient&lt;/span&gt;

&lt;span class="n"&gt;HEART_RATE_UUID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00002a37-0000-1000-8000-00805f9b34fb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_heart_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;little&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bpm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_heart_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Heart rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bpm&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; bpm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;monitor_heart_rate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;BleakScanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HRM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No heart rate monitor found nearby.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;BleakClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HEART_RATE_UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HEART_RATE_UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;monitor_heart_rate&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For devices that need to report continuously without a phone in range — think remote patient monitoring for cardiac rehab patients — cellular options like LTE-M or NB-IoT trade higher power consumption for independence from a paired smartphone. Wi-Fi shows up in fitness equipment and home health hubs but rarely in the wearable itself, since it drains battery far faster than BLE for the same amount of data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning Sensor Streams into Something Clinically Useful
&lt;/h2&gt;

&lt;p&gt;Raw BPM numbers are a start, but heart health tracking becomes genuinely useful once the data is aggregated, contextualized, and checked against patterns over time. A single elevated reading during a workout means nothing. A resting heart rate that's crept up 15 beats per minute over three weeks is worth a second look.&lt;/p&gt;

&lt;p&gt;This is typically handled server-side, once data lands in a time-series database or a managed &lt;a href="https://mif.telkomuniversity.ac.id/pengertian-internet-of-things-cara-kerja-keuntungan-tantangan/" rel="noopener noreferrer"&gt;IoT&lt;/a&gt; platform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HeartRateReading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;bpm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
    &lt;span class="n"&gt;resting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;readings_db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HeartRateReading&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/readings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ingest_reading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HeartRateReading&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;readings_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recorded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/devices/{device_id}/resting-trend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resting_trend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;readings_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;resting_readings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bpm&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resting&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resting_readings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;insufficient_data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;device_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;average_resting_bpm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resting_readings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resting_readings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;days_covered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrhythmia detection, particularly for atrial fibrillation, adds another layer: instead of just averaging BPM, the algorithm looks at beat-to-beat interval variability. An irregular pattern across many consecutive beats is a stronger signal than any single fast or slow reading. This is roughly how consumer AFib detection features work, and it's also why regulatory bodies like the FDA have specifically cleared certain smartwatch features as medical software rather than treating them as generic fitness tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy Limits Worth Knowing About
&lt;/h2&gt;

&lt;p&gt;Wearable heart sensors are good, not infallible, and the gap matters for anyone building or relying on these systems. PPG accuracy degrades with darker skin tones in some devices, largely due to how green light interacts with melanin, a limitation that has drawn scrutiny from researchers and regulators alike. Tattoos, poor wrist fit, and cold hands can all throw off readings too, since they interfere with blood flow detection at the skin surface.&lt;/p&gt;

&lt;p&gt;There's also a difference between what a fitness tracker reports and what's clinically actionable. A device flagging "possible AFib" is a screening signal, not a diagnosis. Responsible product design treats these alerts as a prompt to see a doctor and get a real ECG, not as a replacement for one. Anyone building a health-adjacent IoT product should be explicit about this distinction in both the UI and the documentation, since overstating accuracy creates real liability and, more importantly, can mislead someone about their actual health status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building for the Long Term
&lt;/h2&gt;

&lt;p&gt;Heart health tracking is one of the clearer examples of IoT sensors delivering practical value rather than novelty. The sensors themselves are commodity hardware at this point; the differentiation comes from firmware that filters noise well, connectivity that doesn't drain the battery in a day, and backend logic that turns a stream of numbers into a trend a person can understand. Teams building in this space should treat the full pipeline — sensor, transport, and analysis — as one system to validate together, rather than optimizing each layer in isolation.&lt;/p&gt;

&lt;p&gt;If you're prototyping a wearable heart rate feature, start with a BLE Heart Rate Service–compliant sensor module, build the ingestion API before you need it to scale, and test accuracy against a known reference device under real-world motion, not just at rest on a desk.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>bluetooth</category>
    </item>
    <item>
      <title>Fast and Smart: How Factories Use the Industrial Internet of Things</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sat, 22 Aug 2026 12:05:34 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/fast-and-smart-how-factories-use-the-industrial-internet-of-things-2ce1</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/fast-and-smart-how-factories-use-the-industrial-internet-of-things-2ce1</guid>
      <description>&lt;p&gt;The Industrial Internet of Things is no longer a pilot project confined to a single test line. It has become the operating layer that connects sensors, machines, and software across entire factory floors, letting manufacturers see problems before they become expensive ones. Global IIoT spending is projected to cross $600 billion in 2026, and manufacturing remains the single largest end-use segment driving that growth. What was once a futuristic add-on is now closer to standard equipment for any factory trying to stay competitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Industrial Internet of Things Actually Means
&lt;/h2&gt;

&lt;p&gt;IIoT refers to networks of sensors, controllers, and connected machines that collect operational data and share it in real time, usually feeding into a central platform where engineers and managers can act on it. The concept overlaps with the broader consumer IoT world, but the stakes are different. A smart thermostat that misreports the temperature is an inconvenience. A pressure sensor on an industrial boiler that misreports its readings can shut down a production line or, worse, cause a safety incident.&lt;/p&gt;

&lt;p&gt;This is why industrial deployments emphasize reliability, latency, and security far more than consumer devices do. A vibration sensor bolted to a motor housing needs to report consistently for years in a hot, dusty, electrically noisy environment, and the network carrying that data needs to stay up even when the plant's other systems don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Reactive Maintenance to Predictive Maintenance
&lt;/h2&gt;

&lt;p&gt;The most mature and widely adopted use case for IIoT in manufacturing is predictive maintenance. Traditional factories run equipment until it breaks, then scramble to fix it, or they follow rigid maintenance schedules that replace parts whether they need it or not. Both approaches waste money: one on unplanned downtime, the other on premature part replacement.&lt;/p&gt;

&lt;p&gt;IIoT changes the model by monitoring equipment continuously. Vibration sensors, thermal cameras, and current sensors track how a machine behaves under normal conditions, and software flags deviations that typically precede failure. A bearing that starts vibrating slightly out of pattern, or a motor that draws more current than usual, becomes visible weeks before it would have caused a breakdown.&lt;/p&gt;

&lt;p&gt;Here is a simplified example of how a factory might process incoming sensor readings and flag anomalies using a basic threshold model in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_vibration_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline_mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline_stdev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Flags a sensor reading as anomalous if it deviates from the
    established baseline by more than `threshold` standard deviations.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;current_mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;z_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_mean&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;baseline_mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;baseline_stdev&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anomaly_detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_mean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;normal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="c1"&gt;# Example: baseline vibration in mm/s established over normal operation
&lt;/span&gt;&lt;span class="n"&gt;baseline_mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.1&lt;/span&gt;
&lt;span class="n"&gt;baseline_stdev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;
&lt;span class="n"&gt;recent_readings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_vibration_anomaly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_readings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline_mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline_stdev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real deployments use more sophisticated models, often machine learning classifiers trained on years of historical failure data, but the underlying logic is the same: compare live readings against a known-good baseline and raise an alert when something drifts too far from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-Time Monitoring Across the Production Line
&lt;/h2&gt;

&lt;p&gt;Beyond individual machines, IIoT gives plant managers a live view of the entire production line. Instead of relying on end-of-shift reports, supervisors can see throughput, defect rates, and machine status as they happen. This visibility matters because small inefficiencies compound quickly across a full shift or a full week.&lt;/p&gt;

&lt;p&gt;A conveyor running slightly slower than spec, a station with a longer-than-normal cycle time, or a machine sitting idle waiting for parts, are all the kind of issues that are easy to miss in a walkthrough but obvious on a dashboard tracking data continuously. &lt;cite&gt;&lt;a href="https://bce-sby.telkomuniversity.ac.id/internet-of-things-iot-teknologi-yang-menghubungkan-dunia-dalam-ekosistem-digital/" rel="noopener noreferrer"&gt;IoT&lt;/a&gt; analytics gives manufacturers the data they need to understand their performance and identify what is slowing them down, allowing that process to be automated further by combining IIoT systems with artificial intelligence.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Connectivity architecture matters here too. Many plants historically relied on wired plant networks that were expensive to extend and hard to reconfigure. &lt;cite&gt;The industry has been shifting away from plant-network-dependent architectures toward cellular connectivity, making it easier to deploy sensors on mobile equipment or in areas where running new cable isn't practical.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality Control and Reducing Waste
&lt;/h2&gt;

&lt;p&gt;Manufacturing quality control has traditionally relied on sampling: pulling a handful of units off the line and inspecting them by hand. IIoT enables inline inspection instead, where every unit gets checked as it passes a sensor or camera station. Machine vision systems can catch surface defects, dimensional errors, or assembly mistakes that a sampling process would likely miss.&lt;/p&gt;

&lt;p&gt;This shift matters most in industries with tight tolerances or expensive materials, where a defect caught early saves far more than one caught after several downstream processing steps have already been applied. It also generates a data trail that engineers can use to trace a quality issue back to its root cause, whether that's a specific machine, shift, or supplier batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Adoption Gap Between Large and Small Manufacturers
&lt;/h2&gt;

&lt;p&gt;IIoT adoption is uneven, and that unevenness says a lot about where the technology still faces friction. &lt;cite&gt;Around 72% of large manufacturers with 1,000 or more employees already have at least one IIoT pilot or production deployment, but only about a quarter to a third of them have scaled that pilot into an enterprise-wide rollout. Smaller manufacturers, those under 500 employees, sit at a lower 15 to 25% adoption rate.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;That gap exists for practical reasons. Sensor hardware, network infrastructure, and the data engineering needed to make sense of the resulting streams all cost money and specialized staff that smaller operations often don't have on hand. The pilot-to-scale gap is arguably a bigger industry problem than the initial adoption decision: it's one thing to instrument a single line as a proof of concept, and another to standardize that setup across every line, shift, and facility a company operates.&lt;/p&gt;

&lt;p&gt;Falling hardware costs are narrowing this gap gradually. Low-power sensors and edge AI chips have both gotten cheaper, which lowers the upfront cost of getting started, even if the integration work still takes real engineering time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Computing Is Changing Where the Intelligence Lives
&lt;/h2&gt;

&lt;p&gt;Early IIoT systems sent nearly all their sensor data to the cloud for processing, which works fine until you're dealing with hundreds of sensors producing readings multiple times per second. Bandwidth becomes a real constraint, and round-trip latency to the cloud is too slow for use cases like halting a machine the instant an anomaly appears.&lt;/p&gt;

&lt;p&gt;Edge computing addresses this by processing data locally, on a gateway device sitting on the factory floor, before deciding what needs to go to the cloud and what can be handled immediately. &lt;cite&gt;Over 87% of surveyed manufacturers agree that connected devices should become more intelligent and process data at the edge rather than sending everything to the cloud.&lt;/cite&gt; A basic edge processing pattern looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_sensor_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;95.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Processes readings locally at the edge. Only readings that cross
    a critical threshold get forwarded immediately to the cloud;
    the rest are aggregated and sent in a batch later.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;critical_alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;aggregate_batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;edge_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;critical_alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;aggregate_batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;critical_alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;send_to_cloud_immediately&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;critical_alerts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;aggregate_batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;queue_for_batch_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggregate_batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;immediate_alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;critical_alerts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batched_readings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggregate_batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern, filtering at the edge and only escalating what genuinely needs attention, keeps bandwidth costs manageable and keeps response times fast enough to matter on a live production line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Concerns That Come With Connectivity
&lt;/h2&gt;

&lt;p&gt;Connecting industrial equipment to networks introduces risk that didn't exist when machines operated in isolation. &lt;cite&gt;Operational technology networks were never designed with internet connectivity in mind, and the consequences of a manufacturing cyberattack are physical rather than purely digital: a compromised control system can damage equipment or halt production, not just leak data.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;This is one of the more honest trade-offs in the IIoT conversation. The same connectivity that enables predictive maintenance and real-time monitoring also expands the attack surface a plant has to defend. Manufacturers adopting IIoT at scale generally need to invest in network segmentation, keeping OT and IT networks separated, along with monitoring specifically built for industrial protocols, which differ from standard enterprise IT security tooling. Skipping this step to move faster on deployment tends to be a costly decision later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Is Heading
&lt;/h2&gt;

&lt;p&gt;The trajectory is toward more intelligence living closer to the machines themselves, with cloud platforms handling the aggregation, historical analysis, and cross-plant comparisons that don't need to happen in real time. Artificial intelligence is increasingly layered on top of the raw sensor data, moving predictive maintenance models from simple threshold alerts toward genuinely learned patterns of failure. Several major industrial software vendors have recently rolled out AI-assisted tools aimed specifically at equipment diagnostics and maintenance planning, suggesting the next phase of IIoT is less about connecting more devices and more about making better use of the data already flowing in.&lt;/p&gt;

&lt;p&gt;For manufacturers still early in the adoption curve, the practical starting point isn't a plant-wide overhaul. It's picking one line, one class of equipment, or one recurring failure mode, and instrumenting it well enough to prove the case internally before scaling further. The technology has matured to the point where the tools are no longer the bottleneck. The organizational work of integrating them into how a plant actually operates is where the real effort now lies.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>edgecomputing</category>
      <category>automation</category>
    </item>
    <item>
      <title>Safe and Sound: Stopping Hacks in the Internet of Things</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sat, 22 Aug 2026 11:56:10 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/safe-and-sound-stopping-hacks-in-the-internet-of-things-5cm6</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/safe-and-sound-stopping-hacks-in-the-internet-of-things-5cm6</guid>
      <description>&lt;p&gt;Internet of Things security failures rarely start with a sophisticated exploit. They start with a camera that shipped with the password "admin" still active, a router nobody patched in three years, or a smart thermostat quietly leaking data over an unencrypted connection. By the end of 2025, an estimated 21.1 billion connected devices were online worldwide, and researchers were tracking roughly 820,000 malicious attempts against them every single day. That volume alone should reframe how builders, IT teams, and everyday device owners think about IoT security: not as an edge case, but as the default battlefield.&lt;/p&gt;

&lt;p&gt;This piece looks at why IoT devices remain such an easy target, walks through the technical patterns behind the worst recent breaches, and gives concrete steps — including code — for closing the most common gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why IoT Devices Are Easier to Hack Than Computers
&lt;/h2&gt;

&lt;p&gt;A laptop or phone gets regular software updates, runs endpoint protection, and usually sits behind a user who notices when something looks wrong. Most IoT devices have none of that. They're built to a price point, run stripped-down firmware, and are often installed once and never looked at again.&lt;/p&gt;

&lt;p&gt;Routers and network edge devices illustrate the problem well. They account for the majority of IoT-related attacks and carry a disproportionate share of the most severe vulnerabilities, since compromising the device sitting between a network and the internet gives an attacker a foothold over everything behind it. Forescout's 2026 telemetry found that routers and switches now average around 32 vulnerabilities per device, and that these devices account for roughly a third of the most critical vulnerabilities found across enterprise networks.&lt;/p&gt;

&lt;p&gt;Weak or unchanged default credentials remain one of the simplest and most common entry points. Manufacturers ship devices with predictable admin logins to make setup easy, and a large share of those credentials are never rotated. Attackers don't need to break anything sophisticated when the front door is already unlocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Botnet Problem: When Your Toaster Joins an Attack
&lt;/h2&gt;

&lt;p&gt;Individually, a compromised smart plug or IP camera isn't very useful to an attacker. Aggregated into the tens of thousands, it becomes a weapon. IoT botnets recruit exactly this kind of low-value, poorly monitored device, then use the combined bandwidth to launch distributed denial-of-service (DDoS) attacks against much bigger targets.&lt;/p&gt;

&lt;p&gt;The scale here has grown sharply. Malicious IoT botnet activity increased roughly fivefold over the past year, with the population of compromised devices climbing from around 200,000 to close to a million, and these botnets now account for more than 40% of observed DDoS traffic. The Aisuru botnet, built substantially from hijacked IoT devices, was tied to a record-setting 29.9 Tbps DDoS attack in late 2025. The BadBox 2.0 botnet separately compromised more than 10 million consumer devices globally through a supply-chain infection baked into firmware before the products even reached buyers.&lt;/p&gt;

&lt;p&gt;These numbers matter because they show the shift in the threat model. IoT security isn't only about protecting the device owner's data anymore — a poorly secured smart speaker in a home network can become a small contributor to an attack on infrastructure the owner has never heard of.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption Gaps Are Still Widespread
&lt;/h2&gt;

&lt;p&gt;A large share of IoT traffic still moves without encryption. Palo Alto Networks has reported that around 98% of IoT device traffic is unencrypted, meaning sensor readings, credentials, and command data often travel in plaintext across the network. Combined with tens of billions of active devices, that translates to a huge population of endpoints where a basic packet capture on the local network is enough to expose meaningful data.&lt;/p&gt;

&lt;p&gt;This is a solvable problem at the protocol level, and it's where developers building &lt;a href="https://it.telkomuniversity.ac.id/en/what-is-iot/" rel="noopener noreferrer"&gt;IoT&lt;/a&gt; firmware or backend services have the most direct leverage. The following example shows a minimal but realistic pattern for authenticating an MQTT-connected device and encrypting its traffic with TLS, rather than trusting an open, unauthenticated broker connection — a configuration that's still common in production IoT deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;paho.mqtt.client&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;

&lt;span class="n"&gt;BROKER_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;broker.example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BROKER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8883&lt;/span&gt;  &lt;span class="c1"&gt;# TLS port, not the unencrypted 1883
&lt;/span&gt;&lt;span class="n"&gt;DEVICE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sensor-department-04&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DEVICE_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REPLACE_WITH_SECRET_FROM_ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reason_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DEVICE_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; connected securely&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devices/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DEVICE_ID&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection failed with code &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reason_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEVICE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mqtt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MQTTv5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username_pw_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEVICE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEVICE_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Enforce TLS instead of allowing a fallback to plaintext
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tls_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cert_reqs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CERT_REQUIRED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tls_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTOCOL_TLS_CLIENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tls_insecure_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_connect&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BROKER_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BROKER_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepalive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loop_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details in that snippet do most of the security work: the connection uses port 8883 with &lt;code&gt;tls_set()&lt;/code&gt; enforced rather than the plaintext 1883 default, &lt;code&gt;cert_reqs&lt;/code&gt; requires a valid certificate instead of accepting any endpoint, and the device authenticates with a per-device token rather than a shared password baked into every unit on the production line. None of this is exotic. It's the difference between a device that's defensible and one that isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firmware: The Update Problem Nobody Wants to Own
&lt;/h2&gt;

&lt;p&gt;Unlike a phone or laptop, most IoT devices don't have a clear, user-facing update mechanism. Some can't be patched at all after deployment. Others technically support updates, but the process is manual enough that it rarely happens in practice. That gap is a major reason vulnerabilities discovered years ago are still being actively exploited today — CISA's Known Exploited Vulnerabilities catalog has recorded cases where nearly half of newly added entries were vulnerabilities disclosed well before the reporting period, meaning attackers are often exploiting flaws that were already public knowledge.&lt;/p&gt;

&lt;p&gt;For teams building connected products, the fix isn't just "ship updates" — it's designing for updates from the start. A device that can verify and apply a signed firmware image over the air is fundamentally more defensible than one that requires physical access or manual flashing. Below is a simplified example of a firmware update handler that checks a cryptographic signature before applying an update, which prevents an attacker from pushing malicious firmware even if they can intercept the update channel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_firmware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firmware_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify firmware integrity before flashing using HMAC-SHA256.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;expected_signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;firmware_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firmware_bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;verify_firmware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firmware_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Firmware signature mismatch — update rejected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Only reached if the signature check passes
&lt;/span&gt;    &lt;span class="nf"&gt;write_to_flash_partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firmware_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Firmware verified and applied&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key line is &lt;code&gt;hmac.compare_digest()&lt;/code&gt; rather than a plain &lt;code&gt;==&lt;/code&gt; comparison — a constant-time comparison prevents timing attacks that could otherwise let an attacker infer the correct signature byte by byte. This kind of detail is easy to skip under deadline pressure, and it's exactly the kind of gap that separates a device that resists tampering from one that doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regulation Is Catching Up, Slowly
&lt;/h2&gt;

&lt;p&gt;Governments are starting to close the gap between how fast IoT devices ship and how weak their security baseline has been. The EU's Cyber Resilience Act introduces reporting obligations that take effect in September 2026, requiring manufacturers to report actively exploited vulnerabilities in connected products within a set window after discovery. In the US, the Cyber Trust Mark gives consumers a way to identify products that meet a baseline security standard before they buy.&lt;/p&gt;

&lt;p&gt;These frameworks won't fix the installed base of billions of already-deployed devices, but they change the incentive structure for new products. A manufacturer that has to disclose exploited vulnerabilities on a deadline has a much stronger reason to build patchable firmware and secure defaults from day one, rather than treating security as a cost center to minimize.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Reduces Risk
&lt;/h2&gt;

&lt;p&gt;For organizations managing IoT fleets, the highest-leverage moves are unglamorous. Replacing default credentials before a device goes into production, segmenting IoT traffic onto its own network rather than letting it share a subnet with sensitive systems, and enforcing encrypted connections at the protocol level closes the majority of the attack paths described above. Asset visibility matters just as much — a device that isn't in your inventory can't be patched, monitored, or removed when it's compromised, and unmanaged or forgotten devices are a recurring theme in the incidents researchers have documented over the past year.&lt;/p&gt;

&lt;p&gt;For individual device owners, the same logic applies at a smaller scale. Change the default password immediately, keep firmware updated when the manufacturer supports it, and put smart home devices on a separate guest network rather than the same one used for laptops and phones handling sensitive accounts. A compromised smart bulb is a nuisance. A compromised smart bulb sharing a network with your banking session is a much bigger problem.&lt;/p&gt;

&lt;p&gt;The Internet of Things isn't going to get less connected. Every year adds more sensors, more cameras, and more devices that quietly assume nobody is watching them closely. The organizations and developers who build in authentication, encryption, and patchability from the start are the ones whose devices stay out of the next botnet headline — and that's a far cheaper investment than cleaning up after one.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>iot</category>
    </item>
    <item>
      <title>Connecting the City: How IoT Makes Urban Life Smart</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sat, 22 Aug 2026 11:54:29 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/connecting-the-city-how-iot-makes-urban-life-smart-57lb</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/connecting-the-city-how-iot-makes-urban-life-smart-57lb</guid>
      <description>&lt;p&gt;IoT is the reason a traffic light in Taiwan can now respond to real traffic instead of a fixed timer, and the results are hard to ignore: more than 100 AI-controlled signals working together have cut congestion by as much as 25% in early rollouts. That single example captures what the Internet of Things is actually doing to cities right now. It is not a futuristic concept anymore. It is sensors under the road, meters on the grid, and cameras on the lamp posts, all quietly feeding data into systems that make daily urban life run a little smoother.&lt;/p&gt;

&lt;p&gt;The scale of this shift is no longer speculative. The global IoT in Smart Cities market was valued at roughly $214 billion in 2025 and is on track to pass $250 billion in 2026, with most forecasts putting it well above half a trillion dollars by the early 2030s. That growth is not driven by novelty. It is driven by cities that have run out of room to expand their physical infrastructure and are instead trying to squeeze more performance out of what they already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Smart City" Actually Means in Practice
&lt;/h2&gt;

&lt;p&gt;The phrase "smart city" gets thrown around loosely, so it helps to define it in concrete terms. A smart city uses networks of connected sensors, actuators, and software platforms to collect data about physical infrastructure, then acts on that data automatically or semi-automatically. Traffic lights, water mains, streetlights, waste bins, and public transit vehicles all become nodes in a larger system rather than isolated pieces of infrastructure.&lt;/p&gt;

&lt;p&gt;This matters because cities have historically managed these systems in silos. A water utility does not typically share real-time data with the transportation department, and a power grid operator rarely coordinates directly with waste management. &lt;a href="https://docif.telkomuniversity.ac.id/apa-itu-iot/" rel="noopener noreferrer"&gt;IoT&lt;/a&gt; changes that by giving every one of these systems a common language: sensor data, timestamps, and location. Once that data exists, it can be pooled into a single dashboard or fed into predictive models that span departments.&lt;/p&gt;

&lt;p&gt;The practical effect is that city governments start making decisions based on what is actually happening rather than on fixed schedules or historical averages. A garbage truck no longer follows the same route every day regardless of how full the bins are. A streetlight no longer burns at full brightness on an empty street at 3 a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traffic and Transportation: The Most Visible Win
&lt;/h2&gt;

&lt;p&gt;Traffic congestion is usually the first problem cities try to solve with IoT, and for good reason. It is visible, it is measurable, and the return on investment tends to show up fast. Smart traffic systems combine road sensors, connected cameras, and adaptive signal controllers that adjust timing in real time based on actual vehicle flow rather than a preset cycle.&lt;/p&gt;

&lt;p&gt;The architecture behind this is simpler than people expect. A typical deployment involves edge devices at each intersection that collect vehicle counts and speeds, then send that data upstream to a central optimization engine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IntersectionReading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;intersection_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;vehicle_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;avg_speed_kmh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_green_light_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IntersectionReading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="n"&gt;base_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                  &lt;span class="n"&gt;max_duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Adjust green light duration based on real-time vehicle density.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;congestion_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vehicle_count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_speed_kmh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;adjusted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_duration&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;congestion_factor&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adjusted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_intersection_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;IntersectionReading&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;signal_plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;readings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_green_light_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;signal_plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intersection_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;signal_plan&lt;/span&gt;

&lt;span class="c1"&gt;# Example: readings pulled from intersection sensors every 60 seconds
&lt;/span&gt;&lt;span class="n"&gt;sample_readings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;IntersectionReading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INT-014&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vehicle_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_speed_kmh&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;18.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nc"&gt;IntersectionReading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INT-015&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vehicle_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_speed_kmh&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;35.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_intersection_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_readings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That kind of logic, running across a network of intersections rather than one at a time, is what allows cities to treat traffic as a single connected system instead of hundreds of independent decisions. Congestion at one intersection can be anticipated before it spills into the next one, because the data from upstream sensors arrives seconds before the cars do.&lt;/p&gt;

&lt;p&gt;Public transit benefits from the same approach. Real-time occupancy sensors on buses and trains let transit authorities adjust frequency during unexpected demand spikes, and passenger-facing apps can show accurate arrival times instead of static schedules. Once that data pipeline exists, it also becomes the foundation for longer-term planning, since transit agencies can see exactly which routes are over- or under-served at specific times of day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Energy Grids and the Push Toward Efficiency
&lt;/h2&gt;

&lt;p&gt;Energy management is where IoT has produced some of the clearest financial returns. Smart grids use connected meters and sensors to monitor electricity flow at a granularity that was simply not possible with traditional infrastructure. Utilities can detect outages within seconds instead of waiting for a customer to call, and they can reroute power around damaged sections of the grid automatically.&lt;/p&gt;

&lt;p&gt;Smart lighting is a smaller-scale but widely deployed example, and it currently holds the largest revenue share within smart governance applications, at roughly 31.5% as of 2025. The concept is simple: streetlights equipped with motion and ambient light sensors dim automatically when no one is around and brighten when they detect pedestrians or vehicles. Multiply that behavior across tens of thousands of streetlights in a mid-sized city, and the energy savings compound quickly.&lt;/p&gt;

&lt;p&gt;Virtual power plants are a more advanced application worth knowing about. These systems combine distributed energy resources, such as home solar panels, battery storage units, and electric vehicle chargers, into a single coordinated network that can deliver backup capacity during peak demand. Some pilot programs are already able to supply up to 100 MW of backup grid capacity this way, which is a meaningful contribution during heat waves or unexpected demand surges, without building a single new power plant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Water, Waste, and the Less Glamorous Infrastructure
&lt;/h2&gt;

&lt;p&gt;Not every IoT application in cities gets attention, but some of the least visible ones deliver the most consistent value. Water utilities lose a significant percentage of treated water to leaks in aging pipe networks every year, and much of that loss goes undetected for months. Acoustic sensors placed along water mains can detect the specific sound signature of a leak long before it becomes a visible break in the street, cutting repair costs and water loss simultaneously.&lt;/p&gt;

&lt;p&gt;Waste management has undergone a similar transformation. Traditional garbage collection follows fixed routes on fixed days regardless of how full any individual bin actually is. IoT-enabled waste bins report their fill level to a central system, which then generates collection routes based on actual need. Early deployments of this approach have reduced the number of collection truck runs by as much as 90% in specific pilot programs, which translates directly into lower fuel costs, less vehicle wear, and reduced emissions from the collection fleet.&lt;/p&gt;

&lt;p&gt;The waste management segment is also expected to see the fastest growth rate of any smart utility category between 2026 and 2033, as more municipal governments move past the pilot phase into full deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Backbone Behind All of It
&lt;/h2&gt;

&lt;p&gt;None of these applications work in isolation, and this is where the engineering complexity actually lives. A functioning smart city needs a data pipeline that can ingest readings from thousands of heterogeneous devices, normalize that data into a consistent format, and route it to the right consuming system, whether that is a traffic controller, a utility dashboard, or a public-facing app.&lt;/p&gt;

&lt;p&gt;Message queuing systems like Kafka or MQTT brokers typically sit at the center of this architecture, since they can handle the high-frequency, high-volume nature of sensor data without forcing every downstream service to talk directly to every device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;confluent_kafka&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Consumer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Producer&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_sensor_consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Consumer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bootstrap.servers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:9092&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;group.id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;group_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;auto.offset.reset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;earliest&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Consumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_sensor_reading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Producer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Route incoming sensor data to the appropriate downstream topic.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;sensor_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sensor_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;topic_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;traffic&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city.traffic.processed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city.water.leak-detection&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;waste&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city.waste.fill-level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;energy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city.energy.grid-status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;destination_topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;topic_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city.unclassified&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;destination_topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of routing layer is what allows a city to add new sensor types over time without redesigning the entire system. A newly deployed air quality sensor network, for example, can plug into the same message bus that traffic and water systems already use, rather than requiring its own dedicated infrastructure from scratch.&lt;/p&gt;

&lt;p&gt;Security is a real constraint here, not a footnote. A 2024 study tracked more than 9 billion security events across roughly 50 million IoT devices, which is a useful reminder that every sensor added to a city network is also a potential entry point for attackers. Municipal IoT deployments generally need device-level authentication, encrypted transport, and network segmentation that keeps a compromised streetlight controller from having any path to, say, the water treatment system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Is Actually Headed
&lt;/h2&gt;

&lt;p&gt;The most interesting shift happening in 2026 is not more sensors, it is more autonomy. Early smart city deployments were largely about sensing and reporting: a dashboard would show a city planner that traffic was building up, and a human would decide what to do about it. The systems being deployed now increasingly act on that data directly, adjusting signal timing, rerouting power, or dispatching maintenance crews without waiting for manual approval.&lt;/p&gt;

&lt;p&gt;That shift raises legitimate questions that cities are still working through. Automated systems need clear override mechanisms for edge cases a sensor network cannot anticipate, and residents deserve transparency about what data is being collected and how long it is retained. Cities that get this balance right tend to treat IoT as an operational layer that supports human decision-makers rather than one that replaces them entirely.&lt;/p&gt;

&lt;p&gt;For engineers and product teams building in this space, the opportunity is less about inventing new sensor hardware and more about building the integration layer that makes disparate city systems talk to each other reliably. The cities seeing the strongest results, whether that's Taiwan's traffic network or the municipalities piloting AI-assisted emergency response, are the ones that treated data architecture as seriously as the hardware itself.&lt;/p&gt;

&lt;p&gt;If you're evaluating an IoT platform for municipal infrastructure, start by asking how it handles device failure and network partitioning, not just what it does when everything works. A sensor network that goes silent during a storm is far more dangerous than no sensor network at all if the city has come to depend on it. Building that resilience in from the start is what separates a smart city pilot from a smart city that actually holds up under real conditions.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>edgecomputing</category>
    </item>
    <item>
      <title>Lightweight Encryption Protocols for Resource-Constrained IoT Devices</title>
      <dc:creator>Fuad Husnan</dc:creator>
      <pubDate>Sat, 15 Aug 2026 07:55:28 +0000</pubDate>
      <link>https://dev.to/fuadhusnan_f44f3e13/lightweight-encryption-protocols-for-resource-constrained-iot-devices-1k4n</link>
      <guid>https://dev.to/fuadhusnan_f44f3e13/lightweight-encryption-protocols-for-resource-constrained-iot-devices-1k4n</guid>
      <description>&lt;p&gt;Lightweight encryption protocols exist because standard cryptography assumes resources that most IoT devices don't have. A temperature sensor running on a coin cell battery cannot afford the RAM, CPU cycles, or power draw that AES-256 or RSA-2048 demand on a server. This gap between what conventional encryption needs and what embedded hardware can supply has produced an entire category of ciphers built specifically for constrained environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Standard Cryptography Fails on Small Devices
&lt;/h2&gt;

&lt;p&gt;Most encryption algorithms were designed for desktops, servers, and phones — devices with abundant memory, fast processors, and a stable power supply. IoT endpoints rarely have any of that. A typical microcontroller used in a smart lock or industrial sensor might carry 8-32 KB of RAM and run at speeds measured in single-digit megahertz, not gigahertz.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Typical constrained IoT device profile&lt;/span&gt;
&lt;span class="cp"&gt;#define RAM_AVAILABLE_KB      16
#define FLASH_AVAILABLE_KB    128
#define CPU_CLOCK_MHZ         8
#define BATTERY_CAPACITY_MAH  220
#define EXPECTED_LIFESPAN_YRS 5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under these constraints, running AES-256 in software can consume RAM the device simply doesn't have left over after handling sensor input, networking stacks, and application logic. RSA key exchange is worse — the modular exponentiation involved can take seconds on an 8-bit microcontroller, draining battery reserves that are meant to last years, not days.&lt;/p&gt;

&lt;p&gt;Engineers working on constrained hardware face a three-way trade-off: security strength, computational cost, and energy consumption. Lightweight cryptography doesn't eliminate this trade-off. It shifts the curve, offering security margins appropriate for the threat model while fitting inside a fraction of the memory and power footprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Cipher "Lightweight"
&lt;/h2&gt;

&lt;p&gt;The term lightweight cryptography refers to a specific design philosophy rather than a single algorithm. NIST formalized this category through its Lightweight Cryptography Standardization project, which ran from 2018 to 2023 and evaluated dozens of candidate algorithms against criteria including silicon area, RAM footprint, energy per bit, and resistance to side-channel attacks.&lt;/p&gt;

&lt;p&gt;Three properties distinguish lightweight ciphers from their conventional counterparts:&lt;/p&gt;

&lt;p&gt;Smaller block and key sizes reduce the memory needed to hold intermediate cryptographic state. Simplified round functions cut the number of CPU cycles required per &lt;a href="https://msf.telkomuniversity.ac.id/apa-itu-enkripsi-rahasia-di-balik-keamanan-data-digital/" rel="noopener noreferrer"&gt;encryption&lt;/a&gt; operation. Reduced code size means the compiled binary fits within the limited flash storage typical of microcontrollers, sometimes under 2 KB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Comparing memory footprint: AES-128 vs ASCON (illustrative)&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;cipher_footprint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ram_bytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rom_bytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cycles_per_byte&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;cipher_footprint&lt;/span&gt; &lt;span class="n"&gt;aes128&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"AES-128"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;cipher_footprint&lt;/span&gt; &lt;span class="n"&gt;ascon128&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"ASCON-128"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are representative figures rather than benchmarks from a specific chip, but they illustrate the general pattern: lightweight ciphers aim to do more with meaningfully less.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASCON: The Current NIST Standard
&lt;/h2&gt;

&lt;p&gt;In February 2023, NIST selected the ASCON family as the winner of its Lightweight Cryptography competition, and in 2025 it was formally published as NIST SP 800-232. ASCON is an authenticated encryption with associated data (AEAD) construction, meaning it provides both confidentiality and integrity checking in a single pass rather than requiring separate encryption and MAC operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;ascon.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;encrypt_sensor_reading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;pt_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ascon_aead_ctx_t&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;ascon_aead128_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ascon_aead128_encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ascon_aead128_finalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ASCON's sponge-based construction is what allows it to run in such a small footprint. Unlike AES, which relies on lookup tables that consume both memory and are a known vector for cache-timing side-channel attacks, ASCON's permutation-based design avoids table lookups entirely, making it inherently more resistant to certain timing attacks on constrained hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  PRESENT and SPECK: Earlier Lightweight Approaches
&lt;/h2&gt;

&lt;p&gt;Before ASCON became the standardized answer, several other lightweight ciphers saw adoption in specific contexts. PRESENT, developed in 2007, is an ultra-lightweight block cipher designed for RFID tags and similarly constrained applications, using a 64-bit block size and either 80-bit or 128-bit keys.&lt;/p&gt;

&lt;p&gt;SPECK, published by the NSA in 2013, takes a different approach, favoring simple addition, rotation, and XOR (ARX) operations that map efficiently onto both software and hardware implementations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SPECK round function (simplified, 32-bit words)&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;speck_round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// rotate right 8&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// rotate left 3&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SPECK's reliance on simple arithmetic rather than substitution tables made it attractive for software-only implementations on devices without dedicated cryptographic hardware. It drew criticism, however, over its NSA origin and the absence of public design rationale for some parameter choices, which slowed formal standardization despite its practical performance advantages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elliptic Curve Cryptography for Key Exchange
&lt;/h2&gt;

&lt;p&gt;Symmetric ciphers like ASCON and SPECK handle bulk data encryption efficiently, but IoT devices still need a way to establish shared keys in the first place. This is where the size advantage of elliptic curve cryptography (ECC) over RSA becomes significant: a 256-bit ECC key offers security roughly comparable to a 3072-bit RSA key, at a fraction of the computational cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ECDH key exchange using Curve25519 (via a lightweight library)&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;monocypher.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;generate_shared_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;my_private_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;their_public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;crypto_x25519&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_private_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;their_public_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Curve25519 has become a common choice for constrained devices because its implementation avoids many of the timing-attack pitfalls that plague naive elliptic curve implementations, and reference code exists that compiles to just a few kilobytes of flash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protocol-Level Considerations Beyond the Cipher
&lt;/h2&gt;

&lt;p&gt;Choosing an efficient cipher solves only part of the problem. The communication protocol wrapping that cipher matters just as much for real-world deployments. DTLS (Datagram Transport Layer Security), the UDP-based counterpart to TLS, is commonly paired with CoAP (Constrained Application Protocol) in IoT deployments, but full DTLS handshakes can still be too heavy for the most constrained devices due to certificate exchange overhead.&lt;/p&gt;

&lt;p&gt;This has driven interest in pre-shared key (PSK) modes, which skip certificate-based authentication entirely in favor of keys provisioned during manufacturing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DTLS PSK configuration example (mbedTLS)&lt;/span&gt;
&lt;span class="n"&gt;mbedtls_ssl_conf_psk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;psk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;psk_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;psk_identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;psk_identity&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PSK mode trades the flexibility of certificate-based trust for a dramatically lighter handshake, which matters when a device wakes from deep sleep, needs to transmit a reading, and must return to sleep within a strict energy budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching Protocols to Threat Models
&lt;/h2&gt;

&lt;p&gt;Not every IoT deployment needs the same security posture. A soil moisture sensor reporting non-sensitive agricultural data has a different risk profile than a medical device transmitting patient vitals or an industrial controller managing physical machinery. Lightweight cryptography is not a shortcut around security; it's a recalibration of the trade-off between protection and resource cost for a given threat model.&lt;/p&gt;

&lt;p&gt;Devices handling sensitive data still warrant the strongest lightweight cipher available, such as ASCON-128a for higher throughput needs, combined with proper key rotation and secure boot to prevent firmware tampering. Lower-stakes telemetry devices may reasonably use a smaller security margin if it meaningfully extends battery life across a large deployed fleet, provided the failure mode of a compromise remains low-consequence.&lt;/p&gt;

&lt;p&gt;The honest limitation worth stating plainly: lightweight ciphers generally offer smaller security margins than their full-strength counterparts, and some, like SPECK, faced pushback during standardization over trust and transparency concerns. Engineers should treat cipher selection as one part of a layered security architecture, not a single point of defense, and should stay current with NIST guidance as SP 800-232 implementations mature across hardware platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing It Together
&lt;/h2&gt;

&lt;p&gt;The shift toward lightweight cryptography reflects a broader recognition that security has to fit the hardware it protects. ASCON's selection as the NIST standard gives engineers a well-vetted default for new designs, while ECC-based key exchange and PSK-mode DTLS address the handshake overhead that often gets overlooked when teams focus only on the encryption algorithm itself. For teams building constrained IoT products today, the practical starting point is ASCON for authenticated encryption, Curve25519 for key agreement, and a protocol stack — CoAP over DTLS with PSK where certificate overhead isn't justified — sized to the device's actual power and memory budget rather than borrowed wholesale from enterprise networking.&lt;/p&gt;

&lt;p&gt;Before finalizing a cryptographic approach for a new IoT product, benchmark candidate ciphers directly on your target hardware rather than relying on published figures alone, since real-world performance varies significantly across microcontroller architectures and compiler optimizations.&lt;/p&gt;

</description>
      <category>cryptography</category>
      <category>iot</category>
      <category>security</category>
    </item>
  </channel>
</rss>
