Drop one of these handlers into your app and you’re done. Each uses your language’s standard HMAC library to verify the Geldstuck-Signature header against the raw request body - no wrapper library required. For the full algorithm and rationale, see signatures.
Always verify against the raw body bytes before parsing JSON. Any whitespace or key-order change will break the HMAC.

Reusable verify function

Everything below builds on this tiny helper. Copy it into your codebase.
import crypto from "crypto";

export function verifyWebhook(
  rawBody: string | Buffer,
  header: string,
  secret: string,
  toleranceSec = 300,
): unknown {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=") as [string, string]),
  );
  const timestamp = Number(parts.t);
  const received  = parts.v1;
  if (!timestamp || !received) throw new Error("Malformed signature");
  if (Math.floor(Date.now() / 1000) - timestamp > toleranceSec) {
    throw new Error("Timestamp outside tolerance");
  }

  const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`, "utf8")
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    throw new Error("Invalid signature");
  }
  return JSON.parse(body);
}

Express (Node.js)

import express from "express";
import { verifyWebhook } from "./verify";

const app = express();

app.post(
  "/webhooks/geldstuck",
  express.raw({ type: "application/json" }),   // raw, not json
  (req, res) => {
    let event;
    try {
      event = verifyWebhook(
        req.body,
        req.headers["geldstuck-signature"] as string,
        process.env.GELDSTUCK_WEBHOOK_SECRET!,
      );
    } catch {
      return res.status(400).send("Invalid signature");
    }

    switch ((event as any).type) {
      case "kyc.completed":                /* ... */ break;
      case "transaction.status.changed":   /* ... */ break;
      default: console.log("Unhandled:", (event as any).type);
    }

    res.sendStatus(200);
  },
);

Next.js (App Router)

// app/api/webhooks/route.ts
import { NextResponse } from "next/server";
import { verifyWebhook } from "@/lib/verify";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

export async function POST(req: Request) {
  const body = await req.text();
  const sig  = req.headers.get("geldstuck-signature")!;

  let event;
  try {
    event = verifyWebhook(body, sig, process.env.GELDSTUCK_WEBHOOK_SECRET!);
  } catch {
    return new NextResponse("Invalid signature", { status: 400 });
  }

  await enqueue(event);
  return NextResponse.json({ received: true });
}

Flask (Python)

from flask import Flask, request
from verify import verify_webhook
import os

app = Flask(__name__)

@app.post("/webhooks/geldstuck")
def handle():
    try:
        event = verify_webhook(
            request.data,
            request.headers["Geldstuck-Signature"],
            os.environ["GELDSTUCK_WEBHOOK_SECRET"],
        )
    except ValueError:
        return "Invalid signature", 400

    if event["type"] == "kyc.completed":
        ...
    return "", 200

FastAPI (Python)

from fastapi import FastAPI, Header, Request, HTTPException
from verify import verify_webhook
import os

app = FastAPI()

@app.post("/webhooks/geldstuck")
async def handle(
    request: Request,
    geldstuck_signature: str = Header(...),
):
    payload = await request.body()
    try:
        event = verify_webhook(
            payload,
            geldstuck_signature,
            os.environ["GELDSTUCK_WEBHOOK_SECRET"],
        )
    except ValueError:
        raise HTTPException(400, "Invalid signature")

    return {"received": True}

Gin (Go)

func HandleWebhook(c *gin.Context) {
    body, _ := io.ReadAll(c.Request.Body)
    sig := c.GetHeader("Geldstuck-Signature")
    secret := os.Getenv("GELDSTUCK_WEBHOOK_SECRET")

    event, err := VerifyWebhook(body, sig, secret)
    if err != nil {
        c.AbortWithStatus(http.StatusBadRequest)
        return
    }

    switch event["type"] {
    case "kyc.completed":
        // ...
    }
    c.Status(http.StatusOK)
}
Where VerifyWebhook is the Go equivalent of the helper in signatures.

Rails (Ruby)

# config/routes.rb
post "/webhooks/geldstuck", to: "webhooks#geldstuck"

# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def geldstuck
    payload = request.body.read
    sig     = request.headers["Geldstuck-Signature"]

    event = verify_webhook(payload, sig, ENV.fetch("GELDSTUCK_WEBHOOK_SECRET"))

    case event["type"]
    when "kyc.completed" then KycMailer.completed(event["data"]).deliver_later
    end

    head :ok
  rescue => e
    head :bad_request
  end
end

Cloudflare Workers

export default {
  async fetch(req: Request, env: Env) {
    const body = await req.text();
    const sig  = req.headers.get("geldstuck-signature")!;

    try {
      const event = verifyWebhook(body, sig, env.GELDSTUCK_WEBHOOK_SECRET);
      await env.EVENT_QUEUE.send(event);
    } catch {
      return new Response("Invalid signature", { status: 400 });
    }
    return new Response("ok");
  },
};
Webhook verification is the one place you should not get creative. Copy the reference helper, add tests, move on.