<?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: John David</title>
    <description>The latest articles on DEV Community by John David (@rizehub-ph).</description>
    <link>https://dev.to/rizehub-ph</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%2F4217586%2Fd58805f5-3c0c-43c9-ab8f-e6aa1363ed77.png</url>
      <title>DEV Community: John David</title>
      <link>https://dev.to/rizehub-ph</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rizehub-ph"/>
    <language>en</language>
    <item>
      <title>Stop Double Orders: Deduplicating Shopify Webhooks in n8n</title>
      <dc:creator>John David</dc:creator>
      <pubDate>Mon, 02 Nov 2026 03:16:29 +0000</pubDate>
      <link>https://dev.to/rizehub-ph/stop-double-orders-deduplicating-shopify-webhooks-in-n8n-4ndp</link>
      <guid>https://dev.to/rizehub-ph/stop-double-orders-deduplicating-shopify-webhooks-in-n8n-4ndp</guid>
      <description>&lt;p&gt;Shopify's webhook docs are blunt about it: your app might receive the same webhook more than once. Most of the time the cause is on our side. Shopify allows one second to connect and five seconds for the whole request. If your workflow does its Google Sheets write, a Slack message and a CRM call before it answers, you blow past five seconds, Shopify marks the delivery as failed and sends it again. It retries up to 8 times over 4 hours. Each retry runs your whole workflow again.&lt;/p&gt;

&lt;p&gt;Here's the setup I use now on every Shopify to n8n build. Four nodes, and it takes about fifteen minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Answer Shopify straight away
&lt;/h2&gt;

&lt;p&gt;In the Webhook node, set &lt;strong&gt;Respond&lt;/strong&gt; to &lt;strong&gt;Immediately&lt;/strong&gt;. n8n returns the response code the moment the request arrives and keeps working in the background. That alone removes most duplicates, because Shopify gets its 200 well inside the timeout.&lt;/p&gt;

&lt;p&gt;The trade-off: you can't send back an error from a later node any more. That's fine here. Shopify doesn't care what you do with the order, only that you received it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Verify the signature
&lt;/h2&gt;

&lt;p&gt;Anyone who finds your webhook URL can POST a fake order to it. Shopify signs each delivery with an &lt;code&gt;X-Shopify-Hmac-SHA256&lt;/code&gt; header: a base64 HMAC-SHA256 of the raw request body, made with your app's client secret.&lt;/p&gt;

&lt;p&gt;The catch is "raw". If you re-serialise the parsed JSON, the bytes change and the HMAC won't match. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the Webhook node, add the &lt;strong&gt;Raw Body&lt;/strong&gt; option. The request now comes in as binary.&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;Crypto&lt;/strong&gt; node, action &lt;strong&gt;Hmac&lt;/strong&gt;, type &lt;strong&gt;SHA256&lt;/strong&gt;, encoding &lt;strong&gt;BASE64&lt;/strong&gt;, and turn on &lt;strong&gt;Binary File&lt;/strong&gt; so it hashes the binary property from the webhook (check the property name in the Webhook output). The secret goes in the Crypto credential, not the node.&lt;/li&gt;
&lt;li&gt;Add an &lt;strong&gt;IF&lt;/strong&gt; node that compares the Crypto output with &lt;code&gt;{{ $('Webhook').item.json.headers['x-shopify-hmac-sha256'] }}&lt;/code&gt;. False branch goes nowhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the lowercase header name. Headers arrive lowercased in &lt;code&gt;$json.headers&lt;/code&gt;, and &lt;code&gt;X-Shopify-Hmac-SHA256&lt;/code&gt; with capitals will quietly return nothing.&lt;/p&gt;

&lt;p&gt;After the check passes, parse the body back into JSON (an Extract From File node set to JSON, or a short Code node) so later nodes can read the order fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Drop deliveries you've already processed
&lt;/h2&gt;

