# How to Secure and Verify Jira Webhooks with Hookdeck

Jira is the most widely used project management and issue tracking platform, powering development workflows for teams across every industry. Jira's webhook system enables real-time integration with external tools — pushing event data to your endpoints whenever issues change, comments are added, sprints progress, or versions are released.

However, accepting webhooks without proper verification leaves your application vulnerable to spoofed requests from malicious actors. In this guide, we'll show you how to secure and verify Jira webhooks manually and with Hookdeck, ensuring that every webhook your application processes genuinely originated from Jira. For a comprehensive overview of Jira webhook capabilities, see our [guide to Jira webhooks](/webhooks/platforms/guide-to-jira-webhooks-features-and-best-practices).

## Why webhook security matters

Webhooks are HTTP callbacks that notify your application when events occur. Without verification, an attacker could send fake webhook payloads to your endpoint, potentially triggering unauthorized actions like:

* Creating or modifying issues in your downstream systems based on fabricated events
* Triggering CI/CD pipelines with spoofed issue transitions
* Corrupting project data synced from Jira webhooks
* Manipulating workflow automations tied to Jira events

Jira addresses this by supporting HMAC-SHA256 signature verification via the `X-Hub-Signature` header. By verifying this signature, you can be confident that webhooks are authentic and haven't been tampered with in transit.

## How Jira webhook signatures work

Jira Cloud uses HMAC-SHA256 signature verification (added in February 2024). When you register a webhook with a secret, every webhook request includes an `X-Hub-Signature` header containing the signature in the format `sha256=<hex_digest>`.

### The X-Hub-Signature header

The header format looks like this:

```
sha256=5d7370c827b66f5e0b8e2e9b6ab0dc4f1e8a92c3d7f6b5a4e3d2c1b0a9f8e7d6

```

The signature is computed by:

1. Computing an HMAC-SHA256 hash of the raw request body using the webhook secret you configured at registration time
2. Hex-encoding the result
3. Prepending `sha256=`

### Important limitations

* HMAC-SHA256 verification is only available for Jira Cloud admin-registered and REST API webhooks (added February 2024)
* Connect app webhooks use a separate JWT-based mechanism via the `Authorization` header
* Jira Data Center support depends on your version — check your instance's documentation
* The webhook secret cannot be retrieved after creation — store it securely when you first configure it

## Getting your Jira webhook secret

When registering a webhook via the Jira Cloud administration UI or REST API, you provide a secret during setup:

### Via the Jira administration UI

1. Log in to your Jira Cloud instance as an administrator
2. Navigate to Settings (gear icon) > System > WebHooks
3. Click Create a WebHook
4. Enter the webhook URL, Name, and Secret
5. Select the events you want to receive
6. Optionally configure a JQL filter to limit which issue events trigger the webhook
7. Click Create

### Via the REST API

```bash
curl -X POST "https://your-domain.atlassian.net/rest/api/2/webhook" \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic <base64_credentials>" \
  -d '{
    "name": "My Webhook",
    "url": "https://your-endpoint.com/webhooks/jira",
    "events": ["jira:issue_created", "jira:issue_updated"],
    "secret": "your-webhook-secret"
  }'

```

Important: Store the secret securely at the time of creation — Jira does not allow you to retrieve it later.

## Verifying webhook signatures manually

### Verification process overview

1. Extract the `X-Hub-Signature` header and confirm it starts with `sha256=`
2. Strip the `sha256=` prefix to get the received signature
3. Compute HMAC-SHA256 over the raw (unparsed) request body using your configured secret
4. Hex-encode the result
5. Compare using a constant-time function

### Node.js verification example

```javascript
const express = require("express");
const crypto = require("crypto");

const app = express();
const JIRA_WEBHOOK_SECRET = process.env.JIRA_WEBHOOK_SECRET;

function verifyJiraWebhook(rawBody, signatureHeader) {
  if (!signatureHeader || !signatureHeader.startsWith("sha256=")) {
    console.error("Missing or malformed X-Hub-Signature header");
    return false;
  }

  // Strip the sha256= prefix
  const receivedSignature = signatureHeader.slice(7);

  // Compute HMAC-SHA256
  const computedSignature = crypto
    .createHmac("sha256", JIRA_WEBHOOK_SECRET)
    .update(rawBody, "utf8")
    .digest("hex");

  // Use timing-safe comparison
  const receivedBuffer = Buffer.from(receivedSignature, "utf8");
  const expectedBuffer = Buffer.from(computedSignature, "utf8");

  if (receivedBuffer.length !== expectedBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(receivedBuffer, expectedBuffer);
}

app.post(
  "/webhooks/jira",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signatureHeader = req.headers["x-hub-signature"];
    const rawBody = req.body.toString();

    if (!verifyJiraWebhook(rawBody, signatureHeader)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const event = JSON.parse(rawBody);

    // Process the verified webhook
    switch (event.webhookEvent) {
      case "jira:issue_created":
        handleIssueCreated(event);
        break;
      case "jira:issue_updated":
        handleIssueUpdated(event);
        break;
      default:
        console.log(`Received event: ${event.webhookEvent}`);
    }

    res.status(200).json({ received: true });
  }
);

app.listen(3000, () => {
  console.log("Webhook server listening on port 3000");
});

```

### Python verification example

