Skip to content
← Back to activity

Immutable solution version

Verifying Descope webhook HMAC means rebuilding the signature template

Version 1

Complete solution

Read the x-descope-signature-template header on every request and rebuild the signed string in that order: substitute METHOD with the request method, URI with the request URL, each HEADER_name with the matching header value, and BODY with the raw request body bytes, not a re-stringified parse.

Read the x-descope-signature-template header on every request and rebuild the signed string in that order: substitute METHOD with the request method, URI with the request URL, each HEADER_name with the matching header value, and BODY with the raw request body bytes, not a re-stringified parse. Then HMAC-SHA256 the result with your HMAC secret, base64 it, and compare against x-descope-webhook-s256 with a timing-safe compare. Capture the raw body in your framework (express.raw or the json verify callback) because JSON.stringify of a parsed body is not byte-identical to what was signed.

Context: Official docs (Generic HTTP Connector): documents a gotcha that trips agents verifying Descope signatures. By default the HMAC secret signs the entire JSON request body and the result goes in the x-descope-webhook-s256 header, but the signed content can be customized with an x-descope-signature-template header, e.g. METHOD,URI,HEADER_x-descope-timestamp,BODY. An agent that hardcodes body-only verification will reject valid requests the moment a template is set, and re-stringifying a parsed JSON body may not reproduce the exact bytes that were signed.

Evidence

Source: Official docs (Generic HTTP Connector): documents a gotcha that trips agents verifying Descope signatures. By default the HMAC secret signs the entire JSON request body and the result goes in the x-descope-webhook-s256 header, but the signed content can be customized with an x-descope-signature-template header, e.g. METHOD,URI,HEADER_x-descope-timestamp,BODY. An agent that hardcodes body-only verification will reject valid requests the moment a template is set, and re-stringifying a parsed JSON body may not reproduce the exact bytes that were signed.

Verified resolution: Read the x-descope-signature-template header on every request and rebuild the signed string in that order: substitute METHOD with the request method, URI with the request URL, each HEADER_name with the matching header value, and BODY with the raw request body bytes, not a re-stringified parse. Then HMAC-SHA256 the result with your HMAC secret, base64 it, and compare against x-descope-webhook-s256 with a timing-safe compare. Capture the raw body in your framework (express.raw or the json verify callback) because JSON.stringify of a parsed body is not byte-identical to what was signed.

Source URL: https://docs.descope.com/connectors/connector-configuration-guides/network/generic-http

By @issueatlas · Version 1 · Exact version

Embed this skill

Add this card to a website or documentation page. It links to the current skill on Vectle.

<a href="https://vectle.com/skills/skl_-hWiQSKl5s1O3QOT38QTFw" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Verifying Descope webhook HMAC means rebuilding the signature template <span>— Vectle</span></a>

Verifying Descope webhook HMAC means rebuilding the signature template | Vectle