Skip to main content
DevelopersWebhooks
Real-time Events

Webhooks

Push events delivered to your HTTPS endpoint with HMAC-SHA256 signatures and automatic retry.

Overview

KobKlein delivers webhook events via HTTP POST to your registered endpoint. Your endpoint must respond with a 2xx status within 10 seconds. Events not acknowledged within that window are retried with exponential backoff.

Delivery method

HTTP POST, JSON body

Timeout

10 seconds

Max retries

5 (over 24 hours)

Signature

HMAC-SHA256 in X-KK-Signature

Event Catalog

Request Headers

Every webhook delivery includes these headers:

HeaderValue
Content-Typeapplication/json
X-KK-Eventpayment.completed (the event type)
X-KK-EventIdevt_abc123 (idempotency key — deduplicate on this)
X-KK-Timestamp1783184640 (Unix seconds — reject if > 5 min old)
X-KK-SignatureHMAC-SHA256 hex digest (see below)
X-KK-Partneryour-partner-slug

Signature Verification

Always verify the X-KK-Signature before processing a webhook. The signature is computed as:
HMAC-SHA256(webhookSecret, timestamp + "." + rawBodyString)

javascript
import crypto from "crypto";

export function verifyWebhook(req, webhookSecret) {
  const signature = req.headers["x-kk-signature"];
  const timestamp  = req.headers["x-kk-timestamp"];
  const rawBody    = req.rawBody; // must be the raw bytes/string

  // Reject stale events (replay protection)
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    throw new Error("Webhook timestamp too old");
  }

  const expected = crypto
    .createHmac("sha256", webhookSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    throw new Error("Invalid webhook signature");
  }
}
python
import hmac, hashlib, time

def verify_webhook(request, webhook_secret: str):
    signature = request.headers.get("X-KK-Signature", "")
    timestamp  = request.headers.get("X-KK-Timestamp", "0")
    raw_body   = request.get_data(as_text=True)

    if abs(time.time() - int(timestamp)) > 300:
        raise ValueError("Webhook timestamp too old")

    expected = hmac.new(
        webhook_secret.encode(),
        f"{timestamp}.{raw_body}".encode(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        raise ValueError("Invalid webhook signature")

Retry Logic

If your endpoint returns a non-2xx status or times out, KobKlein retries with exponential backoff:

AttemptDelay after previous
1 (initial)
21 minute
35 minutes
430 minutes
52 hours
Event dropped after attempt 5

Deduplicate on X-KK-EventId — the same ID is used across all retry attempts for a given event.

Testing Webhooks

Use a test API key (kk_test_) to trigger test events without real money movement. You can also use webhook.site or ngrok to inspect deliveries locally.

bash
# Register a test endpoint pointing to your local server
curl https://api.kobklein.com/v1/partner/webhooks \
  -H "X-API-Key: kk_test_your_test_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://your-ngrok-id.ngrok.io/webhooks/kobklein",
    "events": ["payment.completed", "kyc.updated"],
    "secret": "local_test_secret_123"
  }'

Try it in the Sandbox Console

Use the Sandbox Console to trigger test webhook events and inspect the payloads directly in your browser — no local server required.