Webhooks
Receive HTTP POST notifications when blockchain events occur. Push-based notifications eliminate the need for polling and ensure you never miss an event.
Quick Setup
- Go to Dashboard → Webhooks
- Click "Create Webhook"
- Enter your HTTPS endpoint URL
- Select the events you want to receive
- Optionally configure filters (addresses, currencies, etc.)
- Save and securely store your webhook signing secret
Event Types
Subscribe to any combination of these events:
Block Events
block.newTriggered when a new block is confirmed on the chain
block.reorganizationTriggered when a chain reorganization occurs (rare but important)
Transaction Events
transaction.confirmedTriggered when a transaction reaches your specified confirmation threshold
transaction.largeTriggered when a transaction exceeds your configured value threshold
Address Events
address.receivedTriggered when a watched address receives any currency
address.sentTriggered when a watched address sends any currency
address.balance_changedTriggered on any balance change (combines received + sent)
Identity Events
identity.createdTriggered when a new VerusID is registered
identity.updatedTriggered when a VerusID's addresses or content map changes
identity.revokedTriggered when a VerusID is revoked
Currency Events
currency.conversionTriggered when a currency conversion occurs in a basket currency
currency.price_changeTriggered when a currency's price changes by more than your threshold
Payload Format
All webhooks share a consistent payload structure:
{
"id": "evt_abc123def456",
"type": "address.received",
"chainId": "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq",
"timestamp": "2024-02-07T12:00:00.000Z",
"data": {
"address": "RXL3YXG2ceaB6C5hfJcN4fvmLH2C34knhA",
"txid": "abc123...",
"amount": "1000.00000000",
"currencyId": "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq",
"currencyName": "VRSCTEST",
"blockHeight": 689000,
"confirmations": 1
}
}Block Event Payload
{
"id": "evt_block_789",
"type": "block.new",
"chainId": "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq",
"timestamp": "2024-02-07T12:01:00.000Z",
"data": {
"height": 689001,
"hash": "000000000012abc456def...",
"previousHash": "000000000011xyz...",
"time": 1707307260,
"txCount": 5,
"size": 4521,
"difficulty": 1234567890.12
}
}Identity Event Payload
{
"id": "evt_identity_456",
"type": "identity.updated",
"chainId": "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq",
"timestamp": "2024-02-07T12:02:00.000Z",
"data": {
"name": "mike",
"fullyQualifiedName": "mike@",
"identityAddress": "iAddress...",
"operation": "update",
"version": 2,
"blockHeight": 689002,
"txid": "def456...",
"changes": ["primaryAddresses", "contentMap"]
}
}Webhook Headers
Every webhook request includes these headers:
X-ChainVue-SignatureHMAC-SHA256 signature of the payload
X-ChainVue-Event-IDUnique event identifier (for deduplication)
X-ChainVue-TimestampUnix timestamp when the webhook was sent
Content-TypeAlways application/json
Verifying Signatures
Always verify webhook signatures to ensure the request is authentic and hasn't been tampered with.
Node.js Example
import crypto from 'crypto';
function verifyWebhook(payload, signature, timestamp, secret) {
// Prevent replay attacks - reject webhooks older than 5 minutes
const now = Math.floor(Date.now() / 1000);
if (now - parseInt(timestamp) > 300) {
throw new Error('Webhook timestamp too old');
}
// Calculate expected signature
const signedPayload = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express.js middleware example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-chainvue-signature'];
const timestamp = req.headers['x-chainvue-timestamp'];
if (!verifyWebhook(req.body, signature, timestamp, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process event...
res.status(200).send('OK');
});Python Example
import hmac
import hashlib
import time
def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Prevent replay attacks
if time.time() - int(timestamp) > 300:
raise ValueError("Webhook timestamp too old")
# Calculate expected signature
signed_payload = f"{timestamp}.{payload.decode()}"
expected = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(signature, expected)
# Flask example
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-ChainVue-Signature')
timestamp = request.headers.get('X-ChainVue-Timestamp')
if not verify_webhook(request.data, signature, timestamp, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.json
# Process event...
return 'OK', 200Retry Policy
If your endpoint returns a non-2xx status code or times out, we automatically retry with exponential backoff:
| Attempt | Delay | Total Time |
|---|---|---|
| 1 | Immediate | 0 |
| 2 | 1 minute | 1 min |
| 3 | 5 minutes | 6 min |
| 4 | 30 minutes | 36 min |
| 5 | 2 hours | ~2.5 hours |
After 5 failed attempts, the webhook is marked as failed. View delivery logs in your dashboard to debug issues.
Best Practices
- ✓Always verify signatures — Never process unsigned webhooks
- ✓Respond quickly — Return 200 within 5 seconds, process async
- ✓Handle duplicates — Use event IDs for idempotency
- ✓Use HTTPS only — We reject non-TLS endpoints
- ✓Monitor delivery logs — Check the dashboard for failures
- ✓Set up alerting — Get notified when webhooks fail
Webhooks vs Subscriptions
Choose the right approach for your use case:
Use Webhooks when:
- • You need server-to-server notifications
- • Your backend should react to events
- • You want guaranteed delivery with retries
- • You're building notification systems
Use Subscriptions when:
- • You need real-time UI updates
- • Building client-side applications
- • You want lowest possible latency
- • You're building block explorers