1 Overview
Introduction to the Webhook event system.
CRYPTOMENTS sends real-time notifications to your configured Webhook URL when key events occur — such as deposit confirmation or withdrawal completion. This lets partners keep their systems in sync with CRYPTOMENTS.
Key Features
- Real-time notifications: Sent immediately when an event occurs.
- HMAC-SHA256 signing: Every Webhook request is signed with the partner's API Secret for verification.
- Automatic retry: If there's no response within 5 seconds or an error occurs, the request is retried automatically with exponential backoff.
- Idempotency guaranteed: Duplicate processing is prevented based on
transactionHash.
Request Model
CRYPTOMENTS Webhook requests follow this structure:
- Method: HTTP POST
- Content-Type: application/json
- Timeout: 5 seconds
- Success response: HTTP 200 OK
2 Configuration
How to configure and manage your Webhook URL.
Webhook URL Setup
You can configure your Webhook from the Partner Console.
Sign in to the Partner Console.
Navigate to .
Enter your callback URL on the Webhook tab.
Click the "Send test" button to verify that your endpoint receives the event correctly.
3 Authentication
How to verify the signature of a Webhook request.
Signature Verification (HMAC-SHA256)
CRYPTOMENTS signs every Webhook request with HMAC-SHA256. Your receiver must verify the signature to confirm the request's origin and integrity.
Signature Data Composition
The signature is generated by joining the core fields with a pipe (|) delimiter:
partnerId|transactionHash|amount|timestamp
Signature Formula
signature = Hex(HMAC-SHA256(api_secret, "partnerId|txHash|amount|timestamp"))
- Simple, clear structure
- Easy for partners to implement
- Easy to debug
timestampprevents replay attacks
Webhook Request Format
| Item | Description | Example |
|---|---|---|
Content-Type header |
Content type | application/json |
signature (body field) |
HMAC-SHA256 signature (hex-encoded) | a1b2c3d4e5f6... |
timestamp (body field) |
Unix timestamp (seconds) | 1710508200 |
transactionHash (body field) |
Transaction hash (idempotency key) | 0xabc123def456... |
API Secret. You can find it in the Partner Console under Integrations.
Verification Implementation (JavaScript)
const crypto = require('crypto'); function verifySignature(body, apiSecret) { const { partnerId, transactionHash, amount, timestamp, signature } = body; // Build signature data (delimiter |) const signatureData = `${partnerId}|${transactionHash}|${amount}|${timestamp}`; const computed = crypto .createHmac('sha256', apiSecret) .update(signatureData) .digest('hex'); return computed === signature; } app.post('/webhook', (req, res) => { const apiSecret = process.env.API_SECRET; if (!verifySignature(req.body, apiSecret)) { return res.status(401).json({ error: 'Signature mismatch' }); } res.status(200).json({ success: true }); });
Verification Implementation (Python)
import hmac import hashlib from flask import Flask, request app = Flask(__name__) API_SECRET = 'your_api_secret' @app.route('/webhook', methods=['POST']) def webhook(): body = request.get_json() # Build signature data (delimiter |) signature_data = f"{body['partnerId']}|{body['transactionHash']}|{body['amount']}|{body['timestamp']}" computed = hmac.new( API_SECRET.encode(), signature_data.encode(), hashlib.sha256 ).hexdigest() # Verify signature if computed != body.get('signature'): return {'error': 'Signature mismatch'}, 401 return {'success': True}, 200
Verification Implementation (Java)
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Map; @PostMapping("/webhook") public ResponseEntity<?> receiveWebhook(@RequestBody Map<String, Object> body) { try { String apiSecret = System.getenv("API_SECRET"); // Build signature data (delimiter |) String signatureData = body.get("partnerId") + "|" + body.get("transactionHash") + "|" + body.get("amount") + "|" + body.get("timestamp"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec( apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256" )); byte[] hash = mac.doFinal(signatureData.getBytes(StandardCharsets.UTF_8)); // hex encode StringBuilder hex = new StringBuilder(); for (byte b : hash) { hex.append(String.format("%02x", b)); } if (!hex.toString().equals(body.get("signature"))) { return ResponseEntity.status(401).build(); } return ResponseEntity.ok().build(); } catch (Exception e) { return ResponseEntity.status(500).build(); } }
4 Event Types
Event types sent via Webhook.
Click an event card below to inspect its JSON payload.
{
"eventType": "DEPOSIT_CONFIRMED",
"transactionId": 245,
"partnerId": "7",
"userId": "user_001",
"transactionHash": "0xabc123def456...",
"fromAddress": "0x123456789...",
"toAddress": "0x987654321...",
"amount": "1000.000000",
"currencyType": "USDT",
"chainType": "BSC",
"depositMethod": "HD_WALLET",
"status": "CONFIRMED",
"confirmedAt": "2026-03-15T14:30:00",
"tokenKrwPrice": "1350.0",
"tokenUsdPrice": "1.0",
"krwAmount": "1350000",
"usdAmount": "1000.00",
"timestamp": "1710508200",
"signature": "a1b2c3d4e5f6789..."
}eventType— Event type (DEPOSIT_CONFIRMED / WITHDRAWAL_CONFIRMED / WITHDRAWAL_FAILED)transactionId— Internal transaction ID (Long). Used in deposit/withdrawal lookup APIspartnerId— Partner IDuserId— Partner-side user IDdepositMethod— Deposit method (see table below)krwAmount/usdAmount— Deposit amount converted to KRW/USDsignature— HMAC-SHA256 signature (for verification)
HD_WALLET— Deposit to an HD-derived wallet addressEXTERNAL_WALLET— Deposit from a linked external wallet (e.g. Axim)DECIMAL_MATCH— Decimal-tail matching depositDIRECT— Direct top-up by the partnerMANUAL— Manual entry by an administrator
{
"eventType": "WITHDRAWAL_CONFIRMED",
"transactionId": 127,
"partnerId": "7",
"userId": "user_002",
"transactionHash": "0xdef789abc123...",
"fromAddress": null,
"toAddress": "0x789012345...",
"amount": "500.000000",
"currencyType": "USDC",
"chainType": "POLYGON",
"status": "CONFIRMED",
"confirmedAt": "2026-03-15T15:00:00",
"tokenKrwPrice": "1350.0",
"tokenUsdPrice": "1.0",
"krwAmount": "675000",
"usdAmount": "500.00",
"feeAmount": "0",
"timestamp": "1710508200",
"signature": "a1b2c3d4e5f6789..."
}feeAmount— Fee deducted from the transferstatus— Always "CONFIRMED"fromAddress— null for withdrawals (sent from the MASTER wallet)
{
"eventType": "WITHDRAWAL_FAILED",
"transactionId": 128,
"partnerId": "7",
"userId": "user_003",
"transactionHash": null,
"fromAddress": null,
"toAddress": "T9yD14Nj9j7xAB4dbGeiX9h8...",
"amount": "200.000000",
"currencyType": "USDT",
"chainType": "TRON",
"status": "FAILED",
"confirmedAt": null,
"tokenKrwPrice": "1350.0",
"tokenUsdPrice": "1.0",
"krwAmount": "270000",
"usdAmount": "200.00",
"feeAmount": "0",
"timestamp": "1710508200",
"signature": "a1b2c3d4e5f6789..."
}status— "FAILED" (withdrawal failed)transactionHash— May be null on failure (failed before TX broadcast)confirmedAt— null on failure- Payload structure is the same as WITHDRAWAL_CONFIRMED; distinguished by the
statusvalue
📋 Payload Field Reference
Fields common to every Webhook event.
| Field | Type | Required | Description |
|---|---|---|---|
eventType |
String | ✅ | Event type. One of DEPOSIT_CONFIRMED, WITHDRAWAL_CONFIRMED, WITHDRAWAL_FAILED |
transactionId |
Long | ✅ | Internal transaction ID. Used by deposit/withdrawal lookup APIs |
partnerId |
String | ✅ | Partner unique ID |
userId |
String | ✅ | Partner-registered user ID |
transactionHash |
String | ❌ | On-chain transaction hash. Used as the idempotency key. May be null on withdrawal failure |
fromAddress |
String | ❌ | Sender address. Sender wallet for deposits, null for withdrawals (sent from MASTER wallet) |
toAddress |
String | ✅ | Recipient address. Partner wallet for deposits, withdrawal target for withdrawals |
amount |
String | ✅ | Transaction amount (token units, decimal string) |
currencyType |
String | ✅ | Token symbol. USDT, USDC, etc. |
chainType |
String | ✅ | Blockchain network. BSC, ETHEREUM, POLYGON, TRON, etc. |
depositMethod |
String | ❌ | Deposit method. Included only in deposit events (see table below) |
status |
String | ✅ | Transaction status (see table below) |
confirmedAt |
String | ❌ | Block confirmation time (yyyy-MM-dd'T'HH:mm:ss). null if unconfirmed |
tokenKrwPrice |
String | ✅ | KRW price per token |
tokenUsdPrice |
String | ✅ | USD price per token |
krwAmount |
String | ✅ | KRW value of the amount (amount × tokenKrwPrice, integer) |
usdAmount |
String | ✅ | USD value of the amount (amount × tokenUsdPrice, 2 decimal places) |
feeAmount |
String | ❌ | Fee amount. Included only in withdrawal events (currently defaults to "0") |
timestamp |
String | ✅ | Event timestamp (Unix seconds). Used for signature verification |
signature |
String | ✅ | HMAC-SHA256 signature (hex-encoded). See Authentication for verification |
tokenKrwPrice, tokenUsdPrice, krwAmount, and usdAmount are all sent as "0".
📡 eventType Values
| Value | Description | When triggered |
|---|---|---|
DEPOSIT_CONFIRMED |
Deposit confirmed | When a deposit transaction is confirmed on-chain |
WITHDRAWAL_CONFIRMED |
Withdrawal complete | When a withdrawal transaction is confirmed on-chain |
WITHDRAWAL_FAILED |
Withdrawal failed | When an error occurs during withdrawal (insufficient gas, balance, chain error, etc.) |
🔄 status Values
Deposits and withdrawals follow separate status flows. The status sent in the Webhook is the current status of that transaction.
Deposit Status
| Value | Description | Webhook sent |
|---|---|---|
| DETECTED | Deposit transaction detected on-chain (before block confirmation) | ❌ |
| CONFIRMING | Block confirmation in progress (waiting for required confirmations) | ❌ |
| CONFIRMED | Confirmed on-chain. Deposit is valid | ✅ DEPOSIT_CONFIRMED |
| NOTIFIED | Webhook successfully delivered to the partner | ❌ (internal) |
| COLLECTING | Sweeping funds from HOT/POOL wallet to MASTER wallet | ❌ (internal) |
| SETTLED | Final state. Sweep finalized and included in settlement | ❌ (internal) |
| FAILED | Deposit processing failed (chain error, confirmation failure, etc.) | ❌ |
CONFIRMED. Subsequent state changes (NOTIFIED → COLLECTING → SETTLED) do not trigger additional Webhooks.
Withdrawal Status
| Value | Description | Webhook sent |
|---|---|---|
| REQUESTED | Withdrawal request received | ❌ |
| PENDING_APPROVAL | Waiting for admin approval | ❌ |
| REJECTED | Rejected by an administrator | ❌ |
| APPROVED | Approved by an administrator | ❌ |
| PROCESSING | Withdrawal processing (transaction creation & signing) | ❌ |
| BROADCASTING | Broadcasting transaction to the blockchain | ❌ |
| CONFIRMED | Withdrawal transaction confirmed on-chain | ✅ WITHDRAWAL_CONFIRMED |
| FAILED | Error during withdrawal (insufficient gas, balance, chain error, etc.) | ✅ WITHDRAWAL_FAILED |
| CANCELLED | Withdrawal cancelled (by user or system) | ❌ |
| STALE | Unconfirmed for more than 1 hour. Flagged by the safety-net scheduler for manual review | ❌ |
CONFIRMED (success) or FAILED (failure). Intermediate states (REQUESTED → APPROVED → PROCESSING, etc.) do not trigger Webhooks — check the Partner Console for those.
💰 depositMethod Values
A field included only in DEPOSIT_CONFIRMED events, indicating how the deposit was made.
| Value | Description |
|---|---|
HD_WALLET |
Deposit via an HD-derived wallet address. Each user gets a unique address; the depositor is identified by the TO address. |
EXTERNAL_WALLET |
Deposit from an externally linked wallet (e.g. Axim). The depositor is identified by the FROM address. |
DECIMAL_MATCH |
Decimal-tail matching deposit. A unique 4-digit decimal tail is assigned per depositor on a shared address. |
DIRECT |
Direct top-up by the partner. The partner sends tokens directly to the MASTER wallet. |
MANUAL |
Manual entry by an administrator. Used when an inbound deposit can't be processed automatically. |
PHONEPAY |
PhonePay (BanqPipe) KRW deposit. An indirect deposit via KRW payment. |
⛓️ chainType / currencyType Values
Supported blockchain networks and tokens.
chainType (blockchain network)
| Value | Network | Notes |
|---|---|---|
BSC | BNB Smart Chain (BEP-20) | BNB gas, low fees |
ETHEREUM | Ethereum (ERC-20) | ETH gas |
POLYGON | Polygon PoS (ERC-20) | MATIC gas, low fees |
TRON | TRON (TRC-20) | Energy/bandwidth based, can be gasless |
currencyType (token)
| Value | Token | Supported chains |
|---|---|---|
USDT | Tether USD | BSC, ETHEREUM, POLYGON, TRON |
USDC | USD Coin | BSC, ETHEREUM, POLYGON |
5 Retry Policy
How automatic retries work when Webhook delivery fails.
If your server does not respond with HTTP 200 within 5 seconds, CRYPTOMENTS retries up to 5 times. Each retry interval grows with exponential backoff.
Retry Schedule
After All Retries Fail
If all retries fail, the event is marked as FAILED. Partners can view it in the "Failed Webhooks" section of the console and trigger a manual re-send.
6 Telegram Alerts
Receive real-time notifications via a Telegram bot alongside Webhooks.
In addition to Webhook events, CRYPTOMENTS supports two-channel notifications via a Telegram bot. Webhooks are for system integration; Telegram is for operators to monitor status in real time.
Supported Notification Types
| Notification | Description | Example fields |
|---|---|---|
| Deposit detected | Deposit TX detected on-chain (awaiting confirmation) | Deposit code, amount, network, TX hash |
| Deposit confirmed | Deposit confirmed on-chain | Deposit code, amount, network, TX hash |
| Large deposit | Deposit exceeding the alert threshold | Deposit code, amount, threshold, network |
| Withdrawal requested | New withdrawal request received | Withdrawal code, amount, recipient, status |
| Withdrawal approved | Withdrawal request approved | Withdrawal code, amount, approver |
| Withdrawal rejected | Withdrawal rejected by an administrator | Withdrawal code, amount, reason |
| Withdrawal completed | Withdrawal confirmed on-chain | Withdrawal code, amount, recipient, TX hash |
| Withdrawal failed | Error during withdrawal processing | Withdrawal code, amount, network, reason |
| Withdrawal cancelled | Withdrawal request cancelled | Withdrawal code, amount |
@CryptomentsBot in Telegram, then authenticate with the /start or /verify command. See Partner Guide > Integrations for details.
7 Idempotency
How to prevent duplicate processing.
Network delays and retries can cause the same event to be delivered multiple times. To prevent this, every event payload includes a transactionHash that you use as an idempotency key.
Idempotency Implementation
Your receiver should guarantee idempotency as follows:
Save the transactionHash of every processed event in your database.
On receiving a Webhook, first check whether that transactionHash has already been processed.
If it's a duplicate, return HTTP 200 immediately (not an error).
Idempotency Example (JavaScript)
app.post('/webhook', async (req, res) => { const txHash = req.body.transactionHash; try { // 1. Check for duplicate transaction hash const existing = await db.webhookEvents.findOne({ tx_hash: txHash }); if (existing) { return res.status(200).json({ success: true, message: 'Event already processed' }); } // 2. Verify signature (omitted) // 3. Handle event await processEvent(req.body); // 4. Store transaction hash await db.webhookEvents.insertOne({ tx_hash: txHash, event_type: req.body.eventType, processed_at: new Date() }); res.status(200).json({ success: true }); } catch (error) { console.error(error); res.status(500).json({ error: 'Internal error' }); } });
CREATE UNIQUE INDEX idx_tx_hash ON webhook_events(tx_hash);
8 Receiver Implementation
Guide to implementing a Webhook receiver server.
Best Practices
- Respond fast: Reply with HTTP 200 immediately on receiving the Webhook, and handle actual processing in a background job (queue, message broker).
- Verify signature: Verify the signature on every request to confirm the origin.
- Idempotency: Use
transactionHashto prevent duplicate processing. - Logging: Log every Webhook received and its processing outcome.
- Error handling: Design failures to be retryable.
Node.js (Express) Example
const express = require('express'); const crypto = require('crypto'); const Bull = require('bull'); const app = express(); const webhookQueue = new Bull('webhooks'); const API_SECRET = process.env.API_SECRET; app.use(express.json()); // Signature verification helper function verifySignature(body) { const { partnerId, transactionHash, amount, timestamp, signature } = body; const signatureData = `${partnerId}|${transactionHash}|${amount}|${timestamp}`; const computed = crypto .createHmac('sha256', API_SECRET) .update(signatureData) .digest('hex'); return computed === signature; } // Webhook handler app.post('/webhook', async (req, res) => { // 1. Verify signature if (!verifySignature(req.body)) { return res.status(401).json({ error: 'Unauthorized' }); } // 2. Check for duplicates (by transactionHash) const txHash = req.body.transactionHash; const exists = await db.webhookEvents.findOne({ tx_hash: txHash }); if (exists) { return res.status(200).json({ success: true }); } // 3. Enqueue and respond immediately await webhookQueue.add(req.body); res.status(200).json({ success: true }); }); // Background job webhookQueue.process(async (job) => { const data = job.data; try { switch (data.eventType) { case 'DEPOSIT_CONFIRMED': await handleDepositConfirmed(data); break; case 'WITHDRAWAL_CONFIRMED': await handleWithdrawalConfirmed(data); break; case 'WITHDRAWAL_FAILED': await handleWithdrawalFailed(data); break; } await db.webhookEvents.insertOne({ tx_hash: data.transactionHash, event_type: data.eventType, processed_at: new Date() }); } catch (error) { console.error('Webhook processing error:', error); throw error; } }); app.listen(3000);
Python (Flask) Example
from flask import Flask, request, jsonify from celery import Celery import hmac import hashlib app = Flask(__name__) celery = Celery(app.name, broker='redis://localhost:6379') API_SECRET = 'your_api_secret' def verify_signature(body): signature_data = f"{body['partnerId']}|{body['transactionHash']}|{body['amount']}|{body['timestamp']}" computed = hmac.new( API_SECRET.encode(), signature_data.encode(), hashlib.sha256 ).hexdigest() return computed == body.get('signature') @app.route('/webhook', methods=['POST']) def receive_webhook(): body = request.get_json() # 1. Verify signature if not verify_signature(body): return jsonify({'error': 'Unauthorized'}), 401 # 2. Check for duplicates tx_hash = body.get('transactionHash') if db.webhook_events.find_one({'tx_hash': tx_hash}): return jsonify({'success': True}), 200 # 3. Process async and respond immediately process_webhook.delay(body) return jsonify({'success': True}), 200 @celery.task def process_webhook(data): event_type = data['eventType'] try: if event_type == 'DEPOSIT_CONFIRMED': handle_deposit_confirmed(data) elif event_type == 'WITHDRAWAL_CONFIRMED': handle_withdrawal_confirmed(data) elif event_type == 'WITHDRAWAL_FAILED': handle_withdrawal_failed(data) db.webhook_events.insert_one({ 'tx_hash': data['transactionHash'], 'event_type': event_type, 'processed_at': datetime.utcnow() }) except Exception as e: print(f'Error processing webhook: {e}') raise if __name__ == '__main__': app.run()
9 Troubleshooting
Common issues and how to fix them.
Common Issues
| Issue | Cause | Resolution |
|---|---|---|
| Webhook not received | Misconfigured URL, network firewall | 1. Confirm the URL is HTTPS 2. Use "Send test" in the console to verify connectivity 3. Review firewall settings |
| Signature verification fails | Wrong API Secret, mismatched signature data | 1. Re-check API Secret 2. Verify the order: partnerId|txHash|amount|timestamp 3. Confirm hex encoding |
| Timeout (over 5 seconds) | Slow server response, heavy processing | 1. Respond with HTTP 200 immediately, then process async 2. Remove synchronous processing 3. Optimize DB queries |
| SSL/TLS error | Expired or self-signed certificate | 1. Check certificate validity 2. Renew the certificate 3. Use a trusted CA |
| Duplicate processing | Idempotency not implemented | 1. Add transactionHash persistence 2. Implement duplicate-hash checks 3. Use a UNIQUE index in the DB |
| Console shows failure | All 5 retries failed | 1. Check server health 2. Click "Manual re-send" in the console 3. Inspect logs |
Debugging Tips
partnerId|txHash|amount|timestamp string and compare.
Monitoring
For Webhook reliability, monitor the following:
- Response time: Investigate if average response time exceeds 5 seconds
- Error rate: Track HTTP 5xx error increases
- Retry rate: A high retry ratio is a sign to inspect your server
- Event lag: Monitor the delay between event creation and processing
Need help? Contact support.
CRYPTOMENTS Webhook v2.0 © 2026