Skip to content

Signature Verification

Every FLUID webhook includes an HMAC-SHA256 signature that allows you to verify the webhook originated from FLUID and hasn't been tampered with. Always verify signatures to protect your webhook endpoint from spoofed requests.

Why Verify Signatures?

Without signature verification, your webhook endpoint is vulnerable to:

  • Spoofed Requests: Attackers sending fake webhooks to trigger unauthorized actions
  • Man-in-the-Middle Attacks: Modified webhook data during transmission
  • Replay Attacks: Old webhooks being replayed to cause duplicate actions

Security Critical: Never trust webhook data without signature verification. Always validate signatures before processing webhook events or updating your database.


How Signature Verification Works


Webhook Headers

Every webhook includes these security headers:

HeaderDescriptionExample
X-FLUID-SignatureHMAC-SHA256 signature (hex-encoded)a3f8b7c2d1e9f4g5...
X-FLUID-EventEvent typetransaction.completed
X-FLUID-Delivery-IDUnique delivery identifier12345
X-FLUID-TimestampUnix timestamp (seconds)1748793600

Signature Generation Algorithm

FLUID generates signatures using this algorithm:

  1. Serialize Payload: Convert webhook payload to JSON string (minified, no extra spaces)
  2. Compute HMAC: Generate HMAC-SHA256 hash using webhook secret
  3. Hex Encode: Convert binary hash to lowercase hexadecimal string
  4. Include in Header: Send as X-FLUID-Signature header

Pseudo-code

payload_json = JSON.stringify(payload)  // No pretty-printing
signature = HMAC-SHA256(payload_json, webhook_secret)
hex_signature = hex_encode(signature).lowercase()

Implementation Guide

Signature Verification Steps

  1. Extract Headers: Get X-FLUID-Signature from request headers
  2. Get Raw Body: Retrieve the raw request body before JSON parsing
  3. Compute HMAC: Calculate HMAC-SHA256 hash using raw body and your webhook secret
  4. Compare Securely: Use constant-time comparison to check if signatures match
  5. Process or Reject: If signatures match, process the webhook; otherwise return 401 Unauthorized

Key Implementation Requirements

Use Raw Request Body: You must use the raw request body (before JSON parsing) for signature verification. JSON parsing may reorder keys or change whitespace, causing signature mismatch.

Use Constant-Time Comparison: Always use timing-safe comparison functions to prevent timing attacks:

  • Node.js: crypto.timingSafeEqual()
  • Python: hmac.compare_digest()
  • PHP: hash_equals()
  • Ruby: ActiveSupport::SecurityUtils.secure_compare()

Store Secret Securely: Never hardcode webhook secrets in your code. Always use environment variables or secure configuration management.


Testing Signature Verification

Generate Test Signature

Use this bash script to generate test signatures for your webhook endpoint:

bash
#!/bin/bash
# generate-signature.sh

WEBHOOK_SECRET="your_webhook_secret_here"
PAYLOAD='{"event":"transaction.completed","event_id":"evt_test123","timestamp":"2025-01-28T10:00:00Z","api_version":"v1","data":{"transaction":{"id":"dt_12345","status":"completed"}}}'

# Generate signature
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | sed 's/^.* //')

echo "Payload: $PAYLOAD"
echo ""
echo "Signature: $SIGNATURE"
echo ""
echo "Test webhook with:"
echo "curl -X POST http://localhost:3000/webhooks/fluid \\"
echo "  -H 'Content-Type: application/json' \\"
echo "  -H 'X-FLUID-Signature: $SIGNATURE' \\"
echo "  -H 'X-FLUID-Event: transaction.completed' \\"
echo "  -d '$PAYLOAD'"

Manual Testing

bash
# 1. Generate signature
echo -n '{"event":"transaction.completed","data":{"transaction":{"id":"dt_12345"}}}' | \
  openssl dgst -sha256 -hmac "your_secret" | \
  sed 's/^.* //'

# Output: a3f8b7c2d1e9f4g5h6i7j8k9l0m1n2o3...

# 2. Send test webhook
curl -X POST http://localhost:3000/webhooks/fluid \
  -H "Content-Type: application/json" \
  -H "X-FLUID-Signature: a3f8b7c2d1e9f4g5h6i7j8k9l0m1n2o3..." \
  -H "X-FLUID-Event: transaction.completed" \
  -d '{"event":"transaction.completed","data":{"transaction":{"id":"dt_12345"}}}'

# Expected: 200 OK (if signature valid)
# Expected: 401 Unauthorized (if signature invalid)

Troubleshooting

Signature Verification Fails

1. Check Raw Body Usage

Problem: Using parsed JSON body instead of raw body.

Solution: Ensure you're computing signature on raw request body before any JSON parsing occurs. Store the raw body buffer when configuring your HTTP framework's body parser.

2. Check Secret

Problem: Using wrong webhook secret.

Solution: Verify you're using the correct secret provided by FLUID. Check your environment variables match the secret from your FLUID dashboard.

3. Check Comparison Method

Problem: Using non-constant-time comparison.

Solution: Use timing-safe comparison function provided by your language's crypto library (timingSafeEqual, compare_digest, hash_equals, secure_compare).

4. Check Charset Encoding

Problem: Incorrect string encoding (UTF-16 vs UTF-8).

Solution: Ensure UTF-8 encoding when converting buffer to string. Most languages default to UTF-8, but verify your framework configuration.

Common Errors

ErrorCauseSolution
Invalid signatureWrong secret or body mismatchVerify secret; use raw body
Missing signature headerHeader not extracted correctlyCheck header name case-sensitivity
TypeError: timingSafeEqualBuffer length mismatchEnsure both arguments are same length
Signature undefinedHeader parsing issueCheck for X-FLUID-Signature (not X_FLUID_SIGNATURE)

Additional Security Measures

1. Timestamp Validation

Reject old webhook requests to prevent replay attacks. Check the X-FLUID-Timestamp header and reject webhooks older than 5 minutes. Calculate the absolute difference between the webhook timestamp and current time, and reject if greater than your threshold.

2. Idempotency Check

Prevent duplicate processing with event ID tracking. Store processed event IDs in a database or cache. Before processing a webhook, check if the event_id has already been processed. If yes, return 200 OK but skip processing. If no, process the webhook and store the event ID.

3. IP Whitelisting (Optional)

Restrict webhook requests to FLUID's IP addresses. Extract the client IP from the request and compare against a whitelist of FLUID IPs. Return 403 Forbidden if the IP is not authorized.

Contact Support for IPs: Contact your FLUID integration manager for the current list of webhook source IP addresses.


Security Checklist

Before going live, verify:

  • ✅ Signature verification implemented
  • ✅ Using raw request body (not parsed JSON)
  • ✅ Using constant-time comparison
  • ✅ Webhook secret stored securely (environment variable)
  • ✅ HTTPS enabled on webhook endpoint
  • ✅ Timestamp validation implemented (optional but recommended)
  • ✅ Event ID deduplication implemented
  • ✅ Proper error handling (return 401 for invalid signatures)
  • ✅ Webhook endpoint tested with valid and invalid signatures
  • ✅ Monitoring/logging configured for failed verifications

Next Steps

Retry Logic →

Learn how FLUID handles failed webhook deliveries with automatic retries.

Event Types →

Explore all available webhook events and their payloads.

Security Best Practices →

Complete security guide for FLUID Network integration.


See Also