&lt;p&gt;This is the part most tutorials skip. Shopify gives every delivery an &lt;code&gt;X-Shopify-Webhook-Id&lt;/code&gt; header, and a retry of the same delivery carries the same ID. n8n has a node built for this: &lt;strong&gt;Remove Duplicates&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operation: &lt;strong&gt;Remove Items Processed in Previous Executions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Keep Items Where: &lt;strong&gt;Value Is New&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Value to Dedupe On: &lt;code&gt;{{ $('Webhook').item.json.headers['x-shopify-webhook-id'] }}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The node remembers values across executions, so the second delivery comes out with zero items and nothing downstream runs. History Size defaults to 10,000, which is plenty for a small store. Leave Scope on Node unless another node in the same workflow needs to share that history.&lt;/p&gt;

&lt;p&gt;There's a second header, &lt;code&gt;X-Shopify-Event-Id&lt;/code&gt;, and it's easy to mix the two up. The event ID is shared by every subscription that fired from the same merchant action, so if you have two subscriptions on the same topic, both deliveries carry it. Dedupe on the webhook ID if you want "process each delivery once". Use the event ID only if you deliberately want two subscriptions to collapse into one.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Make the write itself safe
&lt;/h2&gt;

&lt;p&gt;Dedupe catches retries. It won't catch everything. If you clear the node's history, rebuild the workflow, or someone replays a test, you can still get a repeat. So the last step is making the write idempotent: in Google Sheets use &lt;strong&gt;Append or Update Row&lt;/strong&gt; and match on the order ID instead of plain Append. A repeated order overwrites its own row instead of adding a new one.&lt;/p&gt;

