Webhook Signatures
Every webhook sent by Hook Mesh includes a cryptographic signature that proves its authenticity. This prevents attackers from forging webhooks to your customers' endpoints.
Why Signatures Matter
Without signatures, anyone could send fake webhooks to your customers' endpoints:
- Unauthorized actions: Trigger operations without authentication
- Data injection: Insert malicious or incorrect data
- Replay attacks: Resend old webhooks to cause duplicate processing
- Service disruption: Flood endpoints with fake webhooks
Hook Mesh's signature system ensures that webhooks can only be sent by you, and that your customers can verify their authenticity before processing.
How Signatures Work
Hook Mesh uses HMAC-SHA256, an industry-standard cryptographic algorithm used by Stripe, GitHub, Shopify, and other major platforms.
Signature Generation Process
Construct signed content
webhook_id.timestamp.json_payloadConcatenate the webhook ID, timestamp, and payload
Compute HMAC-SHA256
HMAC-SHA256(secret, signed_content)Use the endpoint's secret as the HMAC key
Base64 encode
base64(hmac_result)Convert binary result to ASCII string
Add version prefix
v1,base64_signaturePrefix with version for future algorithm changes
Webhook Headers
Hook Mesh includes three security headers with every webhook:
| Header | Example | Purpose |
|---|---|---|
Webhook-Id | msg_2Zy3X8... | Unique webhook identifier |
Webhook-Timestamp | 1705756800 | Unix timestamp (seconds) |
Webhook-Signature | v1,a1b2c3... | HMAC-SHA256 signature |
POST /webhooks HTTP/1.1
Host: api.customer.com
Content-Type: application/json
User-Agent: HookMesh-Delivery/1.0
Webhook-Id: msg_2Zy3X8qP9rK5mN1vB
Webhook-Timestamp: 1705756800
Webhook-Signature: v1,ZjQxNmY3MGViYTc4NDExYWJiZGU0ZjA5ZTU4MTY0NzU=
{
"event_type": "user.created",
"user_id": "usr_123",
"email": "alice@example.com",
"created_at": "2026-01-20T15:30:00Z"
}Endpoint Secrets
Each endpoint has its own unique signing secret. Secrets are:
- 64 characters: Hexadecimal format for high entropy
- Cryptographically random: Generated using secure random bytes
- Per-endpoint: Isolates security between customers
- Rotatable: Can be changed without downtime
whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8whsec_ (webhook secret) to prevent accidental exposure in logs or error messages.Timestamp Tolerance
Hook Mesh includes a timestamp in each signature to prevent replay attacks. Your customers should:
- Check timestamp freshness: Reject webhooks older than 5 minutes
- Allow clock skew: Account for small time differences between servers
- Store processed IDs: Track webhook IDs to prevent duplicate processing
function isTimestampValid(timestamp) {
const webhookTime = parseInt(timestamp, 10);
const currentTime = Math.floor(Date.now() / 1000);
const tolerance = 5 * 60; // 5 minutes in seconds
// Reject if webhook is too old
if (currentTime - webhookTime > tolerance) {
return false;
}
// Reject if webhook is from the future (clock skew)
if (webhookTime - currentTime > tolerance) {
return false;
}
return true;
}
// Example usage
const timestamp = req.headers['webhook-timestamp'];
if (!isTimestampValid(timestamp)) {
return res.status(400).json({ error: 'Webhook timestamp invalid' });
}Signature Versioning
Signatures are prefixed with a version number (v1) to support algorithm changes in the future:
v1,ZjQxNmY3MGViYTc4NDExYWJiZGU0ZjA5ZTU4MTY0NzU=
│ │
│ └─ Base64-encoded signature
└───── Version (currently always v1)If Hook Mesh ever changes the signature algorithm, the version will be incremented (v2, v3, etc.), allowing your customers to support multiple versions during migration.
Security Comparison
| Provider | Algorithm | Signature Format |
|---|---|---|
| Hook Mesh | HMAC-SHA256 | v1,base64 |
| Stripe | HMAC-SHA256 | t=timestamp,v1=sig |
| Svix | HMAC-SHA256 | v1,base64 |
| GitHub | HMAC-SHA256 | sha256=hex |
Common Security Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Not verifying signatures | Critical | Always verify before processing |
| Ignoring timestamp | High | Reject old webhooks (5 min tolerance) |
| Hardcoding secrets | High | Use environment variables |
| Logging signatures | Medium | Sanitize logs, don't log secrets |
| String comparison | Medium | Use timing-safe comparison |
Implementation Guide
Ready to implement signature verification? We provide complete code examples in multiple languages:
Verifying Signatures Guide
Step-by-step implementation with working code in Node.js, Python, Go, and PHP.
View Implementation GuideBest Practices
- Always verify signatures: Never process unverified webhooks
- Validate timestamps: Reject webhooks older than 5 minutes
- Use timing-safe comparison: Prevent timing attacks on signature validation
- Store secrets securely: Use environment variables or secret managers
- Rotate secrets regularly: Change secrets every 90 days for compliance
- Log verification failures: Monitor for potential attacks
- Rate limit webhook endpoints: Protect against DoS attacks
- Document for customers: Provide clear verification instructions
Next Steps
- Implement signature verification with code examples in 4 languages
- Learn about secret rotation for zero-downtime updates
- Review security best practices for production deployments
- Create endpoints with automatic secret generation