SINAI STANDARD

Webhooks

Register webhooks to receive compliance events via HTTP with HMAC-SHA256 signatures

Webhooks

The Oracle dispatches compliance events to registered webhook endpoints. Events are signed with HMAC-SHA256 so you can verify their authenticity.

Registration

Register a webhook to receive events:

const res = await fetch(`${ORACLE_URL}/v1/webhooks`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    url: "https://your-server.com/sinai-events",
    events: ["transfer.blocked", "wallet.verified", "wallet.removed"],
    mint: "optional-mint-filter...", // omit to receive events for all mints
  }),
});
 
const { webhook, secret } = await res.json();
// {
//   webhook: { id: "wh_1709..._a3f2", url: "...", events: [...], createdAt: "..." },
//   secret: "a1b2c3d4..."  <-- 32-byte hex, returned ONLY at creation
// }

Important: Store the secret immediately. It is only returned once at creation and cannot be retrieved later.

Event Types

EventDescription
transfer.executedTransfer hook executed successfully
transfer.blockedTransfer rejected by compliance (allowlist, hold period, max balance)
wallet.verifiedWallet added to allowlist
wallet.removedWallet removed from allowlist
compliance.violationCompliance rule violated
token.pausedToken paused by authority
token.resumedToken resumed by authority
config.updatedCompliance configuration changed (tax rate, hold period, etc.)

Use "*" in the events array to subscribe to all event types.

Delivery Format

When an event fires, the Oracle sends an HTTP POST to your registered URL:

// POST https://your-server.com/sinai-events
// Headers:
//   Content-Type: application/json
//   X-Sinai-Signature: <hmac-sha256-hex>
 
{
  "event": "transfer.blocked",
  "timestamp": "2026-03-04T12:00:00.000Z",
  "data": {
    "program": "Bo3Rd8qZeuxU1cmtCqKEFPRe5Uumx9tusjZ7B1hXtPgc",
    "signature": "5KtP...",
    "reason": "WalletNotAllowed"
  }
}

Mint Filtering

If you registered with a mint parameter, you only receive events for that mint. Without it, you receive events for all mints the Oracle monitors.

Retry Logic

Failed deliveries are retried with exponential backoff:

AttemptDelay
1st retry1 second
2nd retry4 seconds
3rd retry16 seconds

After 3 failed attempts, the delivery is abandoned and the webhook's failuresCount is incremented. The webhook is not automatically disabled — check failuresCount via GET /v1/webhooks to monitor health.

Signature Verification

Every delivery includes an X-Sinai-Signature header containing the HMAC-SHA256 hex digest of the request body, signed with your webhook secret.

Node.js

import crypto from "crypto";
 
function verifyWebhook(
  body: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expected, "hex")
  );
}
 
// Express middleware
app.post("/sinai-events", express.text({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-sinai-signature"] as string;
  if (!verifyWebhook(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }
 
  const event = JSON.parse(req.body);
  console.log("Event:", event.event, event.data);
  res.status(200).json({ received: true });
});

Python

import hmac
import hashlib
 
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Managing Webhooks

// List all webhooks (secrets are never exposed)
const list = await fetch(`${ORACLE_URL}/v1/webhooks`);
// { webhooks: [{ id, url, events, createdAt, failuresCount }], total }
 
// Delete a webhook
await fetch(`${ORACLE_URL}/v1/webhooks/${webhookId}`, {
  method: "DELETE",
});
// { success: true, id: "wh_..." }

On this page