&lt;p&gt;Same idea in a CRM: search by order ID first, then create or update.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finished workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webhook (Respond: Immediately, Raw Body on)
  -&amp;gt; Crypto (Hmac SHA256, base64, binary)
  -&amp;gt; IF (HMAC matches header)
  -&amp;gt; Remove Duplicates (x-shopify-webhook-id, Value Is New)
  -&amp;gt; Extract From File (JSON)
  -&amp;gt; Google Sheets (Append or Update Row, match on order id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One more thing worth knowing
&lt;/h2&gt;

&lt;p&gt;If your endpoint keeps failing, Shopify gives up on it. Subscriptions created through the Admin API are deleted after 8 consecutive failed deliveries. So if your n8n instance is down for a while, check the subscription still exists once it's back, or orders will stop arriving and nothing will tell you.&lt;/p&gt;

&lt;p&gt;That's the whole thing. Answer fast, check the signature, drop repeats by webhook ID, and make the final write safe to run twice.&lt;/p&gt;

&lt;p&gt;We build Shopify and n8n automations like this for small stores: &lt;a href="https://rizehub.ph/services/n8n-automation" rel="noopener noreferrer"&gt;https://rizehub.ph/services/n8n-automation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>shopify</category>
      <category>webhooks</category>
      <category>automation</category>
    </item>
    <item>
      <title>What a website tells you about itself (and two free tools that read it for you)</title>
      <dc:creator>John David</dc:creator>
      <pubDate>Sat, 31 Oct 2026 14:55:09 +0000</pubDate>
      <link>https://dev.to/rizehub-ph/what-a-website-tells-you-about-itself-and-two-free-tools-that-read-it-for-you-4o2k</link>
      <guid>https://dev.to/rizehub-ph/what-a-website-tells-you-about-itself-and-two-free-tools-that-read-it-for-you-4o2k</guid>
      <description>&lt;p&gt;Every public website hands its visitors a surprising amount of information about how it's built. Not secrets, just the normal things a browser needs to load the page. If you know where to look, you can tell the CMS, the framework, the CDN, the analytics setup and even who handles the domain's email, without logging in to anything.&lt;/p&gt;

&lt;p&gt;Here's where that information lives. After that, two free tools we built that read it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Response headers
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;server&lt;/code&gt;, &lt;code&gt;x-powered-by&lt;/code&gt;, &lt;code&gt;via&lt;/code&gt;, &lt;code&gt;cf-ray&lt;/code&gt; and similar headers often name the web server, the language or the CDN. A &lt;code&gt;cf-ray&lt;/code&gt; header means Cloudflare is in front. &lt;code&gt;x-powered-by: PHP/8.x&lt;/code&gt; gives away the backend. Plenty of sites strip these, which is good practice, but many don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The HTML and the scripts it loads
&lt;/h2&gt;

&lt;p&gt;Meta generator tags (&lt;code&gt;&amp;lt;meta name="generator" content="WordPress 6.x"&amp;gt;&lt;/code&gt;), asset paths (&lt;code&gt;/wp-content/&lt;/code&gt;, &lt;code&gt;cdn.shopify.com&lt;/code&gt;, &lt;code&gt;assets.website-files.com&lt;/code&gt;) and global variables (&lt;code&gt;window.Shopify&lt;/code&gt;, &lt;code&gt;__NEXT_DATA__&lt;/code&gt;) are strong fingerprints. Third-party script URLs give away analytics, tag managers, pixels and chat widgets.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. DNS
&lt;/h2&gt;

&lt;p&gt;MX records tell you the email provider (Google Workspace, Microsoft 365, Zoho...). TXT records hold SPF and DMARC. Those decide whether the domain's email lands in inboxes or in spam, and a surprising number of small business domains have neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The TLS certificate
&lt;/h2&gt;

&lt;p&gt;Who issued it and when it expires. An expired certificate is one of the most common "why is my site broken?" causes, and it's completely preventable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool 1: Tech Inspector
&lt;/h2&gt;

&lt;p&gt;We wrapped all of this into a free checker. Paste an address and it reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CMS and frameworks, with versions where the site shows them&lt;/li&gt;
&lt;li&gt;analytics and marketing tags&lt;/li&gt;
&lt;li&gt;hosting and CDN&lt;/li&gt;
&lt;li&gt;the email provider, plus SPF and DMARC&lt;/li&gt;
&lt;li&gt;the SSL details&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It recognizes 7,600+ technologies and gives each detection a confidence level, because fingerprints can be ambiguous. You can download the result as a PDF, which we use when scoping a rebuild or explaining to a client what their site actually runs on.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://rizehub.ph/products/tech-inspector" rel="noopener noreferrer"&gt;Tech Inspector&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool 2: Uptime Monitor
&lt;/h2&gt;

&lt;p&gt;Knowing what a site runs on is one half. Knowing when it stops running is the other. Small business sites usually go down for boring reasons: a hosting hiccup, a plugin update, a certificate or domain that didn't renew. And the owner tends to be the last to find out, often from a customer.&lt;/p&gt;

&lt;p&gt;So we also built a free uptime monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It loads your page every 5 minutes.&lt;/li&gt;
&lt;li&gt;After two failed checks in a row it sends one email or SMS, then one more when the site is back. No flood of alerts.&lt;/li&gt;
&lt;li&gt;It warns you 14 days before your SSL certificate expires.&lt;/li&gt;
&lt;li&gt;An optional keyword check catches the "page loads but shows an error" case that plain ping checks miss.&lt;/li&gt;
&lt;li&gt;Response-time history and an incident log come with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The free plan covers up to 5 sites.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://rizehub.ph/products/uptime-monitor" rel="noopener noreferrer"&gt;Uptime Monitor&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick checklist for your own site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Does it redirect HTTP to HTTPS, and when does the certificate expire?&lt;/li&gt;
&lt;li&gt;Do you have SPF and DMARC records? (No DMARC = easier to spoof your domain.)&lt;/li&gt;
&lt;li&gt;Is the analytics tag you think is installed actually firing?&lt;/li&gt;
&lt;li&gt;Are you loading libraries you no longer use (old jQuery plugins, a second analytics tag)?&lt;/li&gt;
&lt;li&gt;Would you know within 10 minutes if the site went down at 2 AM?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those made you go "hm", the two tools above will answer them in a minute.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm part of RizeHub, a small web studio in Davao City, Philippines. We build Shopify, Webflow and WordPress sites, and these tools came out of our own client work. Disclosure: both tools are ours, and both are free.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>tooling</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