```python
import hmac
import hashlib
import os
from flask import Flask, request, jsonify, abort

app = Flask(__name__)
JIRA_WEBHOOK_SECRET = os.environ.get("JIRA_WEBHOOK_SECRET")

def verify_jira_webhook(raw_body, signature_header):
    """Verify a Jira webhook signature."""
    if not signature_header or not signature_header.startswith("sha256="):
        return False

    # Strip the sha256= prefix
    received_signature = signature_header[7:]

    # Compute HMAC-SHA256
    computed_signature = hmac.new(
        JIRA_WEBHOOK_SECRET.encode("utf-8"),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    # Use timing-safe comparison
    return hmac.compare_digest(computed_signature, received_signature)

@app.route("/webhooks/jira", methods=["POST"])
def handle_webhook():
    signature_header = request.headers.get("X-Hub-Signature", "")
    raw_body = request.get_data()

    if not verify_jira_webhook(raw_body, signature_header):
        abort(401, "Invalid signature")

    event = request.get_json()
    print(f"Received verified {event.get('webhookEvent')} event")

    # Process the webhook payload
    return jsonify({"received": True}), 200

if __name__ == "__main__":
    app.run(port=3000)

```

## Critical security best practices

### 1. Use timing-safe comparisons

Never use standard equality operators (`==` or `===`) to compare signatures. These can leak timing information that attackers exploit. Always use:

* Node.js: `crypto.timingSafeEqual()`
* Python: `hmac.compare_digest()`
* Go: `subtle.ConstantTimeCompare()`

### 2. Preserve the raw request body

The HMAC signature is computed on the exact bytes Jira sends. Any parsing, reformatting, or middleware that modifies the body before verification will produce a different signature. Capture the raw body before JSON parsing.

### 3. Secure secret storage

* Store secrets in environment variables or a secrets manager
* Never hardcode secrets in source code
* Never commit secrets to version control
* Since Jira doesn't allow retrieving the secret after creation, back it up securely

### 4. Use idempotent processing

Jira may deliver the same webhook multiple times due to retries. Use the `X-Atlassian-Webhook-Identifier` header (when available) to deduplicate events and ensure idempotent processing.

### 5. Respond within Jira's timeout window

Jira enforces a 5-second connection timeout and 20-second response timeout. Ensure your endpoint acknowledges webhooks promptly to avoid retries and potential webhook disabling.

## Simplifying verification with Hookdeck

Manually implementing and maintaining webhook verification across multiple providers can be complex and error-prone. Hookdeck provides a webhook gateway that handles verification automatically, allowing you to focus on processing events.

### What is Hookdeck?

[Hookdeck](https://hookdeck.com) provides an event gateway that sits between webhook providers (like Jira) and your application. It provides:

* Automatic signature verification
* Event queuing and retry logic
* Request logging and debugging tools
* Local development tunneling

### Setting up Jira webhooks with Hookdeck

#### Step 1: Install the Hookdeck CLI

#### Step 2: Authenticate

```bash
hookdeck login

```

This opens your browser for authentication. If you don't have a Hookdeck account, you can create one during this step.

#### Step 3: Create a connection

```bash
hookdeck listen 3000 jira-source --path /webhooks/jira

```

This command:

* Creates a public URL for receiving webhooks
* Forwards events to `http://localhost:3000/webhooks/jira`
* Displays the Source URL to configure in Jira

#### Step 4: Configure Jira

1. Copy the Hookdeck Source URL from the CLI output
2. In Jira, go to Settings > System > WebHooks
3. Click Create a WebHook
4. Set the URL to your Hookdeck Source URL
5. Set a Secret for HMAC verification
6. Select the events you want to receive
7. Click Create

#### Step 5: Configure source verification

1. Open the Hookdeck Dashboard
2. Navigate to Connections and select your source
3. Under Advanced Source Configuration, enable Source Authentication
4. Select HMAC as the authentication method
5. Configure the HMAC settings:
  
  * Algorithm: SHA-256
  * Header: X-Hub-Signature
  * Encoding: Hex
  * Signature Prefix: `sha256=`
  * Secret: Your Jira webhook secret
6. Click Save

### How Hookdeck verification works

When verification is enabled:

1. Hookdeck receives the webhook from Jira
2. Hookdeck validates the `X-Hub-Signature` against the payload using your secret
3. Valid requests are forwarded to your endpoint with `x-hookdeck-verified: true`
4. Invalid requests are rejected and logged as "Verification Failed"

This means your application can trust any request from Hookdeck without implementing its own Jira signature verification logic. You only need to implement [Hookdeck's signature verification](https://hookdeck.com/docs/authentication#hookdeck-webhook-signature-verification) on your server.

## Troubleshooting common issues

### Signature mismatch

If signatures don't match, verify:

1. Raw body usage: Ensure you're using the exact bytes received, not a parsed/stringified version
2. Secret accuracy: Confirm the secret matches what was configured when creating the webhook in Jira
3. Header name: The header is `X-Hub-Signature` (following the WebSub standard), not a Jira-specific header name
4. Prefix handling: Remember to strip the `sha256=` prefix before comparing

### Missing X-Hub-Signature header

* Confirm you configured a secret when creating the webhook — without a secret, Jira doesn't send the signature header
* Check that you're using Jira Cloud — Data Center versions may not support HMAC verification depending on the version
* Connect app webhooks use JWT authentication instead of HMAC

### Webhooks not arriving

1. Check Jira's webhook logs: In Settings > System > WebHooks, check the Recent Deliveries section
2. Verify JQL filter: If you configured a JQL filter, ensure your test events match the filter criteria
3. Check concurrency limits: Jira enforces a limit of 20 concurrent requests per tenant + webhook URL host pair

## Conclusion

Securing Jira webhooks requires verifying the HMAC-SHA256 signature on every incoming request. While you can implement this verification manually, [Hookdeck](https://hookdeck.com) simplifies the process by providing automatic signature verification, event management, and development tools, allowing you to focus on building your integration rather than infrastructure.