KKharchai
All posts

How to Create and Verify Kharchai Webhooks

July 12, 2026

Kharchai webhooks push events to your server the moment they happen, so you do not have to poll the API for changes. This guide covers both halves of that: setting up an endpoint from the dashboard, and the code you need on your side to trust what arrives at it.

Prerequisites

Webhooks are available on the Pro and Custom plans. One thing worth knowing before you start: webhook endpoints can only be created, edited, or rotated from the dashboard under your own logged in session. An API key can never touch this surface, even a key with every scope granted. That is deliberate: if a key ever leaks, it should never be enough on its own to add a listening endpoint and start receiving expense and payment data.

Reference: event types

Event Available on
expense.submitted Pro, Custom
expense.approved Pro, Custom
expense.rejected Pro, Custom
payment.processed Pro, Custom
payment.failed Pro, Custom
approval.requested Custom
fiscal_year.activated Custom

Step 1: Create an endpoint

In the dashboard, go to Settings, then Webhooks, and add an endpoint with three things: a name, a URL, and the events you want to subscribe to. The URL must be HTTPS. When you save, Kharchai shows you a signing secret that starts with whsec_. Copy it now: it is shown exactly once and stored encrypted on our side, so if you lose it your only option is to rotate to a new one.

Step 2: Know what you will receive

Every delivery is an HTTP POST with a JSON body shaped like this:

{
  "id": "evt_5f2c9a1b3d4e4f2a8b6c7d9e0f1a2b3c",
  "event": "expense.approved",
  "apiVersion": "v1-2026/07/08",
  "createdAt": "2026-07-12T09:14:02.000Z",
  "organizationId": 481,
  "branchId": 12,
  "data": { }
}

apiVersion identifies the payload contract. It only changes when the shape of data changes, so you can safely branch on it if you ever need to support two contract versions during a migration.

On top of standard headers like Content-Type, three Kharchai-specific headers travel with every delivery:

  • X-Kharchai-Event: the same value as the event field, so you can route without parsing the body first
  • X-Kharchai-Delivery: a unique ID for this specific delivery attempt
  • X-Kharchai-Signature: t=<unix timestamp>,v1=<hex HMAC>

Step 3: Verify the signature

The signature is an HMAC-SHA256 of the string {timestamp}.{raw body}, keyed with your whsec_ secret, hex encoded. Here is the whole check in Node.js:

const crypto = require("crypto");

function isValidKharchaiSignature(secret, signatureHeader, rawBody, toleranceSeconds = 300) {
  const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
  const timestamp = Number(parts.t);
  if (!parts.v1 || !Number.isFinite(timestamp)) return false;

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

  const sameLength = expected.length === parts.v1.length;
  const matches = sameLength && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  const fresh = Math.abs(Date.now() / 1000 - timestamp) <= toleranceSeconds;

  return matches && fresh;
}

Two details matter more than they look:

rawBody has to be the exact bytes you received, before your framework parses it into an object. If you compute the signature over JSON.stringify(JSON.parse(rawBody)) instead, a difference in key order or whitespace changes the hash and every delivery will look invalid. This trips up almost everyone the first time: our own worker signs the exact string it sends, not a re-serialized copy, for this exact reason.

crypto.timingSafeEqual instead of a plain equality check matters because a naive string comparison exits early on the first mismatched character, and an attacker who can measure response time closely enough can use that to guess a valid signature one character at a time. It costs nothing to get right.

The tolerance check on the timestamp is your defense against replay: without it, anyone who ever captures one valid signed request, from a compromised logging tool, a misconfigured proxy, or open browser devtools, can resend it forever and your server will accept it as new every time. Five minutes is a reasonable default.

Step 4: Handle retries idempotently

A failed delivery is retried automatically with exponential backoff, up to 8 attempts total, roughly 2, 4, 8, 16, 32, 64, and 128 minutes apart, before we give up and mark it dead lettered. Any response outside the 200 to 299 range counts as a failure and triggers a retry, including redirects, so make sure your endpoint returns 200 directly rather than redirecting somewhere else.

This means your handler can receive the same event more than once, for example if your server accepted the request and then crashed before finishing its own processing. Use the id field in the envelope, or the X-Kharchai-Delivery header, as an idempotency key: store it against a processed flag before doing anything with side effects, and skip anything you have already seen.

The other side of this is speed. We time out a delivery attempt after 10 seconds, so acknowledge it with a 200 as soon as you have durably queued it, then do the actual work asynchronously. A slow synchronous handler that takes longer than that looks identical to a failure from our side and gets retried, even though your server did receive it.

Troubleshooting

Every webhook endpoint has a delivery log in the dashboard: every attempt, its HTTP status, a snippet of the response body, and when the next retry is scheduled. If you are debugging a new integration, use the Test button first. It sends a synthetic ping event through the exact same signing and delivery path as a real event, without needing a real expense or payment to trigger it.

One more thing worth knowing: if an endpoint fails five deliveries in a row, Kharchai automatically disables it and emails your organization admins, so a broken integration cannot silently pile up months of missed events. Fix the endpoint and switch it back on from the same settings page to resume delivery.

Ready to bring your company spending under control?

Join the waitlist
How to Create and Verify Kharchai Webhooks | Kharchai