Agent skill
Check Webhooks Skill
Receive and verify Check (checkhq.com) payroll API webhooks. Use when setting up Check webhook handlers, debugging Check-Signature verification, or handling payroll, payment and employee events like payroll updated, payment status_change, and employee created.
Install this skill
npx skills add hookdeck/webhook-skills --skill check-hq-webhooks
Check (checkhq.com) is a payroll API. This skill covers its webhooks, not generic "check" events.
When to Use This Skill
- How do I receive Check payroll webhooks?
- How do I verify the
Check-Signatureheader? - Why does my Check webhook signature verification fail?
- How do I handle payroll
updated, paymentstatus_change, or employeecreatedevents? - How do I test a Check Webhook Config with the ping endpoint?
Verification (core)
Check sends Check-Signature: the base-16 (hex) HMAC-SHA256 of the raw request body, keyed with the Webhook Config's key. Use the key as UTF-8 text (it looks like 40 hex characters but is not hex-decoded). There is no timestamp in the signed content. Check has no SDK for this, so verify manually.
Node:
const crypto = require('crypto');
function verify(rawBody, signatureHeader, key) {
if (!signatureHeader) return false;
const expected = crypto.createHmac('sha256', key).update(rawBody).digest('hex');
const a = Buffer.from(signatureHeader.trim().toLowerCase());
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python:
import hmac, hashlib
def verify(raw_body: bytes, signature_header: str, key: str) -> bool:
expected = hmac.new(key.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), (signature_header or "").strip().lower().encode())
In Express read the body with express.raw({ type: '*/*' }) so any content type verifies against the exact bytes.
For complete handlers with route wiring, event dispatch, and tests, see:
Payload and Headers
The body is {"event": "...", "data": {...}}. The resource type (the "topic") is not in the body; it is in the Check-Topic header, so dispatch on topic + event. The ping test webhook is the one exception: a flat {"message": "Test webhook"}.
| Header | Description |
|---|---|
Check-Signature | Hex HMAC-SHA256 of the raw body (verify this one) |
Check-Topic | Category of the webhook, e.g. payroll, payment, employee, ping |
Check-WebhookEvent-ID | Unique ID; store it to ignore duplicates |
Check-Live | true for live, false for sandbox |
Check also still sends deprecated X-... duplicates of these (such as X-Live). Ignore them and verify Check-Signature.
Common Event Types
Topic (Check-Topic) | Event | Description |
|---|---|---|
payroll | created | A payroll was created |
payroll | updated | A payroll was updated (API version 2025-01-01 or later) |
payroll | status_change | Payroll status changed (deprecated in API version 2025-01-01) |
payment | status_change | Payment moved to processing, paid, failed or refunded |
employee | created | An employee was created |
employee | updated | An employee was updated (also used for termination) |
bank_account | created / updated | Bank account changes |
For the full topic and event list, see Webhook event types
Environment Variables
CHECK_WEBHOOK_KEY=your_webhook_config_key # "key" from POST /webhook_configs
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 check-hq --path /webhooks/check-hq
Reference Materials
- references/overview.md - Check webhook concepts, topics and events
- references/setup.md - Create a Webhook Config and get the key
- references/verification.md - Signature verification details and gotchas