iCryptoNow

Webhooks

We send an HTTPS POST to your endpoint whenever a deposit or payout changes. Your endpoint URL and signing secret (whsec_…) are set up during onboarding.

Delivery

  • Reply with any 2xx status within 10 seconds. The response body is ignored; redirects count as failures.
  • Failed deliveries are retried with backoff (10s, 20s, 40s … up to 1 hour apart) for 24 hours.
  • Delivery is at least once and not ordered. Deduplicate on event_id and trust the status inside data.

Headers

HeaderValue
X-Event-IdSame as event_id in the body
X-Event-TypeSame as type in the body
X-TimestampUnix time in milliseconds, new on every attempt
X-SignatureHex HMAC-SHA256, see below

Events

typeWhen
deposit.detectedA payment to a player's address was seen on-chain. Not final yet: do not credit.
deposit.confirmedThe deposit is final. Credit the player with data.net (amount minus fee).
deposit.below_minThe deposit is below the minimum amount and was not credited.
deposit.flaggedThe deposit needs review, for example an asset not enabled on your account.
deposit.orphanedA detected deposit's block was reorganized away. A later deposit.confirmed may still follow.
deposit.sweptThe deposit was moved into your vault. Informational: it was already credited at deposit.confirmed.
payout.pending_approvalThe payout is waiting for manual approval.
payout.completedThe payout is confirmed on-chain. data.tx_hash is set.
payout.failedThe payout could not be sent. data.reason explains why.
payout.rejectedThe payout was rejected during approval. The reserved amount is released.
vault.topupFunds were sent straight into your vault (not via a deposit address). Credited to your balance, no fee.
vault.withdrawalFunds left your vault outside a payout, for example your own withdrawal. Debited from your balance.

Deposit events carry the deposit (deposit_id, chain, asset, decimals, address, player_ref, from, amount, tx_hash, …), see Deposits. Payout events carry the full payout object. Vault events carry vault_event_id, asset, vault, counterparty, amount and tx_hash.

deposit.confirmed
{
  "event_id": "9d4f7a12-3b6e-4c8d-a1f0-5e2b7c9d0a14",
  "type": "deposit.confirmed",
  "created_at": "2026-10-10T08:01:12.000Z",
  "data": {
    "deposit_id": "c1a5e8f2-7b3d-4e9a-8c6f-1d2e3f4a5b6c",
    "status": "confirmed",
    "chain": "tron",
    "asset": "tron:USDT",
    "decimals": 6,
    "address": "TQ9xG7c2...7kLm",
    "player_ref": "player_1024",
    "from": "TFp3...Wq8",
    "amount": "50000000",
    "fee": "1000000",
    "net": "49000000",
    "tx_hash": "7c3e...b91d",
    "log_index": 0,
    "block_number": "68421337",
    "finality": "finalized"
  }
}

Verifying signatures

The signature uses the same scheme as API requests, with your webhook secret as the key:

hex(HMAC-SHA256(whsec_secret, "{X-Timestamp}\nPOST\n{path + query of your webhook URL}\n{raw body}"))
  • Use the raw request body, before any JSON parsing.
  • Use your secret exactly as issued, including the whsec_ prefix.
  • Reject timestamps older than 5 minutes, and compare signatures in constant time.
import crypto from "node:crypto";
import express from "express";

const WEBHOOK_SECRET = process.env.ICN_WEBHOOK_SECRET; // whsec_... (use as-is)
const app = express();

// Keep the raw body: the signature covers the exact bytes we sent.
app.post("/webhooks/icryptonow", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.get("X-Timestamp");
  const raw = req.body.toString("utf8");
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(`${ts}\nPOST\n${req.originalUrl}\n${raw}`)
    .digest("hex");

  const given = Buffer.from(req.get("X-Signature") ?? "", "utf8");
  const fresh = Math.abs(Date.now() - Number(ts)) < 5 * 60 * 1000;
  if (!fresh || given.length !== 64 || !crypto.timingSafeEqual(given, Buffer.from(expected))) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(raw);
  // Deliveries are at-least-once: skip event_ids you have already processed.
  queueForProcessing(event);
  res.sendStatus(200); // reply fast; do the work asynchronously
});
Chat with us