Skip to main content

Request Signing

Webhook requests must be signed using a shared secret. The X-Webhook-Signature header supports multiple signatures, with each signature separated by a comma (,) and each signature version prefixed by the version number (e.g., v1) and an equals sign (=).

Signature v1

To sign a request, compute an HMAC-SHA-256 digest over the exact request body followed by the X-Webhook-Timestamp header value, using the shared secret key, then encode the digest as a hex string. Add the calculated signature to the X-Webhook-Signature request header with the v1= prefix.

NodeJS example

// Prepare event payload
const body = JSON.stringify({
type: 'test.ping',
version: '1',
data: { message: 'ping!' },
})

// Append timestamp to body
const timestamp = Math.floor(Date.now() / 1000).toString()
const hashPayload = body + timestamp

// Create signature with payload + hmac secret
const hmac = crypto.createHmac('sha256', secretKey)
const signature = hmac.update(hashPayload).digest('hex')

// Prepare request
const request = new Request($url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Key-Id': secretKey.slice(0, 8),
'X-Webhook-Signature': `v1=${signature}`,
'X-Webhook-Timestamp': timestamp,
},
body
})

PHP example

// Prepare event payload
$body = json_encode([
'type' => 'test.ping',
'version' => '1',
'data' => ['message' => 'ping!'],
]);

// Append timestamp to body
$timestamp = (string) round(microtime(true));
$payload = $body . $timestamp;

// Create signature with payload + hmac secret
$signature = hash_hmac('sha256', $payload, $secretKey);

// Prepare request
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Webhook-Key-Id: ' . substr($secretKey, 0, 8),
'X-Webhook-Signature: v=1' . $signature,
'X-Webhook-Timestamp: ' . $timestamp,
],
]);