<?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: SignalWatch</title>
    <description>The latest articles on DEV Community by SignalWatch (@signalwatch).</description>
    <link>https://dev.to/signalwatch</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%2F4140352%2F12d40a22-db08-430d-b5f3-c61592458d52.png</url>
      <title>DEV Community: SignalWatch</title>
      <link>https://dev.to/signalwatch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/signalwatch"/>
    <language>en</language>
    <item>
      <title>Run an n8n workflow when a page's text changes</title>
      <dc:creator>SignalWatch</dc:creator>
      <pubDate>Thu, 24 Sep 2026 04:12:02 +0000</pubDate>
      <link>https://dev.to/signalwatch/run-an-n8n-workflow-when-a-pages-text-changes-38m0</link>
      <guid>https://dev.to/signalwatch/run-an-n8n-workflow-when-a-pages-text-changes-38m0</guid>
      <description>&lt;p&gt;The last post was the product. This one is the wiring: an n8n Webhook trigger that runs when a public page's text changes, with the unified diff in the payload.&lt;/p&gt;

&lt;p&gt;Screenshot monitors are built for an inbox. If you already live in n8n, you want a POST you can branch on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An n8n instance (cloud or self-hosted) with a Webhook node&lt;/li&gt;
&lt;li&gt;A public URL whose text you care about: a pricing page, a changelog, a status page, a terms page&lt;/li&gt;
&lt;li&gt;A SignalWatch monitor. Free is 3 monitors at 15 minutes, no card: &lt;a href="https://signalwatch.litework.me/" rel="noopener noreferrer"&gt;https://signalwatch.litework.me/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not render JavaScript and it does not log in. If the price only exists after a client-side render, this will not see it. Server-rendered HTML is the fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Catch the webhook in n8n
&lt;/h2&gt;

&lt;p&gt;Add a Webhook node. Method POST. Copy the &lt;strong&gt;production&lt;/strong&gt; URL (the test URL is only for a trial run).&lt;/p&gt;

&lt;p&gt;n8n's default Webhook output looks like &lt;code&gt;{ headers, params, query, body }&lt;/code&gt;. The event fields are under &lt;code&gt;body&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Point a monitor at that URL
&lt;/h2&gt;

&lt;p&gt;Create a monitor for the page. Two noise controls matter more than the schedule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSS selector&lt;/strong&gt;, so you hash one region (the price table) instead of the whole document&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignore regex&lt;/strong&gt;, so "updated 3 minutes ago" does not page you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paste the n8n production URL into the monitor's webhook URL. Press &lt;strong&gt;Send test alert&lt;/strong&gt;. The Webhook node receives an event with &lt;code&gt;"event": "test"&lt;/code&gt;. Map your later nodes from that payload so you are not waiting for a real change.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Branch on the event
&lt;/h2&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="err"&gt;$json.body.event&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 your Webhook node is set to put the JSON at the top level, drop the &lt;code&gt;.body&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;content.changed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{{$json.body.diff}}&lt;/code&gt; is a unified diff. &lt;code&gt;change_ratio&lt;/code&gt; is how much of the text moved. Post the diff to Slack, append a row, or hand it to an LLM node for one sentence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check.down&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The fetch failed. &lt;code&gt;error&lt;/code&gt; says why.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check.up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;It recovered.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The button. Use it to pin the schema.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A real change looks like this:&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;"check_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Competitor pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content.changed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"change_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.031&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"diff"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--- before&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;+++ after&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;@@ -3 +3 @@&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;-Starter $10/mo&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;+Starter $14/mo"&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;Slack and Discord webhook URLs are special-cased: paste &lt;code&gt;https://hooks.slack.com/...&lt;/code&gt; or a Discord webhook and you get a formatted message with a fenced diff, not this raw JSON. Everything else, including n8n, gets the JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check the signature if the workflow does something expensive
&lt;/h2&gt;

&lt;p&gt;Every delivery sends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;X-SignalWatch-Event&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-SignalWatch-Delivery&lt;/code&gt; (new id per attempt)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-SignalWatch-Timestamp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-SignalWatch-Signature: sha256=...&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The signature is HMAC-SHA256 of &lt;code&gt;timestamp + "." + raw body&lt;/code&gt;, using the webhook signing secret on the dashboard. Reject timestamps older than five minutes. An n8n Crypto node can do the compare before you open a ticket or spend an LLM call.&lt;/p&gt;

&lt;p&gt;Deliveries are queued. If n8n is down, they retry with backoff for hours instead of dying inside the poll.&lt;/p&gt;

&lt;h2&gt;
  
  
  See a real diff first
&lt;/h2&gt;

&lt;p&gt;There is a public monitor on the Hacker News front page, scoped to story titles, so you can read actual diffs with no account: &lt;a href="https://signalwatch.litework.me/live.html" rel="noopener noreferrer"&gt;https://signalwatch.litework.me/live.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Curl, Python, and Node signature checks are on the examples page: &lt;a href="https://signalwatch.litework.me/examples.html#n8n" rel="noopener noreferrer"&gt;https://signalwatch.litework.me/examples.html#n8n&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The product post, if you want the constraints in one place: &lt;a href="https://dev.to/signalwatch/i-wanted-the-diff-not-a-screenshot-a-small-url-change-api-1aoh"&gt;https://dev.to/signalwatch/i-wanted-the-diff-not-a-screenshot-a-small-url-change-api-1aoh&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you wire this up, I want to know which page you pointed it at and which noise filter you actually needed.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>automation</category>
    </item>
    <item>
      <title>I wanted the diff, not a screenshot: a small URL-change API</title>
      <dc:creator>SignalWatch</dc:creator>
      <pubDate>Thu, 24 Sep 2026 04:05:26 +0000</pubDate>
      <link>https://dev.to/signalwatch/i-wanted-the-diff-not-a-screenshot-a-small-url-change-api-1aoh</link>
      <guid>https://dev.to/signalwatch/i-wanted-the-diff-not-a-screenshot-a-small-url-change-api-1aoh</guid>
      <description>&lt;p&gt;I kept wanting one thing: paste a public URL, and get a unified diff when the text changes — in email, Slack, or a JSON webhook I can point at n8n.&lt;/p&gt;

&lt;p&gt;Screenshot tools are built for that inbox moment ("something changed, go look"). Self-hosting changedetection.io is the right answer if you want a container. I wanted the hosted, boring version: visible text, a CSS selector, an ignore regex, and a signed webhook.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://signalwatch.litework.me/" rel="noopener noreferrer"&gt;SignalWatch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Polls a public URL (15 min on the free plan)&lt;/li&gt;
&lt;li&gt;Hashes the visible text, optionally scoped to a CSS selector&lt;/li&gt;
&lt;li&gt;Strips noise with an ignore regex, ignore-line-order, and a minimum change ratio&lt;/li&gt;
&lt;li&gt;Sends email, a Slack/Discord message with a diff block, or HMAC-signed JSON&lt;/li&gt;
&lt;li&gt;Keeps the event history so the dashboard can show what actually moved&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;p&gt;It does not render JavaScript, log in, or take screenshots. If the price only exists after client-side rendering, it will not see it.&lt;/p&gt;

&lt;p&gt;There is a public monitor on the Hacker News front page so you can see real diffs without an account: &lt;a href="https://signalwatch.litework.me/live.html" rel="noopener noreferrer"&gt;live sample&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Free tier is 3 monitors, no card.&lt;/p&gt;

&lt;p&gt;I would like to know what you would watch with it, and which noise filter you actually need.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>python</category>
    </item>
  </channel>
</rss>
