Jjsys docs
API · Webhooks

Webhooks

jsys signs every webhook with HMAC-SHA-256 and a per-endpoint shared secret. The signature format mirrors Stripe's — easy to verify with any HMAC library.

Event types

  • order.pending — order accepted, awaiting processing
  • order.processing — worker started fulfillment
  • order.fulfilled — all line items succeeded
  • order.failed — all retries exhausted; wallet auto-refunded
  • order.cancelled — cancelled while PENDING
  • order.refunded — manual refund issued
  • wallet.topup.confirmed — admin confirmed your wire transfer
  • wallet.low_balance.warning — balance entered the 10% buffer above your threshold
  • wallet.low_balance.critical — balance dropped below your threshold

How to verify a webhook signature — required reading before you point this at a production receiver.

Signature header

X-Jsys-Signature: t=1739644800,v1=8f3a6b…

Where the signed payload is {timestamp}.{rawBody} and v1=hex(hmac_sha256(secret, signedPayload)).

Verifying in Node

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

function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!Number.isFinite(t)) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(parts.v1 ?? '', 'hex');
  return a.length === b.length && timingSafeEqual(a, b);
}

Delivery semantics

At-least-once. We retry on any non-2xx response (or transport error) with exponential backoff up to WEBHOOK_MAX_DELIVERY_ATTEMPTS (default 8). Receivers should be idempotent — the envelope's id field is unique per delivery and stable across retries.

Secret rotation

Rotate from Webhooks → endpoint → Rotate secret. The previous secret stays valid for WEBHOOK_SIGNING_SECRET_ROTATION_GRACE seconds (default 24h), so you can roll receivers without downtime.

Try it live

Run these endpoints against your sandbox with your test key.

Open in the playground