Webhook Setup
Receive verification outcomes on your server as soon as they are available—no polling required.
What webhooks are
Webhooks are HTTPS callbacks: Kynode sends an HTTP POST to your endpoint when something happens (for example a verification completes). They give you near real-time notifications, reduce unnecessary API polling, and scale better than tight loops against the verify API.
Endpoint requirements
Register a public HTTPS URL in the Kynode dashboard. Plain HTTP is not supported. Your handler must validate the signature, return `200 OK` quickly (within about 5 seconds), and offload heavy work to a background queue. Slow endpoints risk timeouts and automatic retries.
Payload format
Each delivery is a JSON body. The cryptographic signature may also be repeated in a dedicated header (recommended); always verify using the raw request body and your webhook secret:
{
"event": "verification.completed",
"timestamp": "2026-05-16T12:00:00.000Z",
"data": {
"isValid": true,
"businessInfo": {
"businessNumber": "8090903407",
"companyName": "Nodemetrics",
"companyNameOriginal": "노드메트릭스",
"companyNameSource": "dart",
"representative": "강민구",
"status": "Active",
"statusCode": "01",
"taxType": "General VAT Taxpayer",
"taxTypeCode": "01",
"verifiedAt": 1747123456789
}
},
"signature": "sha256=abc123def456..."
}Handling webhooks in your app
Verify the signature first, then parse JSON and route by `event`. Respond with `200` immediately and process the event asynchronously so Kynode does not retry for slow work.
Node.js (Express)
import express from 'express';
import crypto from 'crypto';
const app = express();
const WEBHOOK_SECRET = process.env.KYNODE_WEBHOOK_SECRET ?? '';
app.post(
'/webhooks/kynode',
express.raw({ type: 'application/json' }),
(req, res) => {
const rawBody = req.body; // Buffer
const payload = rawBody.toString('utf8');
const headerSig = req.get('x-kynode-signature') ?? '';
const expectedHex = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
const expectedHeader = `sha256=${expectedHex}`;
const a = Buffer.from(headerSig, 'utf8');
const b = Buffer.from(expectedHeader, 'utf8');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('Invalid signature');
}
let event;
try {
event = JSON.parse(payload);
} catch {
return res.status(400).send('Invalid JSON');
}
// Acknowledge immediately — process in a worker / queue
setImmediate(() => handleKynodeEvent(event));
res.status(200).json({ received: true });
}
);
function handleKynodeEvent(event: { event: string; data?: Record<string, unknown> }) {
switch (event.event) {
case 'verification.completed':
// update DB, notify user, sync Stripe metadata, etc.
break;
case 'verification.failed':
break;
default:
break;
}
}Python (Flask)
import hashlib
import hmac
import json
import os
from flask import Flask, request, Response
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['KYNODE_WEBHOOK_SECRET'].encode('utf-8')
@app.route('/webhooks/kynode', methods=['POST'])
def kynode_webhook():
payload = request.get_data()
sig = request.headers.get('X-Kynode-Signature', '')
expected = 'sha256=' + hmac.new(WEBHOOK_SECRET, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig.encode('utf-8'), expected.encode('utf-8')):
return Response('Invalid signature', status=401)
try:
event = json.loads(payload.decode('utf-8'))
except json.JSONDecodeError:
return Response('Invalid JSON', status=400)
# Enqueue Celery/RQ work here — keep HTTP response fast
# process_kynode_event.delay(event)
return {'received': True}, 200Signature verification
Use a constant-time comparison (`crypto.timingSafeEqual` / `hmac.compare_digest`). Never log the full secret. Rotate the webhook secret if it may have leaked.
Event types
Typical event names include:
- `verification.completed` — registry data was fetched and the business is active/valid.
- `verification.failed` — verification could not be completed (invalid number, not found, upstream error).
- `verification.pending` — async pipeline: final result will arrive in a later delivery (if applicable).
Retries and errors
If your endpoint returns `5xx` or times out, Kynode will retry with backoff. Return `4xx` only for bad requests you cannot fix by retrying (after confirming the payload is malformed). Idempotently handle duplicate deliveries using `event` id or a stable idempotency key in `data`.
Local testing
Use webhook.site to inspect raw payloads during development. For localhost, run ngrok (or similar) to expose an HTTPS tunnel, register that URL temporarily in the dashboard, and exercise end-to-end deliveries before going to production.