coinkraft.io
Back to blog
Security2026-05-03· 5 min

How to verify coinkraft.io webhooks in 8 lines of code

Every callback we send carries a Coinkraft-Signature header with t=<unix-ts>,v1=<hex-sha256>. Here's the minimum code to verify it correctly in your language of choice — including the one mistake that turns your signed webhook into an unsigned one.

The verification algorithm

Split the header on commas to extract t (timestamp) and v1 (signature). Compute HMAC-SHA256 over the literal string `${t}.${rawBody}` using your per-merchant secret. Compare in constant time. Reject if the timestamp is older than 5 minutes.

That's it. Don't use === to compare — that leaks bytes via timing. Don't parse the body before verifying — once it's JSON it's tampered with.

Node.js

Use crypto.timingSafeEqual on Buffer values of the same length:

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret: string, rawBody: string, header: string) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = parts.t, v1 = parts.v1;
  if (!t || !v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

The one mistake

Frameworks like Express auto-parse JSON. If you read req.body after that, you're reading a re-stringified object — not the bytes we signed. Use express.raw({ type: 'application/json' }) on the webhook route only, or grab the raw stream before any middleware runs. Same trick applies to Hono, Fastify, FastAPI — anywhere middleware sits between the socket and your handler.

← All postsReply by email →
coinkraft.io — The forge for crypto payments