Appearance
Webhook Signature Verification
CubeConnect signs outbound webhook requests using HMAC-SHA256 when you configure a signing secret. This allows you to verify that webhook requests genuinely come from CubeConnect.
Outbound Webhook Signatures
When you set a Signing Secret in Settings → Webhook, every outbound webhook includes two headers:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 hex digest |
X-Webhook-Timestamp | ISO 8601 timestamp used in the signature |
How It Works
- CubeConnect creates a string:
{timestamp}.{json_payload} - Computes HMAC-SHA256 using your signing secret
- Sends the hex digest in the
X-Webhook-Signatureheader
Verification Examples
php
function verifyWebhookSignature(Request $request, string $secret): bool
{
$signature = $request->header('X-Webhook-Signature');
$timestamp = $request->header('X-Webhook-Timestamp');
if (!$signature || !$timestamp) {
return false;
}
// Prevent replay attacks (5-minute tolerance)
$requestTime = strtotime($timestamp);
if (abs(time() - $requestTime) > 300) {
return false;
}
$payload = $request->getContent();
$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
return hash_equals($expected, $signature);
}
// Usage in Laravel controller
public function handleWebhook(Request $request)
{
$secret = config('services.cubeconnect.webhook_secret');
if (!verifyWebhookSignature($request, $secret)) {
return response('Invalid signature', 401);
}
$event = $request->input('event');
// Process webhook...
return response('OK', 200);
}javascript
const crypto = require('crypto')
function verifyWebhookSignature(body, signature, timestamp, secret) {
if (!signature || !timestamp) return false
// Prevent replay attacks (5-minute tolerance)
const requestTime = new Date(timestamp).getTime()
if (Math.abs(Date.now() - requestTime) > 300000) return false
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + body)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
)
}
// Usage in Express
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature']
const timestamp = req.headers['x-webhook-timestamp']
const body = req.body.toString()
if (!verifyWebhookSignature(body, signature, timestamp, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
const payload = JSON.parse(body)
console.log('Event:', payload.event)
// Process webhook...
res.sendStatus(200)
})python
import hmac
import hashlib
from datetime import datetime, timezone
def verify_webhook_signature(body: str, signature: str, timestamp: str, secret: str) -> bool:
if not signature or not timestamp:
return False
# Prevent replay attacks (5-minute tolerance)
request_time = datetime.fromisoformat(timestamp)
now = datetime.now(timezone.utc)
if abs((now - request_time).total_seconds()) > 300:
return False
expected = hmac.new(
secret.encode(),
(timestamp + '.' + body).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# Usage in Flask
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature', '')
timestamp = request.headers.get('X-Webhook-Timestamp', '')
body = request.get_data(as_text=True)
if not verify_webhook_signature(body, signature, timestamp, os.environ['WEBHOOK_SECRET']):
return 'Invalid signature', 401
payload = request.get_json()
print(f"Event: {payload['event']}")
# Process webhook...
return '', 200Important Notes
- Always use time-safe comparison (
hash_equalsin PHP,timingSafeEqualin Node.js,compare_digestin Python) to prevent timing attacks - Use the raw request body for signature computation, not a parsed/re-serialized version
- Check the timestamp to prevent replay attacks (reject requests older than 5 minutes)
- The signing secret is configured in Settings → Webhook and can be regenerated at any time
Meta Webhook Signatures (Inbound)
Meta signs webhook requests sent to CubeConnect using a different scheme. This is handled automatically by CubeConnect, but for reference:
Header Format
http
X-Hub-Signature-256: sha256=a1b2c3d4e5f6...Verification
php
function verifyMetaSignature(string $payload, string $signature, string $appSecret): bool
{
$expected = hash_hmac('sha256', $payload, $appSecret);
$received = str_replace('sha256=', '', $signature);
return hash_equals($expected, $received);
}INFO
CubeConnect handles Meta signature verification automatically. You only need to verify signatures on your outbound webhook endpoint using the examples above.