# Secure Webhook Deliveries

Use this page to verify webhook requests using an HMAC signature when you configure a Secret Token. Nexus calculates an HMAC-SHA1 signature of the raw JSON payload body and sends it in the `X-Nexus-Webhook-Signature` header.

This page covers examples of different scripts that can be used to verify the payload you receive is what was originally sent. For example on express based node.js, refer to [Working With HMAC Payloads](https://help.sonatype.com/en/working-with-hmac-payloads.html "Working With HMAC Payloads") page.

## Java (Spring Boot)

The following example verifies the `X-Nexus-Webhook-Signature` header using HMAC-SHA1 and rejects requests when the signature does not match.

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
public boolean verifyWebhookSignature(String payload, String signature, String secret) {
    try {
        Mac mac = Mac.getInstance("HmacSHA1");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), "HmacSHA1");
        mac.init(secretKeySpec);
        byte[] hash = mac.doFinal(payload.getBytes());
        String expectedSignature = Hex.encodeHexString(hash);
        return signature.equals(expectedSignature);
    } catch (Exception e) {
        return false;
    }
}

@PostMapping("/webhook")
public ResponseEntity<String> receiveWebhook(
    @RequestBody String payload,
    @RequestHeader("X-Nexus-Webhook-Signature") String signature) {
    if (!verifyWebhookSignature(payload, signature, webhookSecret)) {
        return ResponseEntity.status(401).body("Invalid signature");
    }

// Process webhook
    return ResponseEntity.ok("Received");
}
```

## Python (Flask)

The following example validates the `X-Nexus-Webhook-Signature` before processing the webhook payload.

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

app = Flask(__name__)
WEBHOOK_SECRET = "your-secret-key-here"

def verify_signature(payload, signature):
    """Verify HMAC-SHA1 signature"""
    expected_signature = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha1
    ).hexdigest()

return hmac.compare_digest(signature, expected_signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Nexus-Webhook-Signature')
    payload = request.get_data(as_text=True)

if not signature or not verify_signature(payload, signature):
        return jsonify({'error': 'Invalid signature'}), 401

# Process webhook
    data = request.json
    print(f"Received webhook: {data['event']}")

return jsonify({'status': 'received'}), 200

if __name__ == '__main__':
    app.run(port=8888)
```

## Go

The following example computes and compares the HMAC-SHA1 signature from the request body and returns an error response when verification fails.

```go
import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/hex"
    "encoding/json"
    "io/ioutil"
    "log"
    "net/http"
)

const webhookSecret = "your-secret-key-here"

func verifySignature(payload []byte, signature string) bool {
    mac := hmac.New(sha1.New, []byte(webhookSecret))
    mac.Write(payload)
    expectedSignature := hex.EncodeToString(mac.Sum(nil))

return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

signature := r.Header.Get("X-Nexus-Webhook-Signature")
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Failed to read body", http.StatusBadRequest)
        return
    }

if !verifySignature(body, signature) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

var webhook map[string]interface{}
    if err := json.Unmarshal(body, &webhook); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

log.Printf("Received webhook: %s\n", webhook["event"])

w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]string{"status": "received"})
}

func main() {
    http.HandleFunc("/webhook", webhookHandler)
    log.Println("Webhook server listening on :8888")
    log.Fatal(http.ListenAndServe(":8888", nil))
}
```

## Best Practices

Apply these practices to improve the security and reliability of your webhook endpoint.

- Use HTTPS for webhook endpoints in production environments.
- Store secret tokens securely, such as in environment variables or a secrets management system.
- Compare signatures using a constant-time method to reduce timing-attack risk.
- Verify the signature before you parse or process the payload.
- Log signature verification failures to support security monitoring and investigations.
- Rotate secrets on a regular schedule, and update the value in both Nexus and your receiving service.
- Apply rate limiting to your webhook endpoint to reduce denial-of-service risk.

## Signature Verification FAQs

- **Why does signature verification always fail?**

This typically indicates the receiving service is using a different secret than the one configured in the Nexus webhook. Confirm that the secret token value matches the Nexus configuration.

- **Why does signature verification fail intermittently?**

They usually happen when the payload is modified before verification. Verify the signature against the raw request body, and avoid parsing or re-serializing JSON before verification.

- **Why am I seeing a “Missing signature” error?**

This usually means no secret token is configured for the webhook. Configure a secret token in the Nexus webhook so Nexus sends the signature header.

- **Why do I get a different signature every time?**

This can occur if you are using the wrong hashing algorithm. Ensure your receiver uses HMAC-SHA1 (not SHA256 or MD5).

- **Why does signature verification work locally but fail in production?**

This commonly points to encoding differences. Ensure the payload and secret are handled consistently using UTF-8 encoding in all environments.
