Webhook Security Guide
LoginRadius signs every webhook payload with HMAC-SHA256 and requires HTTPS on all webhook URLs. This guide shows you how to verify those signatures and apply security best practices on your server.
Mandatory HTTPS
All webhook URLs must use HTTPS. LoginRadius rejects any target URL that doesn't, which prevents payloads from being intercepted or read in transit.
Signature Verification
Each webhook request includes an X-Hub-Signature header containing an HMAC-SHA256 signature generated from the raw request body. Verify this signature on every incoming request to confirm the payload originated from LoginRadius and has not been tampered with.
How Signature Verification Works
- LoginRadius computes
HMAC-SHA256(secret, rawBody)and sends the hex-encoded result in theX-Hub-Signatureheader. - Your server must compute the same HMAC using the same secret and the raw request body bytes.
- Compare the computed value against the received header using a constant-time comparison to prevent timing attacks.
- Reject any request where the signatures do not match or where the header is absent.
Signing Key Selection
By default, LoginRadius signs webhook payloads using your app's primary API secret. If you configure a Secret Name on the webhook subscription, the corresponding additional API secret is used instead.
This allows you to:
- Rotate signing keys for individual webhooks without changing your global API secret.
- Assign different secrets to different webhook consumers for isolation.
Signature Validation Code Examples
.NET
Node.js
Python
using System;
using System.Text;
using System.Security.Cryptography;
public class WebhookSignatureValidator
{
public static bool Validate(string secret, string rawBody, string receivedSignature)
{
var keyBytes = Encoding.UTF8.GetBytes(secret);
using var hmac = new HMACSHA256(keyBytes);
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
var computed = BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant();
// Constant-time comparison
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(computed),
Encoding.UTF8.GetBytes(receivedSignature.ToUpperInvariant())
);
}
}
const crypto = require('crypto');
function validateWebhookSignature(secret, rawBody, receivedSignature) {
const computed = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
.toUpperCase();
// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(receivedSignature.toUpperCase())
);
}
import hmac
import hashlib
def validate_webhook_signature(secret: str, raw_body: bytes, received_signature: str) -> bool:
computed = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest().upper()
# Constant-time comparison to prevent timing attacks
return hmac.compare_digest(computed, received_signature.upper())
Implementation Considerations
- Use the correct secret: If a Secret Name is set on the subscription, use that additional API secret — not the primary API secret. See Signing Key Selection above.
- Validate on the raw body: Compute the HMAC over the raw request body bytes before any JSON parsing. Re-serializing may alter whitespace or field order, which changes the bytes and breaks the hash.
- Use constant-time comparison: Never use
==to compare signatures; usecrypto.timingSafeEqual(Node.js),hmac.compare_digest(Python), orCryptographicOperations.FixedTimeEquals(.NET). - Reject requests with missing or invalid signatures: Return HTTP 401 immediately.
Best Practices
- Allowlist LoginRadius IPs: Only accept requests from known LoginRadius IP ranges to block spoofed traffic.
- Rate-limit your endpoint: Add rate limits to protect against abuse or accidental flood delivery.
- Rotate secrets regularly: Store API secrets in a secrets manager, not in source code, and rotate them periodically.
- Check Content-Type and structure: Reject requests that aren't
application/jsonor don't match the expected payload shape. - Log all requests: Keep a record of every incoming webhook, including signature failures and unexpected sources. Set up alerts for anything unusual.
- Handle retries gracefully: If a delivery fails temporarily, LoginRadius will retry. Make your handler idempotent so duplicate deliveries don't cause side effects.