Skip to main content

Verify Webhook Receipt Payload

When the Consent Manager sends a webhook notification, the payload includes a cryptographically signed receipt.
To ensure authenticity and integrity, you must verify:

  1. Signature (JWS verification)
  2. Payload integrity (hash validation)

๐Ÿ” Why Verification is Requiredโ€‹

Webhook payloads are signed to ensure:

  • The data is issued by Consent Manager
  • The payload is not tampered with
  • The consent receipt is authentic and trustworthy

๐Ÿ”„ Verification Flowโ€‹

  1. Fetch JWKS (public keys) from Consent Manager
  2. Extract kid (Key ID) from JWS header
  3. Find the matching public key
  4. Verify the JWS signature
  5. Validate payload hash

๐ŸŒ Step 1: Fetch Public Keys (JWKS)โ€‹

Consent Manager exposes public keys via JWKS endpoint:

  • Staging:
    https://staging.indiaconsent.com/.well-known/jwks.json
  • Production:
    https://www.indiaconsent.com/.well-known/jwks.json

These keys are used to verify webhook signatures.


๐Ÿงพ Step 2: Extract JWS from Webhookโ€‹

The webhook contains a JWS compact string, for example:

<HEADER>.<PAYLOAD>.<SIGNATURE>

You will use this to:

  • Extract header (to get kid)
  • Verify signature
  • Read payload

๐Ÿ” Step 3: Identify Correct Public Keyโ€‹

  • Parse the JWS header
  • Extract kid (Key ID)
  • Match it with the corresponding key from JWKS

โœ… Step 4: Verify JWS Signatureโ€‹

  • Ensure algorithm is RS256
  • Use the matching RSA public key
  • Verify the signature

If verification fails โ†’ Reject the webhook


๐Ÿ” Step 5: Validate Payload Integrityโ€‹

  • Compute SHA-256 hash of the JWS payload
  • Compare it with the receipt payload hash received separately

If mismatch โ†’ Reject the payload


Sample Codeโ€‹


import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.interfaces.RSAPublicKey;
import java.util.HexFormat;
import java.util.Optional;

public class JwsVerifyUsingJWKS {

public static void main(String[] args) throws Exception {

// 1๏ธโƒฃ Read JWKS JSON file
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://staging.indiaconsent.com/.well-known/jwks.json"))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String jwksJson = response.body();

// 2๏ธโƒฃ Parse JWKS
JWKSet jwkSet = JWKSet.parse(jwksJson);

// 3๏ธโƒฃ JWS compact (received from CM)
String jwsCompact = "eyJraWQiOiJjbS1yc2Eta2...";

// 4๏ธโƒฃ Parse JWS header to get kid
JWSObject jwsObject = JWSObject.parse(jwsCompact);
String kid = jwsObject.getHeader().getKeyID();

// 5๏ธโƒฃ Find matching JWK by kid
Optional<JWK> jwkOpt = jwkSet.getKeys()
.stream()
.filter(jwk -> kid.equals(jwk.getKeyID()))
.findFirst();

if (jwkOpt.isEmpty()) {
throw new RuntimeException("No matching key found for kid: " + kid);
}

if (!JWSAlgorithm.RS256.equals(jwsObject.getHeader().getAlgorithm())) {
throw new SecurityException("Unexpected JWS algorithm");
}

// 6๏ธโƒฃ Convert to RSAPublicKey
RSAKey rsaKey = (RSAKey) jwkOpt.get();

if (!"RSA".equals(rsaKey.getKeyType().getValue())) {
throw new SecurityException("Invalid key type");
}

RSAPublicKey publicKey = rsaKey.toRSAPublicKey();

// 7๏ธโƒฃ Verify JWS
JWSVerifier verifier = new RSASSAVerifier(publicKey);
boolean isValid = jwsObject.verify(verifier);

// 8๏ธโƒฃ Output
System.out.println("Verification result: " + isValid);
System.out.println("Using key ID: " + kid);
System.out.println("Signed payload: " + jwsObject.getPayload());

String receiptPayloadHash = "6cfaf1d741a62828ed4...";

String payloadHash = sha256Hex(jwsObject.getPayload().toString());
if (receiptPayloadHash.equals(payloadHash)) {
System.out.println("Receipt Payload Hash matching with JKS Payload Hash");
} else {
throw new SecurityException("Payload Hash not matching");
}

}

private static String sha256Hex(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hash);
} catch (Exception e) {
throw new RuntimeException("Error calculating SHA-256", e);
}
}
}

โš ๏ธ Important Security Checksโ€‹

Always enforce the following:

  • โœ… Accept only HTTPS requests
  • โœ… Verify JWS signature (RS256 only)
  • โœ… Validate kid โ†’ public key mapping
  • โœ… Validate payload hash
  • โœ… Ensure idempotency (avoid duplicate processing)
  • โœ… Match requestId with your records

๐Ÿšซ When to Reject the Webhookโ€‹

Reject the request if:

  • Signature verification fails
  • No matching kid found
  • Algorithm is not RS256
  • Payload hash mismatch
  • Invalid or missing fields

๐Ÿ“Œ Summaryโ€‹

Webhook verification ensures that:

  • The payload is authentic
  • The data is untampered
  • The consent decision is trusted

Always verify both signature + payload hash before processing or storing consent data.