Webhooks
Checking a Webhook Signature
Swaps.xyz signs the webhook events and requests we send to your endpoints. We do so by including a signature in each event’s X-Webhook-Signature
header. This allows you to validate that the events and requests were sent by Swaps.xyz, not by a third party.
Before you can verify X-Webhook-Signature
signatures for webhook events, you need to retrieve your webhook API key from us.
The X-Webhook-Signature
header contains a timestamp and one signature. The timestamp is prefixed by t=
, and the signature is prefixed by s=
.
X-Webhook-Signature: t=1492774577,s=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Swaps.xyz generates signatures using a hash-based message authentication code (HMAC) with SHA-256.
Step 1
Split the header, using the ,
character as the separator, to get a list of elements. Then split each element, using the =
character as the separator, to get a prefix and value pair.
The value for the prefix t
corresponds to the timestamp, and s
corresponds to the signature.
Step 2
You achieve this by concatenating:
- The timestamp (as a string)
- The character
.
and - For a
POST
request, the actual JSON payload (i.e., the request's body).
Step 3
Compute a HMAC with the SHA-256 hash function. Use your account's webhook API key as the key.
Compare the signature in the header to the expected signature.
Code example
const crypto = require('crypto');
function verifyWebhookSignature(
payload: any,
signatureHeader: string,
webhookSecret: string,
) {
// Parse signature header
const parts = signatureHeader.split(',');
const timestamp = parts[0].substring(2);
const receivedSignature = parts[1].substring(2);
// Check timestamp tolerance (5 minutes)
const now = Date.now();
const timeDiff = Math.abs(now - Number(timestamp));
if (timeDiff > 300000) {
throw new Error('Webhook timestamp too old');
}
// Generate expected signature
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
const hmac = crypto.createHmac('sha256', webhookSecret);
hmac.update(signedPayload);
const expectedSignature = hmac.digest('hex');
// Compare signatures (use timing-safe comparison)
return crypto.timingSafeEqual(
Buffer.from(receivedSignature, 'hex'),
Buffer.from(expectedSignature, 'hex'),
);
}
import hmac
import hashlib
import time
import json
class WebhookVerifier:
def __init__(self, webhook_secret):
self.webhook_secret = webhook_secret.encode('utf-8')
def verify(self, request_body, signature_header, tolerance_seconds=300):
try:
# Parse signature header
parts = signature_header.split(',')
timestamp = int(parts[0].split('=')[1])
received_signature = parts[1].split('=')[1]
# Check timestamp tolerance
now = int(time.time() * 1000) # Convert to milliseconds
time_diff = abs(now - timestamp)
if time_diff > tolerance_seconds * 1000:
raise ValueError(f"Webhook timestamp too old: {time_diff}ms")
# Generate expected signature
signed_payload = f"{timestamp}.{request_body}"
expected_signature = hmac.new(
self.webhook_secret,
signed_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Compare signatures (timing-safe comparison)
return hmac.compare_digest(received_signature, expected_signature)
except Exception as e:
print(f"Webhook verification failed: {e}")
return False
Updated 16 days ago