Java JWT verification fails for Atlassian webhook events using HMAC secret

I’m having trouble verifying JWT tokens from Atlassian webhook notifications in my Java application. The verification keeps failing and I get this error:

JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

Here’s what I did:

  1. Set up a webhook endpoint for issue comment events using the REST API at /rest/api/2/webhook
  2. Configured it with a symmetric key: "secret": "MyWebhookSecret"
  3. The webhook triggers correctly and I receive events at my endpoint
  4. The Authorization header contains a JWT token that I’m trying to validate

My validation code looks like this:

public static boolean verifyWebhookToken(String token, String sharedSecret) {
    try {
        JwtParser parser = Jwts.parser()
            .setSigningKey(Keys.hmacShaKeyFor(sharedSecret.getBytes()))
            .build();
            
        Jws<Claims> parsedJwt = parser.parseClaimsJws(token);
        Claims payload = parsedJwt.getBody();
        
        // Check if token is expired
        if (payload.getExpiration().before(new Date())) {
            System.out.println("Token expired");
            return false;
        }
        
        // Verify issuer
        String tokenIssuer = payload.getIssuer();
        if (!"jira".equals(tokenIssuer)) {
            System.out.println("Wrong issuer");
            return false;
        }
        
        return true;
    } catch (Exception ex) {
        ex.printStackTrace();
        System.out.println(ex.getMessage());
        return false;
    }
}

The signature verification keeps failing. What could be wrong with my approach?

Check how you’re handling the request URL during verification. I hit this same issue - turns out I was constructing the canonical request wrong for signature validation. Atlassian calculates the JWT signature using a specific format: HTTP method, URL path, and request body hash. Your verification has to reconstruct that exact string. Also double-check your shared secret matches what you set in the webhook config. Any trailing spaces or encoding differences will break the signature. I’d log the actual token payload to see what claims you’re getting - sometimes the issuer field has the full instance URL instead of just ‘jira’ depending on which Atlassian product you’re using.

Had this exact problem last year - it’s usually query string hash verification. Atlassian webhooks include query parameters when calculating the JWT signature, but most people skip this step. You’ve got to pull the query string from the original request URL and include it during token verification. Also double-check you’re using HS256 - that’s what Atlassian typically uses for webhook signatures. Set your JWT parser to explicitly use HS256 instead of trusting the header. I’ve seen cases where the algorithm in the token header didn’t match what was actually used for signing. One more thing - check the token format. Sometimes the Authorization header has a "JWT " prefix that needs to be stripped before parsing. Make sure you’re only passing the token string to your parser method.

you’re using the wrong encoding for the secret key. atlassian uses base64url encoding for the shared secret, not plain bytes. try Keys.hmacShaKeyFor(Base64.getUrlDecoder().decode(sharedSecret)) instead of .getBytes(). this tripped me up when i first set up webhook verification too.