Verify signatures

Verify the raw body and timestamp before accepting an event, then deduplicate its signed ID.

What your receiver must verify

Every attempt carries X-Kua-Signature: t={unix_seconds},v1={hex_hmac}. Kua signs HMAC-SHA256(secret, "{t}.{raw_body}"). The timestamp and exact raw body are authenticated; the other HTTP headers are not part of that HMAC.

Use your endpoint’s stored signing secret. Reject missing, malformed, stale or implausibly future signatures before trusting the payload. The SDK helpers apply a default absolute timestamp tolerance of 300 seconds. Keep the receiver clock accurate; changing this tolerance changes replay protection.

Node.js receiver

Install the source SDK and your chosen web framework. This Express transport example uses the SDK verifier; supply your own durable queue/database at the marked step.

import express from "express";
import { verifySignature } from "@kuahr/node";
const app = express();
const secret = process.env.KUA_WEBHOOK_SECRET!;
async function acceptDurably(event: { id: string; type: string }) {
  // Replace with your transactional inbox/queue; fail closed until configured.
  throw new Error("Configure durable event acceptance before enabling this receiver");
}

// Mount BEFORE any global express.json() middleware.
app.post("/kua/webhooks", express.raw({ type: "application/json" }), async (req, res) => {
  if (!verifySignature(req.body, req.header("X-Kua-Signature"), secret)) {
    return res.status(400).end();
  }
  let event;
  try { event = JSON.parse(req.body.toString("utf8")); }
  catch { return res.status(400).end(); }
  if (typeof event.id !== "string" || typeof event.type !== "string") {
    return res.status(400).end();
  }
  try { await acceptDurably(event); }
  catch { return res.status(503).end(); }
  return res.status(200).end();
});

The framework, durable storage and business handler are your application’s responsibility. This is receiver transport scaffolding, not a complete production worker. Do not deploy it with the durable-acceptance step omitted.

Python verification

Use your framework’s raw-body API before parsing JSON:

import os
from kua import verify_signature

# raw_body and signature_header come from your HTTP framework.
valid = verify_signature(
    raw_body, signature_header,
    os.environ["KUA_WEBHOOK_SECRET"],
    tolerance_seconds=300,
)
# Reject invalid requests. Only parse and durably accept the event if valid.

Deduplicate the verified event

Use event.id from the verified body as the unique key for your durable inbox or effect record. Do not trust an unsigned X-Kua-Event-Id header as the sole deduplication authority; if you use it, require it to equal the body ID.

Commit durable acceptance before returning 2xx. Couple the processed-event record with your business transaction, or use a durable inbox and an idempotent worker. A process crash between an effect and a separate dedupe write can duplicate the effect. An already accepted event may return 2xx without repeating the action. Unknown event types should be durably recorded or ignored intentionally, rather than triggering endless retries.

Delivery metadata

HeaderMeaning
X-Kua-SignatureTimestamp and HMAC over the exact raw body
X-Kua-Event-TypeRouting hint, for example payroll.run.disbursed; compare with verified body
X-Kua-Event-IdEvent-ID hint; deduplicate the verified body ID
X-Kua-Delivery-IdDelivery record; retries/replays retain it

Rotation and acceptance checks

New attempts use a rotated secret immediately, including replays of older events. There is no promised old-secret overlap. Follow signing-secret rotation and preserve the same durable event-ID dedupe across rotation.

Done when: a valid ping is accepted durably, an invalid signature is refused, an old timestamp is refused, and a duplicate verified ID produces one business effect. Then confirm delivery readback and recovery.

Search documentation

↑ ↓ to move · Enter to open · Esc to close