Verifying Webhook Signatures (Optional)
Each webhook is assigned a unique Signing Secret, visible in the Edit Webhook panel of the Portal. You can use this secret to verify that incoming requests were sent by our platform and have not been tampered with.
Every webhook request includes an X-Webhook-Signature header in the format:
X-Webhook-Signature: t=1738900000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Where t is the Unix timestamp of when the request was sent, and v1 is the HMAC-SHA256 signature.
How Verification Works
- Extract the
t(timestamp) andv1(signature) values from theX-Webhook-Signatureheader. - Build the signing payload by concatenating the timestamp, a period (
.), and the raw request body:{t}.{body} - Compute the HMAC-SHA256 of the signing payload using your webhook's Signing Secret.
- Compare your computed signature with the
v1value from the header. - Optionally, check that the timestamp is within an acceptable window (e.g., 5 minutes) to prevent replay attacks.
Verification with Official SDKs
Our official SDKs provide a Webhook.verify() helper that handles header parsing, HMAC computation, constant-time comparison, and timestamp tolerance checking. This is the recommended approach.
PHP
use GeneratorLabs\Webhook;
$header = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
try {
$payload = Webhook::verify($body, $header, $signing_secret);
// $payload contains the decoded event data
} catch (GeneratorLabs\Exception $e) {
http_response_code(403);
}
Python
from generatorlabs import Webhook
header = request.headers.get('X-Webhook-Signature', '')
body = request.get_data(as_text=True)
try:
payload = Webhook.verify(body, header, signing_secret)
# payload contains the decoded event data
except Exception:
return jsonify({'error': 'Invalid signature'}), 403
Node.js
import { Webhook } from 'generatorlabs';
const header = req.headers['x-webhook-signature'];
const body = req.body; // raw string body
try {
const payload = Webhook.verify(body, header, signingSecret);
// payload contains the decoded event data
} catch (error) {
res.status(403).json({ error: 'Invalid signature' });
}
Go
import generatorlabs "github.com/generator-labs/go-sdk"
header := r.Header.Get("X-Webhook-Signature")
body, _ := io.ReadAll(r.Body)
payload, err := generatorlabs.VerifyWebhook(string(body), header, signingSecret, generatorlabs.DefaultWebhookTolerance)
if err != nil {
http.Error(w, "Invalid signature", http.StatusForbidden)
return
}
// payload contains the decoded event data
Ruby
require 'generatorlabs'
header = request.env['HTTP_X_WEBHOOK_SIGNATURE'] || ''
body = request.body.read
begin
payload = GeneratorLabs::Webhook.verify(body, header, signing_secret)
# payload contains the decoded event data
rescue GeneratorLabs::Error
halt 403, { error: 'Invalid signature' }.to_json
end
All SDKs default to a 5-minute timestamp tolerance window. You can customize this (in seconds) via an optional parameter, or pass 0 to disable timestamp checking.
Each SDK repository includes a complete webhook verification example in the examples/ directory. See the SDK README for your language for details.
Manual Verification
If you prefer not to use an SDK, you can verify signatures manually:
- Parse the
tandv1values from theX-Webhook-Signatureheader. - Compute:
HMAC-SHA256(key=signing_secret, message="{t}.{raw_body}"). - Compare your computed hex digest with
v1using a constant-time comparison. - Optionally check that
tis within an acceptable time window.
$header = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
// Parse the header
preg_match('/t=(\d+),v1=([a-f0-9]+)/', $header, $matches);
$timestamp = $matches[1] ?? '';
$signature = $matches[2] ?? '';
// Check timestamp tolerance (5 minutes)
if (abs(time() - intval($timestamp)) > 300) {
http_response_code(403);
exit;
}
// Compute and compare the signature
$expected = hash_hmac('sha256', $timestamp . '.' . $body, $signing_secret);
if (hash_equals($expected, $signature)) {
$payload = json_decode($body, true);
// Signature is valid, process the event
}
Regenerating a Signing Secret
You can regenerate a webhook's Signing Secret at any time from the Edit Webhook panel. After regeneration, the previous secret will no longer produce valid signatures: make sure to update your application with the new secret.