Webhook Events
POST/webhooks/kraken/events
This endpoint should be implemented by the partner to receive real-time event notifications from Kraken Embed.
When specific events occur (such as a quote being executed), Kraken sends a POST request to your configured webhook URL with event details.
Security
All webhook requests include an X-Signature header containing a timestamped HMAC-SHA256 signature. Partners should verify this signature to ensure the authenticity and integrity of the request.
Signature Header Format
The signature header follows the format:
t={timestamp},v1={signature}
Where:
t- Unix timestamp (seconds) when the webhook was sentv1- Hex-encoded HMAC-SHA256 signature
Example header:
X-Signature: t=1734567890,v1=5f2b3c4d1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d
Signature Verification
To verify the webhook signature:
- Extract the timestamp (
t) and signature (v1) from theX-Signatureheader - Construct the signed payload by concatenating:
{timestamp}.{raw_request_body} - Base64-decode your webhook secret (received when registering the webhook)
- Compute HMAC-SHA256 of the signed payload using the decoded secret
- Compare the computed signature (hex-encoded) with the
v1value from the header
import crypto from 'crypto';
interface ParsedSignature {
timestamp: number;
signatures: string[];
}
// Parse the X-Signature header
function parseSignatureHeader(header: string): ParsedSignature | null {
let timestamp: number | null = null;
const signatures: string[] = [];
for (const part of header.split(',')) {
if (part.startsWith('t=')) {
timestamp = parseInt(part.slice(2), 10);
} else if (part.startsWith('v1=')) {
signatures.push(part.slice(3));
}
}
if (timestamp === null) return null;
return { timestamp, signatures };
}
// Verify the webhook signature
function verifyWebhookSignature(
signatureHeader: string,
body: string,
secretBase64: string
): boolean {
const parsed = parseSignatureHeader(signatureHeader);
if (!parsed || parsed.signatures.length === 0) {
return false;
}
// Construct signed payload: {timestamp}.{body}
const signedPayload = `${parsed.timestamp}.${body}`;
// Decode base64 secret
const secretBytes = Buffer.from(secretBase64, 'base64');
// Compute HMAC-SHA256
const computedSignature = crypto
.createHmac('sha256', secretBytes)
.update(signedPayload)
.digest('hex');
// Compare with provided signatures (constant-time comparison recommended)
return parsed.signatures.some(sig =>
crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(sig)
)
);
}
Timestamp Validation
The timestamp in the signature header indicates when the webhook was sent. To prevent replay attacks, you should:
- Parse the timestamp from the
X-Signatureheader - Compare it against the current time
- Reject webhooks with timestamps older than your tolerance window (e.g., 5 minutes)
function isTimestampValid(timestamp: number, toleranceSeconds: number = 300): boolean {
const currentTime = Math.floor(Date.now() / 1000);
return Math.abs(currentTime - timestamp) <= toleranceSeconds;
}
Event Types
| Event Type | Description |
|---|---|
quote.executed | A quote has been successfully executed |
webhook.test | A test event sent via the Test Webhook endpoint |
Headers
All webhook requests include these headers:
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Signature | Timestamped HMAC-SHA256 signature (format: t={timestamp},v1={signature}) |
X-Webhook-Event | The event type (e.g., quote.executed) |
X-Webhook-ID | The webhook configuration ID |
Request
Responses
- 200
- 500
Webhook received and processed successfully. Return any 2xx status code to acknowledge receipt.
Internal server error on partner side. Kraken will retry delivery.