Skip to main content

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 sent
  • v1 - Hex-encoded HMAC-SHA256 signature

Example header:

X-Signature: t=1734567890,v1=5f2b3c4d1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d

Signature Verification

To verify the webhook signature:

  1. Extract the timestamp (t) and signature (v1) from the X-Signature header
  2. Construct the signed payload by concatenating: {timestamp}.{raw_request_body}
  3. Base64-decode your webhook secret (received when registering the webhook)
  4. Compute HMAC-SHA256 of the signed payload using the decoded secret
  5. Compare the computed signature (hex-encoded) with the v1 value 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:

  1. Parse the timestamp from the X-Signature header
  2. Compare it against the current time
  3. 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 TypeDescription
quote.executedA quote has been successfully executed
webhook.testA test event sent via the Test Webhook endpoint

Headers

All webhook requests include these headers:

HeaderDescription
Content-TypeAlways application/json
X-SignatureTimestamped HMAC-SHA256 signature (format: t={timestamp},v1={signature})
X-Webhook-EventThe event type (e.g., quote.executed)
X-Webhook-IDThe webhook configuration ID

Request

Responses

Webhook received and processed successfully. Return any 2xx status code to acknowledge receipt.