Title: Verify an HMAC webhook signature in Java
Open Graph Title: Verify an HMAC webhook signature in Java
X Title: Verify an HMAC webhook signature in Java
Description: Verify HMAC-SHA256 webhook signatures in Java: raw body, v1= list, constant-time compare, 5-minute tolerance. Tested code.
Open Graph Description: Verify HMAC-SHA256 webhook signatures in Java: raw body, v1= list, constant-time compare, 5-minute tolerance. Tested code.
X Description: Verify HMAC-SHA256 webhook signatures in Java: raw body, v1= list, constant-time compare, 5-minute tolerance. Tested code.
Mail addresses
contact@urlpipe.dev
Opengraph URL: https://urlpipe.dev/code/java/verify-webhook
Domain: urlpipe.dev
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://urlpipe.dev/#organization","name":"URLpipe","url":"https://urlpipe.dev","logo":"https://urlpipe.dev/icon.png","sameAs":["https://github.com/URLpipe"],"founder":{"@type":"Person","name":"Roger Campos","url":"https://rogercampos.com"}},{"@type":"WebSite","@id":"https://urlpipe.dev/#website","url":"https://urlpipe.dev","name":"URLpipe","publisher":{"@id":"https://urlpipe.dev/#organization"}}]}
{"@context":"https://schema.org","@graph":[{"@type":"TechArticle","@id":"https://urlpipe.dev/code/java/verify-webhook#article","headline":"Verify an HMAC webhook signature in Java","description":"Verify HMAC-SHA256 webhook signatures in Java: raw body, v1= list, constant-time compare, 5-minute tolerance. Tested code.","url":"https://urlpipe.dev/code/java/verify-webhook","mainEntityOfPage":"https://urlpipe.dev/code/java/verify-webhook","datePublished":"2026-09-24","dateModified":"2026-09-24","author":{"@type":"Person","name":"Roger Campos","url":"https://rogercampos.com"},"publisher":{"@id":"https://urlpipe.dev/#organization"},"image":"https://urlpipe.dev/og-image.png"},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Why verify against the raw body?","acceptedAnswer":{"@type":"Answer","text":"The signature covers the exact bytes that were sent. Parsing the JSON and serializing it again changes key order and whitespace, and the HMAC with them. In Java: @RequestBody byte[] in Spring, getRequestBody() in HttpServer."}},{"@type":"Question","name":"Why can the signature header hold more than one value?","acceptedAnswer":{"@type":"Answer","text":"During a secret rotation every delivery is signed with the new and the old secret for 24 hours, so a receiver holding either one keeps verifying. Accept the delivery if any v1= value matches."}},{"@type":"Question","name":"What should a receiver answer when the check fails?","acceptedAnswer":{"@type":"Answer","text":"A 401. The delivery is retried with backoff, up to six attempts, and the result stays available from GET /result/:token for 30 days either way."}},{"@type":"Question","name":"Why a five-minute tolerance?","acceptedAnswer":{"@type":"Answer","text":"The timestamp is signed, so it cannot be changed; rejecting old ones stops a captured delivery being replayed later. Every retry is signed afresh, so genuine retries pass."}}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://urlpipe.dev/"},{"@type":"ListItem","position":2,"name":"Code recipes","item":"https://urlpipe.dev/code"},{"@type":"ListItem","position":3,"name":"Java","item":"https://urlpipe.dev/code/java"},{"@type":"ListItem","position":4,"name":"Verify a webhook signature in Java","item":"https://urlpipe.dev/code/java/verify-webhook"}]},{"@type":"HowTo","name":"Verify a webhook signature in Java","description":"To verify a URLpipe webhook in Java, compute HMAC-SHA256 over the X-URLpipe-Timestamp value, a dot and the raw request body, keyed with your whsec_ secret. Prefix it with v1= and compare it with MessageDigest.isEqual against each comma-separated value of X-URLpipe-Signature; reject timestamps more than five minutes off.","url":"https://urlpipe.dev/code/java/verify-webhook","tool":[{"@type":"HowToTool","name":"Java 17+"}],"step":[{"@type":"HowToStep","name":"Before you start","text":"Nothing to add: javax.crypto computes the HMAC and the JDK's own com.sun.net.httpserver serves the endpoint, so the receiver runs from source with no jar. Turn signing on under Settings → Webhook Signing and copy the secret (it starts with whsec_).","url":"https://urlpipe.dev/code/java/verify-webhook#setup"},{"@type":"HowToStep","name":"A receiver that verifies every delivery","text":"verify is the part to copy into your service. In Spring, take the body as @RequestBody byte[] so you verify the bytes that were signed. MessageDigest.isEqual is the constant-time compare; String.equals stops at the first difference and leaks where it was.","url":"https://urlpipe.dev/code/java/verify-webhook#receiver"}]}]}
| view-transition | same-origin |
| csrf-param | authenticity_token |
| csrf-token | aVES6_GW7-7C4sLMj44eSJ84G-TL_wtSTsPrtpdgGLE9XIXLdQSR5QRTY3FbkCtqQB7VlvF4V_phngPglvtr9w |
| csp-nonce | 6eb065f274a250b46bdf03807a4fe44b |
| og:type | website |
| og:site_name | URLpipe |
| og:locale | en_US |
| og:image | https://urlpipe.dev/og-image.png |
| og:image:width | 1200 |
| og:image:height | 630 |
| og:image:alt | URLpipe — URL in. Clean data out. |
| twitter:card | summary_large_image |
| twitter:image | https://urlpipe.dev/og-image.png |
| twitter:image:alt | URLpipe — URL in. Clean data out. |
| theme-color | #3b82f6 |
Links:
Viewport: width=device-width,initial-scale=1