Webhook Integration¶
Complete guide to integrating Vigil webhooks into your applications.
Overview¶
Webhooks send HTTP POST requests to your server when events occur, enabling real-time integrations.
For basic webhook setup, see Webhook Notifications.
Integration Architecture¶
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Vigil │────▶│ Your Server │────▶│ Your App │
│ Event │ │ Endpoint │ │ Logic │
└─────────────┘ └─────────────┘ └─────────────┘
Example Implementations¶
Node.js/Express¶
const express = require('express');
const crypto = require('crypto');
const app = express();
// Verify HMAC signature
function verifySignature(timestamp, payload, signature, secret) {
const expectedSig = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.`)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', '')),
Buffer.from(expectedSig)
);
}
app.post('/webhook/vigil', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-btcwatch-signature'];
const timestamp = req.headers['x-btcwatch-timestamp'];
const secret = process.env.VIGIL_WEBHOOK_SECRET;
// Verify signature
if (!verifySignature(timestamp, req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Check timestamp freshness
const requestTime = parseInt(timestamp);
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - requestTime) > 300) { // 5 min tolerance
return res.status(401).send('Request too old');
}
// Process event
const event = JSON.parse(req.body);
console.log(`Received ${event.event_type} for wallet ${event.wallet_id}`);
// Handle different event types
switch(event.event_type) {
case 'transaction.deposit':
handleDeposit(event);
break;
case 'transaction.withdrawal':
handleWithdrawal(event);
break;
case 'theft_shield.incident_detected':
handleTheftAlert(event);
break;
}
res.status(200).send({status: 'received'});
});
Python/Flask¶
import hmac
import hashlib
import time
from flask import Flask, request
app = Flask(__name__)
def verify_signature(timestamp, payload, signature, secret):
mac = hmac.new(secret.encode(), digestmod=hashlib.sha256)
mac.update(str(timestamp).encode())
mac.update(b".")
mac.update(payload)
expected_sig = mac.hexdigest()
received_sig = signature.replace('sha256=', '')
return hmac.compare_digest(received_sig, expected_sig)
@app.route('/webhook/vigil', methods=['POST'])
def vigil_webhook():
signature = request.headers.get('X-BtcWatch-Signature')
timestamp = request.headers.get('X-BtcWatch-Timestamp')
secret = os.environ['VIGIL_WEBHOOK_SECRET']
# Verify signature
if not verify_signature(timestamp, request.data, signature, secret):
return {'error': 'Invalid signature'}, 401
# Check timestamp
request_time = int(timestamp)
if abs(time.time() - request_time) > 300:
return {'error': 'Request too old'}, 401
# Process event
event = request.json
print(f"Received {event['event_type']} for wallet {event.get('wallet_id')}")
# Route to handlers
if event['event_type'] == 'transaction.deposit':
handle_deposit(event)
elif event['event_type'] == 'transaction.withdrawal':
handle_withdrawal(event)
elif event['event_type'] == 'theft_shield.incident_detected':
handle_theft_alert(event)
return {'status': 'received'}
Go¶
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"time"
)
func verifySignature(timestamp, payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(timestamp)
mac.Write([]byte("."))
mac.Write(payload)
expectedSig := hex.EncodeToString(mac.Sum(nil))
receivedSig := signature[7:] // Remove "sha256=" prefix
return hmac.Equal([]byte(receivedSig), []byte(expectedSig))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-BtcWatch-Signature")
timestampStr := r.Header.Get("X-BtcWatch-Timestamp")
secret := os.Getenv("VIGIL_WEBHOOK_SECRET")
payload, _ := ioutil.ReadAll(r.Body)
// Verify signature
if !verifySignature([]byte(timestampStr), payload, signature, secret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Verify timestamp
timestamp, _ := strconv.ParseInt(timestampStr, 10, 64)
if time.Now().Unix()-timestamp > 300 {
http.Error(w, "Request too old", http.StatusUnauthorized)
return
}
// Parse and handle event
var event map[string]interface{}
json.Unmarshal(payload, &event)
log.Printf("Received %s for wallet %v", event["event_type"], event["wallet_id"])
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "received"})
}
Use Cases¶
Accounting Integration¶
Log all transactions to your accounting system:
function handleDeposit(event) {
const entry = {
date: event.timestamp,
account: 'Bitcoin Receivable',
amount: (event.event_data.amount_sats || 0) / 100000000,
description: `Deposit event ${event.event_data.txid || ''}`,
wallet: event.wallet_id
};
accountingDB.insert(entry);
}
Automated Processing¶
Process payments when confirmed:
def handle_confirmed(event):
if event['event_type'] == 'payment.confirmed':
order_id = lookup_order_by_payment(event['event_data'])
mark_order_paid(order_id)
send_confirmation_email(order_id)
Alert Forwarding¶
Forward critical alerts to monitoring:
function handleTheftAlert(event) {
// Send to PagerDuty
pagerduty.trigger({
severity: 'critical',
summary: `Theft detected on ${event.wallet.name}`,
details: event
});
// Send to Slack
slack.postMessage({
channel: '#security-alerts',
text: `🚨 THEFT SHIELD ALERT: ${event.wallet.name}`
});
}
Testing¶
Local Testing with ngrok¶
# Start your local server
node server.js
# In another terminal, start ngrok
ngrok http 3000
# Use the ngrok HTTPS URL in Vigil webhook settings
# https://abc123.ngrok.io/webhook/vigil
Test Payload¶
Send a test webhook from Vigil dashboard or use curl:
curl -X POST https://your-server.com/webhook/vigil \
-H "Content-Type: application/json" \
-H "X-BtcWatch-Signature: sha256=..." \
-H "X-BtcWatch-Timestamp: $(date +%s)" \
-d '{"event_type":"transaction.deposit",...}'
Production Best Practices¶
- Always verify signatures - Prevent spoofing
- Check timestamps - Prevent replay attacks
- Handle idempotently - Same event may be sent multiple times
- Return quickly - Process asynchronously if needed
- Log everything - Debugging webhooks is hard
- Monitor failures - Set up alerting for webhook errors
Back to: Technical Reference →