Developer docsAPI version 2026-11-01
https://api.neofin.tech/v1StatusGet sandbox keys

Verifying signatures

Every delivery carries a NEO-Signature header. Verify it before you trust the body.

Header format

Header
NEO-Signature: t=1793906533,v1=5f1d3c9a0e7b4a2d8c6f1e0b9a8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d

t is the Unix timestamp at which the signature was computed and v1 is the hex HMAC-SHA256 of "{t}.{raw body}" using your endpoint secret. Reject requests whose timestamp is more than five minutes old to prevent replays, and compare signatures in constant time.

Node

Node
import crypto from "node:crypto";

export function verifyNeoSignature(body, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("="))
  );
  const signed = `${parts.t}.${body}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signed)
    .digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return (
    fresh &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
  );
}

Python

Python
import hmac, hashlib, time

def verify_neo_signature(body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    signed = f"{parts['t']}.".encode() + body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return fresh and hmac.compare_digest(expected, parts["v1"])

PHP

PHP
function verifyNeoSignature(string $body, string $header, string $secret): bool
{
    $parts = [];
    foreach (explode(',', $header) as $p) { [$k, $v] = explode('=', $p, 2); $parts[$k] = $v; }
    $expected = hash_hmac('sha256', $parts['t'].'.'.$body, $secret);
    $fresh = abs(time() - (int) $parts['t']) < 300;
    return $fresh && hash_equals($expected, $parts['v1']);
}
Use the raw request body exactly as received. Re-serialising the JSON will change whitespace and break the signature.