Signing secret
The webhook signing secret is the same secret key associated with your API key.
HTTP Headers
All webhook requests include the following headers:
| Header | Description | Example |
|---|---|---|
Content-Type | Request content type | application/json |
X-Webhook-Signature | HMAC-SHA256 signature | a1b2c3d4e5f6... (64 chars) |
X-Webhook-Timestamp | Unix timestamp (seconds) | 1705920000 |
X-Webhook-Event-Id | Unique event ID | 12345 |
X-Webhook-Event-Name | Event type | deposit.completed |
X-Webhook-Delivery-Id | Delivery attempt ID | 67890 |
Signature Verification
To verify webhook authenticity, compute the HMAC-SHA256 signature and compare it with X-Webhook-Signature.
Algorithm
signed_payload = "{timestamp}.{request_body}"
expected_signature = HMAC-SHA256(signed_payload, webhook_secret)
Verification Steps
- Extract
X-Webhook-SignatureandX-Webhook-Timestampfrom headers - Concatenate:
{timestamp}.{raw_request_body} - Compute HMAC-SHA256 using your webhook secret
- Compare signatures using constant-time comparison
- Reject if timestamp is older than 5 minutes (replay attack prevention)
Code Example (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, timestamp, secret) {
// Check timestamp (5 minute tolerance)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) {
return false;
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}Code Example (Python)
import hmac
import hashlib
import time
def verify_webhook_signature(payload: str, signature: str, timestamp: int, secret: str) -> bool:
# Check timestamp (5 minute tolerance)
now = int(time.time())
if abs(now - timestamp) > 300:
return False
# Compute expected signature
signed_payload = f"{timestamp}.{payload}"
expected_signature = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(signature, expected_signature)