File tree Expand file tree Collapse file tree 3 files changed +104
-0
lines changed
Integration/Scripted REST Api/Webhook receiver with HMAC SHA-256 validation Expand file tree Collapse file tree 3 files changed +104
-0
lines changed Original file line number Diff line number Diff line change 1+ // Script Include: HmacUtils
2+ // Purpose: Compute HMAC SHA-256 and constant-time compare.
3+
4+ var HmacUtils = Class . create ( ) ;
5+ HmacUtils . prototype = {
6+ initialize : function ( ) { } ,
7+
8+ hmacSha256Hex : function ( secret , message ) {
9+ var mac = Packages . javax . crypto . Mac . getInstance ( 'HmacSHA256' ) ;
10+ var key = new Packages . javax . crypto . spec . SecretKeySpec (
11+ new Packages . java . lang . String ( secret ) . getBytes ( 'UTF-8' ) ,
12+ 'HmacSHA256'
13+ ) ;
14+ mac . init ( key ) ;
15+ var raw = mac . doFinal ( new Packages . java . lang . String ( message ) . getBytes ( 'UTF-8' ) ) ;
16+
17+ var sb = new Packages . java . lang . StringBuilder ( ) ;
18+ for ( var i = 0 ; i < raw . length ; i ++ ) {
19+ var hex = Packages . java . lang . Integer . toHexString ( ( raw [ i ] & 0xff ) | 0x100 ) . substring ( 1 ) ;
20+ sb . append ( hex ) ;
21+ }
22+ return sb . toString ( ) ;
23+ } ,
24+
25+ constantTimeEquals : function ( a , b ) {
26+ var A = String ( a || '' ) ;
27+ var B = String ( b || '' ) ;
28+ if ( A . length !== B . length ) return false ;
29+ var diff = 0 ;
30+ for ( var i = 0 ; i < A . length ; i ++ ) diff |= A . charCodeAt ( i ) ^ B . charCodeAt ( i ) ;
31+ return diff === 0 ;
32+ } ,
33+
34+ type : 'HmacUtils'
35+ } ;
Original file line number Diff line number Diff line change 1+ # Webhook receiver with HMAC SHA-256 validation
2+
3+ ## What this solves
4+ Inbound webhooks should be verified to ensure the payload really came from the sender. This receiver validates an ` X-Signature ` header containing an HMAC SHA-256 of the request body using a shared secret. Invalid signatures return HTTP 401.
5+
6+ ## Where to use
7+ - Scripted REST API resource script
8+ - Include the ` HmacUtils ` Script Include in the same app or global
9+
10+ ## How it works
11+ - Reads raw request body and the ` X-Signature ` header
12+ - Computes HMAC SHA-256 using the shared secret
13+ - Compares in constant time to avoid timing attacks
14+ - If valid, inserts the payload into a target table or queues it for processing
15+
16+ ## Configure
17+ - Set ` SHARED_SECRET ` (prefer credentials or encrypted properties)
18+ - Update ` TARGET_TABLE ` for successful inserts
19+
20+ ## References
21+ - Scripted REST APIs
22+ https://www.servicenow.com/docs/bundle/zurich-application-development/page/build/applications/task/create-scripted-rest-api.html
23+ - REST API request/response objects
24+ https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideHTTPRequest/concept/c_scripted-rest-api-request.html
25+ - Java crypto (used server-side)
26+ https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/Script/server_apis/concept/java-use.html
Original file line number Diff line number Diff line change 1+ // Scripted REST API Resource Script: Webhook receiver with HMAC validation
2+ ( function process ( /*RESTAPIRequest*/ request , /*RESTAPIResponse*/ response ) {
3+ var SHARED_SECRET = gs . getProperty ( 'x_acme.webhook.secret' , '' ) ;
4+ var TARGET_TABLE = 'x_acme_inbound_webhook' ; // replace with your table
5+
6+ try {
7+ var body = request . body && request . body . data ? request . body . data : '' ;
8+ var signature = request . getHeader ( 'X-Signature' ) || '' ; // hex HMAC hash
9+
10+ if ( ! SHARED_SECRET ) {
11+ response . setStatus ( 500 ) ;
12+ response . setBody ( { error : 'Server not configured' } ) ;
13+ return ;
14+ }
15+ if ( ! signature || ! body ) {
16+ response . setStatus ( 400 ) ;
17+ response . setBody ( { error : 'Missing signature or body' } ) ;
18+ return ;
19+ }
20+
21+ var util = new HmacUtils ( ) ;
22+ var expected = util . hmacSha256Hex ( SHARED_SECRET , body ) ;
23+
24+ if ( ! util . constantTimeEquals ( expected , signature ) ) {
25+ response . setStatus ( 401 ) ;
26+ response . setBody ( { error : 'Invalid signature' } ) ;
27+ return ;
28+ }
29+
30+ // Valid payload: insert a record for processing
31+ var rec = new GlideRecord ( TARGET_TABLE ) ;
32+ rec . initialize ( ) ;
33+ rec . payload = body ;
34+ rec . signature = signature ;
35+ rec . insert ( ) ;
36+
37+ response . setStatus ( 200 ) ;
38+ response . setBody ( { ok : true } ) ;
39+ } catch ( e ) {
40+ response . setStatus ( 500 ) ;
41+ response . setBody ( { error : String ( e ) } ) ;
42+ }
43+ } ) ( request , response ) ;
You can’t perform that action at this time.
0 commit comments