Webhook Authentication - Secure Your Endpoints
finlight provides multiple authentication methods to secure your webhook deliveries. Every webhook includes signature validation by default, with optional additional authentication layers for enhanced security.
finlight supports four authentication methods that can be used individually or in combination:
None
No additional authentication beyond the default signature validation.
When to Use:
- Testing and development environments
- Internal endpoints behind secure networks
- When signature validation provides sufficient security
Note: Signature validation is still included with every webhook request regardless of this setting.
X-Finlight-Key Header
Sends your API key in a custom X-Finlight-Key header with each webhook request.
Configuration:
- Provide your API key during webhook setup
- The key will be included in the
X-Finlight-Keyheader
Implementation: Your endpoint should validate the incoming header:
const finlightKey = req.headers['x-finlight-key']
if (finlightKey !== 'your-expected-api-key') {
return res.status(401).send('Invalid API key')
}
Headers Sent:
X-Finlight-Key: your-api-key-value
X-Webhook-Signature: sha256=signature
X-Webhook-Timestamp: 2024-01-15T10:30:00.000Z
Basic Authentication
HTTP Basic Authentication with username/password credentials.
Configuration:
- Set username and password during webhook setup
- Credentials are base64-encoded and sent in the
Authorizationheader
Implementation: Your endpoint receives standard HTTP Basic Auth:
const auth = req.headers.authorization
if (!auth || !auth.startsWith('Basic ')) {
return res.status(401).send('Missing Basic Auth')
}
const credentials = Buffer.from(auth.slice(6), 'base64').toString()
const [username, password] = credentials.split(':')
if (username !== 'expected-user' || password !== 'expected-pass') {
return res.status(401).send('Invalid credentials')
}
Headers Sent:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
X-Webhook-Signature: sha256=signature
X-Webhook-Timestamp: 2024-01-15T10:30:00.000Z
Signature Validation
Automatic Security: Every webhook request includes signature validation regardless of your chosen authentication method.
How It Works:
- finlight generates a timestamp when sending the webhook
- Creates a message by concatenating:
timestamp + '.' + payload - Signs the message using HMAC-SHA256 with your webhook's secret key
- Sends both signature and timestamp in headers
Headers Included:
X-Webhook-Signature: sha256=computed-signature
X-Webhook-Timestamp: 2024-01-15T10:30:00.000Z
Signature Algorithm:
message = timestamp + '.' + raw_request_body
signature = HMAC-SHA256(message, webhook_secret)
When no X-Webhook-Timestamp header is present, the message is the raw body alone. Compare the result in constant time, and reject deliveries whose timestamp is more than five minutes old.
The body must be the raw, unparsed request bytes. Any middleware that decodes and re-encodes the JSON before you read it changes the byte sequence and invalidates the signature — this is the most common cause of verification failures. Read the body as a raw string or buffer, verify it, and only then parse it.
Verify with an official client
Every client library ships a helper that performs the whole check for you: it accepts the sha256= prefix on the signature, compares in constant time, enforces the five-minute replay window, and returns the parsed article. It throws or returns an error when verification fails — always answer that with a 4xx and never process the payload.
Verify a webhook
import express from 'express'
import { WebhookService } from 'finlight-client'
const app = express()
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const article = WebhookService.constructEvent(
req.body.toString(),
req.headers['x-webhook-signature'] as string,
process.env.WEBHOOK_SECRET!,
req.headers['x-webhook-timestamp'] as string,
)
console.log('New article:', article.title)
res.sendStatus(200)
} catch (err) {
console.error('Webhook verification failed:', err)
res.sendStatus(400)
}
})
Security Best Practices
Timestamp Validation
The constructEvent helpers above already enforce a five-minute replay window, so this is only needed if you verify signatures by hand:
function isTimestampValid(timestamp, toleranceSeconds = 300) {
const now = Date.now()
const requestTime = new Date(timestamp).getTime()
const difference = Math.abs(now - requestTime) / 1000
return difference <= toleranceSeconds
}
Secure Credential Storage
- Environment Variables: Store all secrets in environment variables
- Secret Management: Use AWS Secrets Manager, HashiCorp Vault, or similar
- Never Hard-Code: Never commit secrets to version control
- Regular Rotation: Update webhook secrets periodically
For webhook setup guidance, see the main webhooks documentation. For comprehensive testing, check the webhook testing guide.