Secure Webhook Deliveries

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 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.

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.

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.

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.

Signature Verification FAQs

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.

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.

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.

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

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