<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Backend | Toolbox Guides &amp; Articles</title><link>https://blog.toolbox.vishnudigital.com/tags/backend/</link><atom:link href="https://blog.toolbox.vishnudigital.com/tags/backend/index.xml" rel="self" type="application/rss+xml"/><description>Backend</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sun, 06 Sep 2026 00:00:00 +0000</lastBuildDate><image><url>https://blog.toolbox.vishnudigital.com/media/icon.svg</url><title>Backend</title><link>https://blog.toolbox.vishnudigital.com/tags/backend/</link></image><item><title>HMAC-SHA256 Webhook Signature Verification: RFC 2104 Internals and Provider Formats</title><link>https://blog.toolbox.vishnudigital.com/hmac-sha256-webhook-signature-verification-guide/</link><pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.toolbox.vishnudigital.com/hmac-sha256-webhook-signature-verification-guide/</guid><description>
&lt;aside class="comic-bubble not-prose my-8 p-6 rounded-2xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#fdfbf7] dark:bg-[#28201a] shadow-[4px_4px_0px_#2c221a] dark:shadow-[4px_4px_0px_#14100d]" aria-label="AEO Quick Answer &amp; Specification Dossier"&gt;
&lt;div class="flex items-center justify-between gap-3 mb-3"&gt;
&lt;div class="flex items-center gap-2"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2.5 py-0.5 rounded text-[11px] font-black tracking-wider"&gt;QUICK ANSWER&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold text-[#2c221a]/70 dark:text-[#ede6db]/70"&gt;RFC SPECIFICATION SUMMARY&lt;/span&gt;
&lt;/div&gt;
&lt;span class="hidden sm:inline-flex items-center gap-1.5 text-[11px] font-bold font-mono px-2 py-0.5 rounded border border-emerald-700 dark:border-emerald-500 bg-emerald-100 dark:bg-emerald-950 text-emerald-900 dark:text-emerald-200"&gt;
&lt;span class="inline-block w-1.5 h-1.5 rounded-full bg-emerald-600 dark:bg-emerald-400"&gt;&lt;/span&gt; 100% Client-Side
&lt;/span&gt;
&lt;/div&gt;
&lt;p class="text-base sm:text-lg text-[#2c221a] dark:text-[#ede6db] leading-relaxed font-semibold mb-4"&gt;
HMAC (RFC 2104) authenticates a message by nesting two hash passes around a padded secret: HMAC(K, m) = H((K&amp;#39; XOR opad) || H((K&amp;#39; XOR ipad) || m)), where ipad is 0x36 and opad is 0x5c repeated to the hash block size. To verify a webhook, recompute the HMAC over the exact raw request body using the shared signing secret, then compare against the provider&amp;#39;s header with a constant-time comparison. Stripe uses Stripe-Signature with t and v1 fields over &amp;#39;timestamp.rawBody&amp;#39;, GitHub uses X-Hub-Signature-256 as sha256= plus a hex digest, and Shopify uses X-Shopify-Hmac-SHA256 in Base64.
&lt;/p&gt;
&lt;div class="flex flex-wrap gap-2 text-xs font-mono font-bold mt-4 pt-3 border-t border-[#ded4c3] dark:border-[#3b332c]"&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Difficulty: Intermediate&lt;/span&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Time: 8 Mins&lt;/span&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Privacy: 100% Client-Side RAM&lt;/span&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Standard: RFC 2104 / FIPS PUB 198-1&lt;/span&gt;
&lt;/div&gt;
&lt;/aside&gt;
&lt;div class="tool-embed-container comic-panel not-prose my-10 rounded-2xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#fdfbf7] dark:bg-[#28201a] shadow-[4px_4px_0px_#2c221a] dark:shadow-[4px_4px_0px_#14100d] overflow-hidden" id="embed-hmac-generator-1788851897568855000-box" role="region" aria-label="Interactive Tool Workbench"&gt;
&lt;div class="comic-halftone p-4 sm:p-5 border-b-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#f5efe4] dark:bg-[#211a14] flex flex-wrap items-center justify-between gap-3"&gt;
&lt;div class="flex items-center gap-2.5 flex-wrap min-w-0"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2.5 py-0.5 rounded text-[10px] font-black tracking-wider uppercase"&gt;
Interactive Workbench
&lt;/span&gt;
&lt;span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300 border border-emerald-300 dark:border-emerald-800 shrink-0"&gt;
&lt;span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"&gt;&lt;/span&gt;
&lt;span&gt;LIVE&lt;/span&gt;
&lt;/span&gt;
&lt;h3 class="text-base sm:text-lg font-extrabold text-[#2c221a] dark:text-[#ede6db] tracking-tight truncate m-0"&gt;
HMAC-SHA256 Webhook Signature Verification: RFC 2104 Internals and Provider Formats
&lt;/h3&gt;
&lt;/div&gt;
&lt;div class="flex items-center gap-2 shrink-0"&gt;
&lt;a href="https://toolbox.vishnudigital.com/hmac-generator" target="_blank" rel="noopener noreferrer" class="comic-btn-secondary !py-1 !px-3 !text-xs font-mono font-bold flex items-center gap-1.5 shadow-[2px_2px_0px_#2c221a] dark:shadow-[2px_2px_0px_#14100d] hover:translate-x-[-1px] hover:translate-y-[-1px] transition-all" title="Open full utility in new window"&gt;
&lt;span&gt;Open Fullscreen ↗&lt;/span&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="relative w-full overflow-hidden bg-[#fdfbf7] dark:bg-[#1a1410]" style="min-height: 420px;"&gt;
&lt;div id="embed-hmac-generator-1788851897568855000-skeleton" class="absolute inset-0 flex flex-col items-center justify-center p-8 bg-[#fdfbf7] dark:bg-[#1e1814] z-10 transition-opacity duration-300"&gt;
&lt;div class="flex items-center gap-3 p-4 rounded-xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#f5efe4] dark:bg-[#28201a] shadow-[2px_2px_0px_#2c221a] dark:shadow-[2px_2px_0px_#14100d] max-w-sm w-full"&gt;
&lt;div class="w-8 h-8 rounded-lg bg-amber-500/20 flex items-center justify-center shrink-0"&gt;
&lt;svg class="w-5 h-5 text-amber-600 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"&gt;
&lt;circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"&gt;&lt;/circle&gt;
&lt;path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"&gt;&lt;/path&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="min-w-0 flex-1"&gt;
&lt;div class="text-xs font-bold text-[#2c221a] dark:text-[#ede6db] truncate"&gt;Initializing Workbench...&lt;/div&gt;
&lt;div class="text-[10px] font-mono text-[#2c221a]/60 dark:text-[#ede6db]/60"&gt;100% Client-Side RAM Sandbox&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;iframe
id="embed-hmac-generator-1788851897568855000-frame"
data-base-src="https://toolbox.vishnudigital.com/hmac-generator?embed=true"
src="about:blank"
title="HMAC-SHA256 Webhook Signature Verification: RFC 2104 Internals and Provider Formats – Free Online Utility"
class="w-full border-0 block opacity-0 transition-opacity duration-300"
style="height: 600px; min-height: 420px;"
loading="lazy"
allow="clipboard-read; clipboard-write; web-share"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-downloads"&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;div class="p-3 sm:px-5 border-t border-[#ded4c3] dark:border-[#3b332c] bg-[#f5efe4]/80 dark:bg-[#211a14]/80 flex flex-wrap items-center justify-between gap-3 text-xs font-mono"&gt;
&lt;div class="flex items-center gap-2 text-[#2c221a]/70 dark:text-[#ede6db]/70"&gt;
&lt;span class="text-emerald-600 dark:text-emerald-400 font-bold"&gt;🔒 Private Execution:&lt;/span&gt;
&lt;span class="hidden sm:inline"&gt;Computations run in local browser memory. Zero server uploads.&lt;/span&gt;
&lt;span class="sm:hidden"&gt;Zero server uploads.&lt;/span&gt;
&lt;/div&gt;
&lt;div class="flex items-center gap-1.5 shrink-0"&gt;
&lt;a href="https://toolbox.vishnudigital.com/hmac-generator" target="_blank" rel="noopener noreferrer" class="font-bold text-amber-700 dark:text-amber-400 hover:text-amber-800 dark:hover:text-amber-300 hover:underline"&gt;
Launch Fullscreen →
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
(function() {
const containerId = "embed-hmac-generator-1788851897568855000";
const frame = document.getElementById(containerId + "-frame");
const skeleton = document.getElementById(containerId + "-skeleton");
if (!frame) return;
function getCurrentTheme() {
return document.documentElement.classList.contains("dark") ? "dark" : "light";
}
function resolveSrc() {
const baseSrc = frame.getAttribute("data-base-src");
if (!baseSrc) return "";
const theme = getCurrentTheme();
return baseSrc + (baseSrc.includes("?") ? "&amp;" : "?") + "theme=" + theme;
}
function loadIframe() {
if (frame.getAttribute("src") !== "about:blank") return;
const fullSrc = resolveSrc();
frame.src = fullSrc;
frame.onload = function() {
if (skeleton) {
skeleton.classList.add("opacity-0");
setTimeout(function() { skeleton.style.display = "none"; }, 300);
}
frame.classList.remove("opacity-0");
frame.classList.add("opacity-100");
syncThemeToIframe();
};
}
function syncThemeToIframe() {
if (!frame || !frame.contentWindow) return;
try {
frame.contentWindow.postMessage({
type: "TOOLBOX_THEME_CHANGE",
theme: getCurrentTheme()
}, "*");
} catch(e) {}
}
if ("IntersectionObserver" in window) {
const observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
loadIframe();
observer.disconnect();
}
});
}, { rootMargin: "250px" });
observer.observe(frame);
} else {
loadIframe();
}
window.addEventListener("message", function(event) {
if (
event.data &amp;&amp;
typeof event.data === "object" &amp;&amp;
event.data.type === "TOOLBOX_RESIZE" &amp;&amp;
event.data.height
) {
if (event.source === frame.contentWindow) {
const measured = Math.max(event.data.height + 20, 420);
frame.style.height = measured + "px";
}
}
});
const themeObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if (m.attributeName === "class") {
syncThemeToIframe();
}
});
});
themeObserver.observe(document.documentElement, { attributes: true });
document.querySelectorAll(".theme-toggle").forEach(function(btn) {
btn.addEventListener("click", function() {
setTimeout(syncThemeToIframe, 50);
});
});
})();
&lt;/script&gt;
&lt;div class="tool-hero-cta not-prose my-10 p-6 sm:p-7 rounded-2xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#fdfbf7] dark:bg-[#28201a] shadow-[4px_4px_0px_#2c221a] dark:shadow-[4px_4px_0px_#14100d] flex flex-col md:flex-row items-start md:items-center justify-between gap-6 transition-all" role="complementary" aria-label="Interactive Companion Tool"&gt;
&lt;div class="space-y-2 max-w-xl"&gt;
&lt;div class="flex items-center gap-2"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2.5 py-0.5 rounded text-[10px] font-black tracking-wider uppercase"&gt;
Free In-Browser Tool
&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold text-stone-500 dark:text-stone-400"&gt;TOOLBOX UTILITY&lt;/span&gt;
&lt;/div&gt;
&lt;h3 class="text-xl font-extrabold text-[#2c221a] dark:text-[#ede6db] tracking-tight m-0"&gt;
HMAC Signature Generator &amp;amp; Verifier
&lt;/h3&gt;
&lt;p class="text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium m-0"&gt;
Paste a payload and secret, choose SHA-256, SHA-384, SHA-512, or SHA-1, and compare the computed digest against the signature you received.
&lt;/p&gt;
&lt;/div&gt;
&lt;div class="shrink-0 w-full md:w-auto"&gt;
&lt;a href="https://toolbox.vishnudigital.com/hmac-generator" class="comic-btn-primary w-full md:w-auto text-center" &gt;
Open HMAC Generator &amp;amp; Verifier →
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="the-problem-hmac-solves"&gt;The Problem HMAC Solves&lt;/h2&gt;
&lt;p&gt;You receive an HTTP POST claiming to be a payment confirmation. The body says an order was paid. Acting on it moves inventory and money. Two questions must be answered before your code touches the database:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Authenticity&lt;/strong&gt; — did this come from the provider, or from anyone who found the endpoint URL?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrity&lt;/strong&gt; — is the body byte-for-byte what the provider sent, or was an amount edited in transit?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A Keyed-Hash Message Authentication Code answers both. The provider holds a secret, you hold the same secret, and the provider attaches a tag that only a holder of that secret could have produced over that exact payload.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What you get from this guide:&lt;/strong&gt; the construction, the specific attack that motivates it, working verification code for three major providers, and the four mistakes that cause almost every &amp;ldquo;signature invalid&amp;rdquo; incident.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-rfc-2104-construction"&gt;The RFC 2104 Construction&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HMAC(K, m) = H( (K&amp;#39; XOR opad) || H( (K&amp;#39; XOR ipad) || m ) )
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;H&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Underlying hash function (SHA-256, SHA-512, SHA-1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hash block size in bytes — &lt;strong&gt;64&lt;/strong&gt; for SHA-1/SHA-224/SHA-256, &lt;strong&gt;128&lt;/strong&gt; for SHA-384/SHA-512&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;K'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The key, normalised to exactly &lt;code&gt;B&lt;/code&gt; bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ipad&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Byte &lt;code&gt;0x36&lt;/code&gt; repeated &lt;code&gt;B&lt;/code&gt; times (inner pad)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;opad&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Byte &lt;code&gt;0x5C&lt;/code&gt; repeated &lt;code&gt;B&lt;/code&gt; times (outer pad)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Key normalisation matters:&lt;/strong&gt; a key shorter than &lt;code&gt;B&lt;/code&gt; is right-padded with zero bytes; a key &lt;strong&gt;longer&lt;/strong&gt; than &lt;code&gt;B&lt;/code&gt; is first hashed with &lt;code&gt;H&lt;/code&gt;, and that digest becomes the key. This means a 200-character secret and its SHA-256 digest produce identical HMACs — a detail that occasionally explains why two &amp;ldquo;different&amp;rdquo; secrets validate the same payload.&lt;/p&gt;
&lt;h3 id="why-not-hsecret--message"&gt;Why not &lt;code&gt;H(secret || message)&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;Because SHA-1 and the SHA-2 family are &lt;strong&gt;Merkle–Damgård&lt;/strong&gt; constructions that expose their internal state as the output digest. Given &lt;code&gt;H(secret || message)&lt;/code&gt; and the length of &lt;code&gt;secret || message&lt;/code&gt;, an attacker can resume the hash from that state and compute a valid digest for &lt;code&gt;secret || message || padding || evil&lt;/code&gt; — without ever learning the secret. That is a &lt;strong&gt;length-extension attack&lt;/strong&gt;, and it turns a naive &amp;ldquo;signature&amp;rdquo; into a forgery oracle.&lt;/p&gt;
&lt;h3 id="block-sizes-and-digest-lengths"&gt;Block sizes and digest lengths&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hash function&lt;/th&gt;
&lt;th&gt;Block size &lt;code&gt;B&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Digest&lt;/th&gt;
&lt;th&gt;Hex characters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MD5&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;128 bits&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;160 bits&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SHA-256&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;256 bits&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-384&lt;/td&gt;
&lt;td&gt;128 bytes&lt;/td&gt;
&lt;td&gt;384 bits&lt;/td&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-512&lt;/td&gt;
&lt;td&gt;128 bytes&lt;/td&gt;
&lt;td&gt;512 bits&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The two pads are the whole trick: &lt;code&gt;ipad&lt;/code&gt; and &lt;code&gt;opad&lt;/code&gt; differ in exactly half their bits, so the inner and outer passes behave like two independent keyed functions.&lt;/p&gt;
&lt;p&gt;The double-pad nesting in HMAC means the attacker only ever sees the output of the &lt;em&gt;outer&lt;/em&gt; hash, whose input they cannot control or extend. This is why RFC 2104 (1997) and FIPS PUB 198-1 (2008) both standardise the nested form, and why &lt;code&gt;crypto.createHmac&lt;/code&gt; exists in every standard library.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="provider-formats-compared"&gt;Provider Formats Compared&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Encoding&lt;/th&gt;
&lt;th&gt;Signed data&lt;/th&gt;
&lt;th&gt;Secret&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stripe&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Stripe-Signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hex, inside &lt;code&gt;t=…,v1=…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;quot;{timestamp}.{raw_body}&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;endpoint secret (&lt;code&gt;whsec_…&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Hub-Signature-256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hex, prefixed &lt;code&gt;sha256=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw body&lt;/td&gt;
&lt;td&gt;webhook secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shopify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Shopify-Hmac-SHA256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base64&lt;/td&gt;
&lt;td&gt;raw body&lt;/td&gt;
&lt;td&gt;app client secret&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All three use HMAC-SHA256 and all three require the &lt;strong&gt;unmodified raw body&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="stripe"&gt;Stripe&lt;/h3&gt;
&lt;p&gt;The header carries a timestamp and one or more scheme-prefixed signatures:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Stripe-Signature: t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verification, per Stripe&amp;rsquo;s manual procedure: split the header on &lt;code&gt;,&lt;/code&gt; then &lt;code&gt;=&lt;/code&gt;, build &lt;code&gt;signed_payload&lt;/code&gt; as the timestamp, a literal &lt;code&gt;.&lt;/code&gt;, and the raw JSON body, compute HMAC-SHA256 with the endpoint secret, compare in constant time, and reject if the timestamp is outside your tolerance. Stripe&amp;rsquo;s official libraries default to a &lt;strong&gt;five-minute&lt;/strong&gt; tolerance, and Stripe advises ignoring any scheme that is not &lt;code&gt;v1&lt;/code&gt; to prevent downgrade attacks. During a secret roll, an endpoint can have two active secrets and the header then carries one &lt;code&gt;v1&lt;/code&gt; signature per secret — so compare against &lt;strong&gt;all&lt;/strong&gt; &lt;code&gt;v1&lt;/code&gt; values, not just the first.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node:crypto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;verifyStripe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toleranceSec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Freshness: a valid signature is not a fresh one.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&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="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;toleranceSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;utf8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;v1=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;slice&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;timingSafeEqualHex&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="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;timingSafeEqualHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ab&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;bb&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;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="github"&gt;GitHub&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;verifyGithub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sha256=&amp;#34;&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="nx"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;hex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&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;b&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;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="shopify"&gt;Shopify&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;verifyShopify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sha256&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;utf8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;base64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="s2"&gt;&amp;#34;base64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;base64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&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;b&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;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;div class="in-article-ad-wrapper not-prose my-8 p-3 sm:p-4 rounded-xl border-2 border-stone-900/40 dark:border-stone-400/40 bg-[#fdfbf7] dark:bg-[#211c18] shadow-[3px_3px_0px_rgba(41,30,20,0.3)] dark:shadow-[3px_3px_0px_rgba(242,237,228,0.2)] overflow-hidden"&gt;
&lt;div class="flex items-center justify-between mb-2 border-b border-stone-200 dark:border-stone-800 pb-1"&gt;
&lt;span class="text-[10px] font-mono font-bold uppercase tracking-widest text-stone-500 dark:text-stone-400"&gt;Advertisement&lt;/span&gt;
&lt;span class="text-[9px] font-mono uppercase px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-950 text-amber-800 dark:text-amber-300"&gt;Sponsored&lt;/span&gt;
&lt;/div&gt;
&lt;div class="w-full flex justify-center items-center overflow-hidden min-h-[250px]"&gt;
&lt;ins class="adsbygoogle"
style="display:block; text-align:center; min-height:250px; width:100%;"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-5474548764059850"
&gt;&lt;/ins&gt;
&lt;script&gt;
(adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="the-four-failure-modes"&gt;The Four Failure Modes&lt;/h2&gt;
&lt;h3 id="1-the-body-was-parsed-before-you-hashed-it"&gt;1. The body was parsed before you hashed it&lt;/h3&gt;
&lt;p&gt;The most common cause by a wide margin. &lt;code&gt;express.json()&lt;/code&gt;, Django&amp;rsquo;s request handling, and Next.js route defaults may hand you a re-serialised object. Re-encoding &lt;code&gt;{&amp;quot;a&amp;quot;:1.0}&lt;/code&gt; as &lt;code&gt;{&amp;quot;a&amp;quot;:1}&lt;/code&gt;, reordering keys, or normalising Unicode escapes all change the byte string and therefore the digest. Stripe states the requirement plainly: the raw body must be the string it sent, in UTF-8, unchanged.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Order matters. Mount the raw-body webhook route BEFORE the JSON parser.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/webhooks/stripe&amp;#34;&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="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-non-constant-time-comparison"&gt;2. Non-constant-time comparison&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;expected === received&lt;/code&gt; returns as soon as it hits a differing byte. That timing difference is measurable across enough requests and leaks the expected digest byte by byte. Use &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt;, &lt;code&gt;hmac.compare_digest&lt;/code&gt; (Python), or &lt;code&gt;hash_equals&lt;/code&gt; (PHP). Compare decoded &lt;strong&gt;bytes&lt;/strong&gt; of equal length — &lt;code&gt;timingSafeEqual&lt;/code&gt; throws on length mismatch, so check length first.&lt;/p&gt;
&lt;h3 id="3-no-replay-protection"&gt;3. No replay protection&lt;/h3&gt;
&lt;p&gt;A valid signature says &amp;ldquo;this was authentic when created&amp;rdquo;. It says nothing about &lt;em&gt;when&lt;/em&gt;. Sign a timestamp (Stripe&amp;rsquo;s model) and enforce a tolerance, or store processed delivery IDs and drop repeats. Stripe also warns against setting the tolerance to &lt;code&gt;0&lt;/code&gt;, which disables the recency check entirely.&lt;/p&gt;
&lt;h3 id="4-signature-comparison-as-the-only-gate"&gt;4. Signature comparison as the only gate&lt;/h3&gt;
&lt;p&gt;Also validate that the event type is one you handle, return &lt;code&gt;2xx&lt;/code&gt; quickly and process asynchronously, and treat delivery as at-least-once — providers retry, and duplicate events are normal. Stripe explicitly does not guarantee ordering, so deduplicate on event ID rather than trusting timestamps.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="hex-base64-or-base64url--match-the-provider-exactly"&gt;Hex, Base64, or Base64url — Match the Provider Exactly&lt;/h2&gt;
&lt;p&gt;One 32-byte HMAC-SHA256 tag has several textual forms, and mismatched encoding is the second most common cause of failed verification after raw-body mistakes.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Encoding&lt;/th&gt;
&lt;th&gt;Length for SHA-256&lt;/th&gt;
&lt;th&gt;Typical users&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lowercase hex&lt;/td&gt;
&lt;td&gt;64 characters&lt;/td&gt;
&lt;td&gt;GitHub, Stripe, most REST APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base64&lt;/td&gt;
&lt;td&gt;44 characters (with &lt;code&gt;=&lt;/code&gt; padding)&lt;/td&gt;
&lt;td&gt;Shopify, Slack legacy, AWS headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base64url&lt;/td&gt;
&lt;td&gt;43 characters (no padding)&lt;/td&gt;
&lt;td&gt;JWT &lt;code&gt;HS256&lt;/code&gt; signature segment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A digest that &amp;ldquo;looks wrong&amp;rdquo; but is the right length in a different alphabet is an encoding bug, not a secret mismatch. For the encoding mechanics themselves see the
; for the JWT variant, the
.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="computing-hmac-in-the-browser"&gt;Computing HMAC in the Browser&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SubtleCrypto&lt;/code&gt; supports HMAC natively, so a signature can be produced or checked with no network exposure at all:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;hmacSha256Hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enc&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;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;raw&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;HMAC&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SHA-256&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sign&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;verify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HMAC&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encode&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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="nx"&gt;padStart&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="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For verification prefer &lt;code&gt;crypto.subtle.verify(&amp;quot;HMAC&amp;quot;, key, signatureBytes, data)&lt;/code&gt; — it compares internally instead of exposing you to a naive &lt;code&gt;===&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="operational-checklist"&gt;Operational Checklist&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Generate keys randomly.&lt;/strong&gt; 32 bytes from &lt;code&gt;crypto.getRandomValues&lt;/code&gt; or &lt;code&gt;openssl rand -hex 32&lt;/code&gt; — never a passphrase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sign the exact bytes on the wire.&lt;/strong&gt; No re-serialisation, trimming, or charset conversion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compare in constant time.&lt;/strong&gt; &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt;, Python&amp;rsquo;s &lt;code&gt;hmac.compare_digest&lt;/code&gt;, PHP&amp;rsquo;s &lt;code&gt;hash_equals&lt;/code&gt;, or &lt;code&gt;subtle.verify&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Include a timestamp and reject stale requests.&lt;/strong&gt; HMAC proves authenticity, not freshness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support two active secrets during rotation.&lt;/strong&gt; Accept either tag while migrating, then retire the old key — this is exactly why Stripe can send one &lt;code&gt;v1&lt;/code&gt; signature per active secret.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never log the secret or put it in a URL.&lt;/strong&gt; Query strings land in access logs, proxies, and browser history.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="choosing-the-hash"&gt;Choosing the Hash&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Digest&lt;/th&gt;
&lt;th&gt;Verdict for new work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HMAC-SHA256&lt;/td&gt;
&lt;td&gt;32 bytes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Default.&lt;/strong&gt; Universal library support, ample margin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HMAC-SHA512&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;Fine; faster than SHA-256 on 64-bit CPUs for large payloads.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HMAC-SHA384&lt;/td&gt;
&lt;td&gt;48 bytes&lt;/td&gt;
&lt;td&gt;Use when a compliance profile requires it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HMAC-SHA1&lt;/td&gt;
&lt;td&gt;20 bytes&lt;/td&gt;
&lt;td&gt;Legacy interop only (older GitHub &lt;code&gt;X-Hub-Signature&lt;/code&gt;, TOTP). Not broken inside HMAC, but do not choose it for new protocols.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;HMAC-SHA1 remains safe &lt;em&gt;as an authenticator&lt;/em&gt; because HMAC does not depend on collision resistance — the same reason
. Even so, auditors flag it, so new designs should start at SHA-256. For plain (unkeyed) integrity digests, see the
.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-by-step-debugging-a-failing-signature-with-toolbox"&gt;Step-by-Step: Debugging a Failing Signature with Toolbox&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Capture the raw body&lt;/strong&gt; from your server logs or the provider&amp;rsquo;s delivery inspector — copy the exact bytes, not a pretty-printed version.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open the tool&lt;/strong&gt;: visit the
.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paste the payload and the signing secret&lt;/strong&gt;, and select &lt;strong&gt;SHA-256&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For Stripe, prepend the timestamp&lt;/strong&gt;: enter &lt;code&gt;1492774577.{&amp;quot;id&amp;quot;:&amp;quot;evt_…&amp;quot;}&lt;/code&gt; — timestamp, dot, raw body — matching the &lt;code&gt;signed_payload&lt;/code&gt; construction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compare digests&lt;/strong&gt;: place the provider&amp;rsquo;s header value next to the computed digest. A hex/Base64 mismatch means an encoding bug; a completely different digest means the body was mutated or the wrong secret is in use.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Outcome:&lt;/strong&gt; you localise the fault to one of three causes — wrong secret, mutated body, or wrong signed-payload construction — in a couple of minutes, without redeploying to add log statements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Related guides:&lt;/strong&gt;
·
·
&lt;/p&gt;
&lt;section class="faqs-container not-prose my-12 border-t-2 border-stone-200 dark:border-stone-800 pt-8" aria-label="Frequently Asked Questions"&gt;
&lt;div class="flex items-center gap-2 mb-6"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2 py-0.5 rounded text-[10px] font-black tracking-wider"&gt;FAQ&lt;/span&gt;
&lt;h2 class="text-2xl font-extrabold text-[#2c221a] dark:text-[#ede6db] tracking-tight m-0"&gt;
Frequently Asked Questions
&lt;/h2&gt;
&lt;/div&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;Why is HMAC used instead of simply hashing the secret and the message together?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
SHA-1 and SHA-2 are Merkle-Damgard constructions vulnerable to length extension: given H(secret || message) and the input length, an attacker can produce a valid digest for message || padding || attacker_data without knowing the secret. HMAC&amp;#39;s nested form, H((K XOR opad) || H((K XOR ipad) || message)), prevents that, which is why RFC 2104 and FIPS PUB 198-1 specify it.
&lt;/p&gt;
&lt;/details&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;Why does webhook signature verification fail when my framework parses JSON automatically?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
The digest covers the exact bytes the provider sent. Body parsers re-serialise JSON and can reorder keys or change whitespace, number, and Unicode formatting, producing a different byte string and digest. Capture the raw body before parsing middleware runs — in Express, mount the webhook route before express.json() or use express.raw() on that route.
&lt;/p&gt;
&lt;/details&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;What is the difference between HMAC and a digital signature?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
HMAC is symmetric: the same secret creates and verifies the tag, so any verifier can also forge. It proves the sender holds the shared secret. RSA or ECDSA signatures are asymmetric — the private key signs, the public key only verifies — adding non-repudiation. Webhooks use HMAC because a shared secret already exists and HMAC is far faster.
&lt;/p&gt;
&lt;/details&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;Do I still need a timestamp check if the signature is valid?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
Yes. A valid signature proves authenticity and integrity, not freshness. Without a signed timestamp an intercepted request can be replayed indefinitely. Stripe signs a timestamp and its libraries default to a five-minute tolerance; where no timestamp is signed, deduplicate on the delivery or event ID.
&lt;/p&gt;
&lt;/details&gt;
&lt;/section&gt;</description></item><item><title>Cron Syntax &amp; Expression Architecture: The Complete Guide to Scheduling Jobs</title><link>https://blog.toolbox.vishnudigital.com/cron-syntax-expression-architecture-guide/</link><pubDate>Sat, 05 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.toolbox.vishnudigital.com/cron-syntax-expression-architecture-guide/</guid><description>
&lt;aside class="comic-bubble not-prose my-8 p-6 rounded-2xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#fdfbf7] dark:bg-[#28201a] shadow-[4px_4px_0px_#2c221a] dark:shadow-[4px_4px_0px_#14100d]" aria-label="AEO Quick Answer &amp; Specification Dossier"&gt;
&lt;div class="flex items-center justify-between gap-3 mb-3"&gt;
&lt;div class="flex items-center gap-2"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2.5 py-0.5 rounded text-[11px] font-black tracking-wider"&gt;QUICK ANSWER&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold text-[#2c221a]/70 dark:text-[#ede6db]/70"&gt;RFC SPECIFICATION SUMMARY&lt;/span&gt;
&lt;/div&gt;
&lt;span class="hidden sm:inline-flex items-center gap-1.5 text-[11px] font-bold font-mono px-2 py-0.5 rounded border border-emerald-700 dark:border-emerald-500 bg-emerald-100 dark:bg-emerald-950 text-emerald-900 dark:text-emerald-200"&gt;
&lt;span class="inline-block w-1.5 h-1.5 rounded-full bg-emerald-600 dark:bg-emerald-400"&gt;&lt;/span&gt; 100% Client-Side
&lt;/span&gt;
&lt;/div&gt;
&lt;p class="text-base sm:text-lg text-[#2c221a] dark:text-[#ede6db] leading-relaxed font-semibold mb-4"&gt;
A standard UNIX cron expression consists of 5 whitespace-separated fields: Minute (0-59), Hour (0-23), Day of Month (1-31), Month (1-12), and Day of Week (0-6). Special operators include * (every value), , (list), - (range), and / (step increments). To avoid Daylight Saving Time (DST) execution duplication or skips, production cron schedules should always be anchored to UTC.
&lt;/p&gt;
&lt;div class="flex flex-wrap gap-2 text-xs font-mono font-bold mt-4 pt-3 border-t border-[#ded4c3] dark:border-[#3b332c]"&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Difficulty: Intermediate&lt;/span&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Time: 6 Mins&lt;/span&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Privacy: 100% Client-Side RAM&lt;/span&gt;
&lt;span class="px-2.5 py-1 rounded bg-[#ede6db] dark:bg-[#382e26] text-[#2c221a] dark:text-[#ede6db] border border-[#ded4c3] dark:border-[#4a3e35]"&gt;Standard: IEEE Std 1003.1 (POSIX)&lt;/span&gt;
&lt;/div&gt;
&lt;/aside&gt;
&lt;div class="tool-hero-cta not-prose my-10 p-6 sm:p-7 rounded-2xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#fdfbf7] dark:bg-[#28201a] shadow-[4px_4px_0px_#2c221a] dark:shadow-[4px_4px_0px_#14100d] flex flex-col md:flex-row items-start md:items-center justify-between gap-6 transition-all" role="complementary" aria-label="Interactive Companion Tool"&gt;
&lt;div class="space-y-2 max-w-xl"&gt;
&lt;div class="flex items-center gap-2"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2.5 py-0.5 rounded text-[10px] font-black tracking-wider uppercase"&gt;
Free In-Browser Tool
&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold text-stone-500 dark:text-stone-400"&gt;TOOLBOX UTILITY&lt;/span&gt;
&lt;/div&gt;
&lt;h3 class="text-xl font-extrabold text-[#2c221a] dark:text-[#ede6db] tracking-tight m-0"&gt;
Cron Schedule Builder
&lt;/h3&gt;
&lt;p class="text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium m-0"&gt;
Build, parse, and validate cron schedules with instant human-readable English descriptions and live execution timeline preview.
&lt;/p&gt;
&lt;/div&gt;
&lt;div class="shrink-0 w-full md:w-auto"&gt;
&lt;a href="https://toolbox.vishnudigital.com/cron-generator" class="comic-btn-primary w-full md:w-auto text-center" &gt;
Open Cron Builder &amp;amp; Schedule Tester →
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="what-is-cron-and-why-does-syntax-precision-matter"&gt;What Is Cron and Why Does Syntax Precision Matter?&lt;/h2&gt;
&lt;p&gt;Derived from the Greek word &lt;em&gt;Chronos&lt;/em&gt; (time), &lt;strong&gt;cron&lt;/strong&gt; is the universal time-based job scheduler found in Unix-like operating systems, cloud task runners (AWS EventBridge, Google Cloud Scheduler), and backend background queues (Celery, BullMQ, Sidekiq).&lt;/p&gt;
&lt;p&gt;A misconfigured cron expression in production can lead to severe consequences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Database backup scripts running every &lt;strong&gt;minute&lt;/strong&gt; instead of every &lt;strong&gt;night at midnight&lt;/strong&gt;, causing CPU exhaustion.&lt;/li&gt;
&lt;li&gt;Billing webhooks executing &lt;strong&gt;twice&lt;/strong&gt; during a Daylight Saving Time autumn rollback.&lt;/li&gt;
&lt;li&gt;Data retention cleanups failing to execute because of contradictory day-of-month and day-of-week constraints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Understanding the underlying grammar of cron expressions enables engineers to write deterministic, resilient background job schedules.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-5-field-unix-cron-anatomy"&gt;The 5-Field UNIX Cron Anatomy&lt;/h2&gt;
&lt;p&gt;Standard POSIX / Vixie cron expressions are composed of five distinct fields separated by white space:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌───────────── Minute (0 - 59)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ┌───────────── Hour (0 - 23, 24-hour clock)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ ┌───────────── Day of the Month (1 - 31)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ ┌───────────── Month of the Year (1 - 12 or JAN - DEC)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ │ ┌───────────── Day of the Week (0 - 6 or SUN - SAT)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; * * * * *
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="detailed-field-range-specifications"&gt;Detailed Field Range Specifications&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field Position&lt;/th&gt;
&lt;th&gt;Field Name&lt;/th&gt;
&lt;th&gt;Allowed Values&lt;/th&gt;
&lt;th&gt;Allowed Special Characters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Minute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 - 59&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt; &lt;code&gt;,&lt;/code&gt; &lt;code&gt;-&lt;/code&gt; &lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Hour&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 - 23&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt; &lt;code&gt;,&lt;/code&gt; &lt;code&gt;-&lt;/code&gt; &lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Day of Month&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1 - 31&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt; &lt;code&gt;,&lt;/code&gt; &lt;code&gt;-&lt;/code&gt; &lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Month&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1 - 12&lt;/code&gt; or &lt;code&gt;JAN - DEC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt; &lt;code&gt;,&lt;/code&gt; &lt;code&gt;-&lt;/code&gt; &lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Day of Week&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 - 6&lt;/code&gt; (0 = Sunday, 6 = Saturday)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt; &lt;code&gt;,&lt;/code&gt; &lt;code&gt;-&lt;/code&gt; &lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;&lt;strong&gt;Note on Sunday representation:&lt;/strong&gt; In many modern implementations (including Vixie Cron, crontab on Ubuntu, and macOS), both &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;7&lt;/code&gt; are accepted as Sunday for usability.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="special-operators-explained"&gt;Special Operators Explained&lt;/h2&gt;
&lt;p&gt;Cron expressions derive their expressive power from five core syntax operators:&lt;/p&gt;
&lt;h3 id="1-asterisk---the-wildcard"&gt;1. Asterisk (&lt;code&gt;*&lt;/code&gt;) — The Wildcard&lt;/h3&gt;
&lt;p&gt;Matches every valid unit within the field&amp;rsquo;s permissible range.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;* * * * *&lt;/code&gt;: Runs at the beginning of every minute of every hour, day, and month.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 * * * *&lt;/code&gt;: Runs at minute 0 of every hour (i.e. once every hour on the hour).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-comma---value-lists"&gt;2. Comma (&lt;code&gt;,&lt;/code&gt;) — Value Lists&lt;/h3&gt;
&lt;p&gt;Defines an explicit enumeration of discrete values.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0 9,12,18 * * *&lt;/code&gt;: Triggers at 9:00 AM, 12:00 PM, and 6:00 PM every day.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 0 * * 1,3,5&lt;/code&gt;: Runs at midnight on Mondays, Wednesdays, and Fridays.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-hyphen----value-ranges"&gt;3. Hyphen (&lt;code&gt;-&lt;/code&gt;) — Value Ranges&lt;/h3&gt;
&lt;p&gt;Specifies a continuous, inclusive interval between two bounds.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0 9-17 * * *&lt;/code&gt;: Runs at minute 0 of every hour between 9:00 AM and 5:00 PM inclusive.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 0 1-7 * *&lt;/code&gt;: Runs at midnight on the first seven days of every month.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-slash---step-values"&gt;4. Slash (&lt;code&gt;/&lt;/code&gt;) — Step Values&lt;/h3&gt;
&lt;p&gt;Specifies step increments through a range. When preceded by an asterisk (&lt;code&gt;*/n&lt;/code&gt;), it means &amp;ldquo;every &lt;em&gt;n&lt;/em&gt; units starting from the minimum bound&amp;rdquo;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;*/15 * * * *&lt;/code&gt;: Runs at minutes 0, 15, 30, and 45 of every hour.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 0/2 * * *&lt;/code&gt;: Runs every 2 hours (00:00, 02:00, 04:00, 06:00, &amp;hellip;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;10-50/10 * * * *&lt;/code&gt;: Runs between minutes 10 and 50 in 10-minute steps (10, 20, 30, 40, 50).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div class="in-article-ad-wrapper not-prose my-8 p-3 sm:p-4 rounded-xl border-2 border-stone-900/40 dark:border-stone-400/40 bg-[#fdfbf7] dark:bg-[#211c18] shadow-[3px_3px_0px_rgba(41,30,20,0.3)] dark:shadow-[3px_3px_0px_rgba(242,237,228,0.2)] overflow-hidden"&gt;
&lt;div class="flex items-center justify-between mb-2 border-b border-stone-200 dark:border-stone-800 pb-1"&gt;
&lt;span class="text-[10px] font-mono font-bold uppercase tracking-widest text-stone-500 dark:text-stone-400"&gt;Advertisement&lt;/span&gt;
&lt;span class="text-[9px] font-mono uppercase px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-950 text-amber-800 dark:text-amber-300"&gt;Sponsored&lt;/span&gt;
&lt;/div&gt;
&lt;div class="w-full flex justify-center items-center overflow-hidden min-h-[250px]"&gt;
&lt;ins class="adsbygoogle"
style="display:block; text-align:center; min-height:250px; width:100%;"
data-ad-layout="in-article"
data-ad-format="fluid"
data-ad-client="ca-pub-5474548764059850"
&gt;&lt;/ins&gt;
&lt;script&gt;
(adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="5-field-standard-vs-6-field-extended-quartz--aws"&gt;5-Field Standard vs 6-Field Extended (Quartz / AWS)&lt;/h2&gt;
&lt;p&gt;While POSIX systems use the 5-field format, several popular enterprise engines (Java Quartz Scheduler, AWS CloudWatch / EventBridge, Spring Framework &lt;code&gt;@Scheduled&lt;/code&gt;) utilize 6-field or 7-field formats:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Architecture Dimension&lt;/th&gt;
&lt;th&gt;5-Field UNIX (POSIX / crontab)&lt;/th&gt;
&lt;th&gt;6-Field Quartz / AWS EventBridge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Seconds Precision&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ No (Minute is lowest granularity)&lt;/td&gt;
&lt;td&gt;✅ Yes (Field 1 is Seconds &lt;code&gt;0-59&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Year Specification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ Supported as optional 7th field (&lt;code&gt;1970-2099&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Question Mark (&lt;code&gt;?&lt;/code&gt;) Operator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Throws syntax error&lt;/td&gt;
&lt;td&gt;✅ Mandatory when specifying Day of Month or Week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;L&lt;/code&gt; (Last) and &lt;code&gt;W&lt;/code&gt; (Weekday)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Rarely supported&lt;/td&gt;
&lt;td&gt;✅ Fully supported (&lt;code&gt;L&lt;/code&gt; = last day, &lt;code&gt;15W&lt;/code&gt; = nearest weekday)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Platforms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Linux &lt;code&gt;crontab&lt;/code&gt;, Node.js &lt;code&gt;node-cron&lt;/code&gt;, Python &lt;code&gt;Celery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AWS EventBridge, Spring Boot, Quartz Java, Kubernetes CronJobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="example-quartz--aws-syntax"&gt;Example: Quartz / AWS Syntax&lt;/h3&gt;
&lt;p&gt;In AWS EventBridge, a schedule running at 10:15 AM every weekday requires 6 fields and the &lt;code&gt;?&lt;/code&gt; character:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cron(0 15 10 ? * MON-FRI *)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="10-common-production-cron-recipes"&gt;10 Common Production Cron Recipes&lt;/h2&gt;
&lt;p&gt;Here are production-tested recipes ready to deploy for common infrastructure patterns:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operational Objective&lt;/th&gt;
&lt;th&gt;Standard Cron Expression&lt;/th&gt;
&lt;th&gt;English Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Health Check Heartbeat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*/5 * * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every 5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hourly Log Rotation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 * * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once an hour on the hour (:00)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nightly Database Backup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 2 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every day at 02:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weekday Business Hours Task&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 9 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Monday through Friday at 09:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weekly Maintenance Window&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 3 * * 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every Sunday at 03:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly Invoice Generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 1 * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1st of every month at midnight (00:00)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quarterly Audit Report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 1 1,4,7,10 *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1st of Jan, Apr, Jul, Oct at midnight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twice-Daily Cache Warmup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 6,18 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every day at 06:00 AM and 06:00 PM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;End of Month Reconciliation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 23 28-31 * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nightly from 28th-31st (pair with bash check)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Yearly Archival&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 1 1 *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;January 1st at midnight&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="critical-pitfalls--production-best-practices"&gt;Critical Pitfalls &amp;amp; Production Best Practices&lt;/h2&gt;
&lt;h3 id="1-the-daylight-saving-time-dst-trap"&gt;1. The Daylight Saving Time (DST) Trap&lt;/h3&gt;
&lt;p&gt;When a server is configured to a local timezone observing Daylight Saving Time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spring Forward (+1 hr):&lt;/strong&gt; At 2:00 AM, the clock jumps directly to 3:00 AM. Any job scheduled for &lt;code&gt;0 2 * * *&lt;/code&gt; is completely skipped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fall Back (-1 hr):&lt;/strong&gt; At 2:00 AM, the clock rewinds to 1:00 AM. Any job scheduled between 1:00 AM and 2:00 AM runs &lt;strong&gt;twice&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Always set the system timezone of worker servers and container clusters to &lt;code&gt;UTC&lt;/code&gt; (&lt;code&gt;tzdata: Etc/UTC&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id="2-the-thundering-herd-problem"&gt;2. The &amp;ldquo;Thundering Herd&amp;rdquo; Problem&lt;/h3&gt;
&lt;p&gt;If 50 different microservices schedule heavy batch jobs at exactly &lt;code&gt;0 0 * * *&lt;/code&gt; (midnight), database IOPS and CPU utilization will spike dramatically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Stagger cron schedules with jitter. Run service A at &lt;code&gt;17 2 * * *&lt;/code&gt;, service B at &lt;code&gt;34 2 * * *&lt;/code&gt;, and service C at &lt;code&gt;49 2 * * *&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="3-day-of-month-and-day-of-week-interaction"&gt;3. Day of Month AND Day of Week Interaction&lt;/h3&gt;
&lt;p&gt;In POSIX cron, if you specify both:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0 0 15 * 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The job does &lt;strong&gt;not&lt;/strong&gt; execute only when the 15th happens to be a Monday. It executes whenever the day is the 15th &lt;strong&gt;OR&lt;/strong&gt; whenever the day is a Monday.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-by-step-generating-cron-schedules-with-toolbox"&gt;Step-by-Step: Generating Cron Schedules with Toolbox&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open the Tool&lt;/strong&gt;: Visit the
.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select Frequency&lt;/strong&gt;: Use the interactive visual controls to choose between Minute, Hourly, Daily, Weekly, Monthly, or Custom intervals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inspect Real-Time Translation&lt;/strong&gt;: The engine instantly renders a plain-English translation of your schedule (e.g. &lt;em&gt;&amp;ldquo;At 02:30 AM on day 1 of the month&amp;rdquo;&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preview Next 5 Execution Dates&lt;/strong&gt;: Verify the exact upcoming UTC and local timestamps to ensure no scheduling surprises occur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Copy Expression&lt;/strong&gt;: Click &lt;strong&gt;Copy Expression&lt;/strong&gt; to paste the POSIX-compliant string directly into your &lt;code&gt;crontab&lt;/code&gt;, Dockerfile, or infrastructure code.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="tool-embed-container comic-panel not-prose my-10 rounded-2xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#fdfbf7] dark:bg-[#28201a] shadow-[4px_4px_0px_#2c221a] dark:shadow-[4px_4px_0px_#14100d] overflow-hidden" id="embed-cron-generator-1788851897565404000-box" role="region" aria-label="Interactive Tool Workbench"&gt;
&lt;div class="comic-halftone p-4 sm:p-5 border-b-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#f5efe4] dark:bg-[#211a14] flex flex-wrap items-center justify-between gap-3"&gt;
&lt;div class="flex items-center gap-2.5 flex-wrap min-w-0"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2.5 py-0.5 rounded text-[10px] font-black tracking-wider uppercase"&gt;
Interactive Workbench
&lt;/span&gt;
&lt;span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-mono font-bold bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300 border border-emerald-300 dark:border-emerald-800 shrink-0"&gt;
&lt;span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"&gt;&lt;/span&gt;
&lt;span&gt;LIVE&lt;/span&gt;
&lt;/span&gt;
&lt;h3 class="text-base sm:text-lg font-extrabold text-[#2c221a] dark:text-[#ede6db] tracking-tight truncate m-0"&gt;
Cron Syntax &amp;amp; Expression Architecture: The Complete Guide to Scheduling Jobs
&lt;/h3&gt;
&lt;/div&gt;
&lt;div class="flex items-center gap-2 shrink-0"&gt;
&lt;a href="https://toolbox.vishnudigital.com/cron-generator" target="_blank" rel="noopener noreferrer" class="comic-btn-secondary !py-1 !px-3 !text-xs font-mono font-bold flex items-center gap-1.5 shadow-[2px_2px_0px_#2c221a] dark:shadow-[2px_2px_0px_#14100d] hover:translate-x-[-1px] hover:translate-y-[-1px] transition-all" title="Open full utility in new window"&gt;
&lt;span&gt;Open Fullscreen ↗&lt;/span&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="px-5 py-2.5 bg-[#fdfbf7] dark:bg-[#28201a] border-b border-[#ded4c3] dark:border-[#3b332c] text-xs font-medium text-[#2c221a]/80 dark:text-[#ede6db]/80"&gt;
Build, parse, and validate cron schedules with instant human-readable English descriptions and live execution timeline preview.
&lt;/div&gt;
&lt;div class="relative w-full overflow-hidden bg-[#fdfbf7] dark:bg-[#1a1410]" style="min-height: 420px;"&gt;
&lt;div id="embed-cron-generator-1788851897565404000-skeleton" class="absolute inset-0 flex flex-col items-center justify-center p-8 bg-[#fdfbf7] dark:bg-[#1e1814] z-10 transition-opacity duration-300"&gt;
&lt;div class="flex items-center gap-3 p-4 rounded-xl border-2 border-[#2c221a] dark:border-[#ded4c3] bg-[#f5efe4] dark:bg-[#28201a] shadow-[2px_2px_0px_#2c221a] dark:shadow-[2px_2px_0px_#14100d] max-w-sm w-full"&gt;
&lt;div class="w-8 h-8 rounded-lg bg-amber-500/20 flex items-center justify-center shrink-0"&gt;
&lt;svg class="w-5 h-5 text-amber-600 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"&gt;
&lt;circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"&gt;&lt;/circle&gt;
&lt;path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"&gt;&lt;/path&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;div class="min-w-0 flex-1"&gt;
&lt;div class="text-xs font-bold text-[#2c221a] dark:text-[#ede6db] truncate"&gt;Initializing Workbench...&lt;/div&gt;
&lt;div class="text-[10px] font-mono text-[#2c221a]/60 dark:text-[#ede6db]/60"&gt;100% Client-Side RAM Sandbox&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;iframe
id="embed-cron-generator-1788851897565404000-frame"
data-base-src="https://toolbox.vishnudigital.com/cron-generator?embed=true"
src="about:blank"
title="Cron Syntax &amp;amp; Expression Architecture: The Complete Guide to Scheduling Jobs – Free Online Utility"
class="w-full border-0 block opacity-0 transition-opacity duration-300"
style="height: 580px; min-height: 420px;"
loading="lazy"
allow="clipboard-read; clipboard-write; web-share"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-downloads"&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;div class="p-3 sm:px-5 border-t border-[#ded4c3] dark:border-[#3b332c] bg-[#f5efe4]/80 dark:bg-[#211a14]/80 flex flex-wrap items-center justify-between gap-3 text-xs font-mono"&gt;
&lt;div class="flex items-center gap-2 text-[#2c221a]/70 dark:text-[#ede6db]/70"&gt;
&lt;span class="text-emerald-600 dark:text-emerald-400 font-bold"&gt;🔒 Private Execution:&lt;/span&gt;
&lt;span class="hidden sm:inline"&gt;Computations run in local browser memory. Zero server uploads.&lt;/span&gt;
&lt;span class="sm:hidden"&gt;Zero server uploads.&lt;/span&gt;
&lt;/div&gt;
&lt;div class="flex items-center gap-1.5 shrink-0"&gt;
&lt;a href="https://toolbox.vishnudigital.com/cron-generator" target="_blank" rel="noopener noreferrer" class="font-bold text-amber-700 dark:text-amber-400 hover:text-amber-800 dark:hover:text-amber-300 hover:underline"&gt;
Launch Fullscreen →
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
(function() {
const containerId = "embed-cron-generator-1788851897565404000";
const frame = document.getElementById(containerId + "-frame");
const skeleton = document.getElementById(containerId + "-skeleton");
if (!frame) return;
function getCurrentTheme() {
return document.documentElement.classList.contains("dark") ? "dark" : "light";
}
function resolveSrc() {
const baseSrc = frame.getAttribute("data-base-src");
if (!baseSrc) return "";
const theme = getCurrentTheme();
return baseSrc + (baseSrc.includes("?") ? "&amp;" : "?") + "theme=" + theme;
}
function loadIframe() {
if (frame.getAttribute("src") !== "about:blank") return;
const fullSrc = resolveSrc();
frame.src = fullSrc;
frame.onload = function() {
if (skeleton) {
skeleton.classList.add("opacity-0");
setTimeout(function() { skeleton.style.display = "none"; }, 300);
}
frame.classList.remove("opacity-0");
frame.classList.add("opacity-100");
syncThemeToIframe();
};
}
function syncThemeToIframe() {
if (!frame || !frame.contentWindow) return;
try {
frame.contentWindow.postMessage({
type: "TOOLBOX_THEME_CHANGE",
theme: getCurrentTheme()
}, "*");
} catch(e) {}
}
if ("IntersectionObserver" in window) {
const observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
loadIframe();
observer.disconnect();
}
});
}, { rootMargin: "250px" });
observer.observe(frame);
} else {
loadIframe();
}
window.addEventListener("message", function(event) {
if (
event.data &amp;&amp;
typeof event.data === "object" &amp;&amp;
event.data.type === "TOOLBOX_RESIZE" &amp;&amp;
event.data.height
) {
if (event.source === frame.contentWindow) {
const measured = Math.max(event.data.height + 20, 420);
frame.style.height = measured + "px";
}
}
});
const themeObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if (m.attributeName === "class") {
syncThemeToIframe();
}
});
});
themeObserver.observe(document.documentElement, { attributes: true });
document.querySelectorAll(".theme-toggle").forEach(function(btn) {
btn.addEventListener("click", function() {
setTimeout(syncThemeToIframe, 50);
});
});
})();
&lt;/script&gt;
&lt;section class="faqs-container not-prose my-12 border-t-2 border-stone-200 dark:border-stone-800 pt-8" aria-label="Frequently Asked Questions"&gt;
&lt;div class="flex items-center gap-2 mb-6"&gt;
&lt;span class="comic-stamp bg-amber-600 text-white px-2 py-0.5 rounded text-[10px] font-black tracking-wider"&gt;FAQ&lt;/span&gt;
&lt;h2 class="text-2xl font-extrabold text-[#2c221a] dark:text-[#ede6db] tracking-tight m-0"&gt;
Frequently Asked Questions
&lt;/h2&gt;
&lt;/div&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;What is the standard order of fields in a 5-field cron expression?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
The 5 standard UNIX cron fields from left to right are: 1. Minute (0-59), 2. Hour (0-23), 3. Day of the Month (1-31), 4. Month of the Year (1-12 or JAN-DEC), and 5. Day of the Week (0-6 or SUN-SAT, where 0 and 7 usually represent Sunday).
&lt;/p&gt;
&lt;/details&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;What is the difference between * and */5 in cron syntax?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
An asterisk (*) matches every possible value in that field (e.g. * in the minute field means run every minute). A step value with a slash (*/5) matches every nth interval starting from the field&amp;#39;s minimum (e.g. */5 in the minute field triggers at minutes 0, 5, 10, 15, ..., 55).
&lt;/p&gt;
&lt;/details&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;How does Daylight Saving Time (DST) affect scheduled cron jobs?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
If your server timezone observes DST, schedules running during the transition hour can either be skipped entirely (when clocks spring forward by 1 hour) or execute twice (when clocks fall back by 1 hour). For mission-critical background jobs and batch billing, servers and cron schedulers should always be configured to UTC.
&lt;/p&gt;
&lt;/details&gt;
&lt;details name="faq" class="group border-2 border-[#2c221a] dark:border-[#ded4c3] rounded-xl p-4 bg-[#fdfbf7] dark:bg-[#28201a] shadow-[3px_3px_0px_#2c221a] dark:shadow-[3px_3px_0px_#14100d] mb-3 transition-all"&gt;
&lt;summary class="flex items-center justify-between cursor-pointer font-bold text-[#2c221a] dark:text-[#ede6db] select-none text-base"&gt;
&lt;span&gt;Can I specify both Day of Month and Day of Week simultaneously?&lt;/span&gt;
&lt;span class="text-xs font-mono font-bold transition-transform duration-200 group-open:rotate-180 shrink-0 ml-2"&gt;▼&lt;/span&gt;
&lt;/summary&gt;
&lt;p class="mt-3 text-sm text-stone-600 dark:text-stone-300 leading-relaxed font-medium mb-0"&gt;
In standard UNIX cron (Vixie Cron), specifying non-wildcard values in both the Day of Month and Day of Week fields creates an OR condition rather than an AND condition. The job triggers whenever EITHER condition matches. In Quartz/AWS cron, one of these fields must be set to &amp;#39;?&amp;#39; to avoid ambiguous scheduling.
&lt;/p&gt;
&lt;/details&gt;
&lt;/section&gt;</description></item></channel></rss>