← Developers
Start here
QuickstartSDKAuthentication
Identity
Sign in with Shin ID
Access
EntitlementsUsage & Qualified Active Days
Money
Purchases & refundsWebhooksVerifying signatures
Selling
Importing a productGitHub importSelling without the SDKCurrencies and pricingBuy with Guiru from your own site
Operating
Integration diagnosticsErrors & rate limitsTestingMobile API
Reference
ReferenceVersioning & changesFor agents
Machine-readable
llms.txt
Money

Verifying signatures

HMAC-SHA256 over timestamp and raw body, compared in constant time, inside a five-minute window.

The guiru-signature header is t=<unix seconds>,v1=<hex hmac>. The signed message is the timestamp, a dot, and the raw request body — the exact bytes we sent, before any JSON parsing or re-serialisation by your framework.

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

export function verify(secret: string, rawBody: string, header: string, toleranceSec = 300): boolean {
  const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  const ts = Number(parts.t);
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > toleranceSec || !parts.v1) return false;
  const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
Python
import hmac, hashlib, time

def verify(secret: str, raw_body: bytes, header: str, tolerance_sec: int = 300) -> bool:
    parts = dict(kv.split("=", 1) for kv in header.split(","))
    try:
        ts = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - ts) > tolerance_sec:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))
Careful

Read the body as text or bytes before your framework parses it. JSON.stringify(req.body) is a different string from what we signed — key order and whitespace survive the wire and do not survive a round trip.

The five-minute window

Reject a signature whose timestamp is more than five minutes away from now, in either direction. That is what makes a captured delivery useless to replay, and it is why an endpoint with a badly wrong clock fails every verification while looking perfectly healthy.

Rotating the secret

During a rotation window, deliveries carry both guiru-signature (new secret) and guiru-signature-previous (old). Accept either while you deploy, then drop the old one. Nothing breaks if you roll slowly; everything breaks if you roll a secret with no window and one instance still holding the old value.

PreviousWebhooksNextImporting a product
Guiru
AboutStoreHow Guiru worksQuality BarGSPGuiru MemberStatus
Buying
Buy with GuiruYour libraryRefundsBuyer termsTrust & safety
Builders
For buildersSubmit a productGuiru ProPricing & feesPromoteDeveloper docs
Help
Help centreYour libraryBuying & refundsHow a product gets listedHow ranking worksReport a problem
Legal
All policiesTermsPrivacyCookiesCreator PoolAffiliate disclosure
Your account
Account settingsSign in
Shin AI
Shin WebsiteKatsuiTrouble? Message Mario
Find your next favourite. Software, games and digital products on Guiru.Guiru Beta · Feedback© 2026 Shinai Tecnologias I.S.
Beta
StoreLibraryBuildersMember
Sign in
StoreSearchLibraryMemberSign in