Agent skill

Kombo Webhooks Skill

Receive and verify Kombo webhooks. Use when setting up Kombo webhook handlers, debugging X-Kombo-Signature verification, or handling unified HRIS/ATS/LMS events like data-changed, integration-created, integration-state-changed, or issue-status-changed.

Install this skill

npx skills add hookdeck/webhook-skills --skill kombo-webhooks


When to Use This Skill

  • How do I receive Kombo webhooks?
  • How do I verify the X-Kombo-Signature header?
  • How do I handle data-changed or integration-state-changed events?
  • Why is my Kombo webhook signature verification failing?

Verification (core)

Kombo signs the raw request body (JSON with two-space indentation) with HMAC-SHA256. The key is your Kombo Webhook Secret used as its UTF-8 bytes (do not base64-decode it). The digest is sent in X-Kombo-Signature as base64url without padding. Encode your HMAC as base64url, strip any = padding, and compare it with the header text in constant time. Never re-serialize the parsed JSON: use the exact bytes received.

const crypto = require('crypto');

function verifyKomboWebhook(rawBody, signatureHeader, secret) {
  if (!signatureHeader) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('base64url');
  const provided = Buffer.from(signatureHeader.replace(/=+$/, ''));
  return provided.length === expected.length && crypto.timingSafeEqual(provided, Buffer.from(expected));
}
import base64, hashlib, hmac

def verify_kombo_webhook(raw_body: bytes, signature_header: "str | None", secret: str) -> bool:
    if not signature_header:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
    # Compare base64url text (Kombo strips the padding) so trailing junk can't decode to a match
    expected_b64 = base64.urlsafe_b64encode(expected).rstrip(b"=")
    return hmac.compare_digest(signature_header.rstrip("=").encode(), expected_b64)

Kombo has no webhook-verification SDK, so verify manually. In Express read the body with express.raw({ type: '*/*' }) so any content type is verified.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

The event name is the type field in the JSON body (there is no event header). Payload shape: { "id": "...", "type": "<event>", "data": { ... } }.

Event (type)Triggered When
data-changedData changed in Kombo (sync or upstream webhook); debounced to one per 30s
sync-finishedA sync finished (deprecated; use data-changed)
integration-createdAn integration is created
integration-deletedAn integration is deleted
integration-state-changedIntegration state becomes ACTIVE, INVALID or INACTIVE
connection-flow-failedAn error occurred in the connection flow (can still succeed later)
issue-status-changedKombo raises or resolves an issue (change: raised / resolved)
assessment:order-receivedAn assessment is ordered for a candidate in the customer's tool

Treat type as an open set: acknowledge unknown events with 200.

For the full event reference, see Kombo webhook docs.

Important Headers

HeaderDescription
X-Kombo-SignatureUnpadded base64url HMAC-SHA256 of the raw body; use this to verify

Environment Variables

KOMBO_WEBHOOK_SECRET=your_kombo_webhook_secret   # Kombo dashboard > Configuration > Webhooks (per environment)

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 kombo --path /webhooks/kombo

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →