Getting Started with Webhooks
Set up your first FortiBlox webhook in 5 minutes - registration, testing, and deployment
Getting Started with Webhooks
This guide will walk you through setting up your first webhook endpoint, registering it with FortiBlox, and handling your first blockchain event notification.
Prerequisites
Before you begin, ensure you have:
- Business tier or higher - Webhooks require Business, Professional, or Enterprise tier
- API key - Generate from Nexus Dashboard
- Public HTTPS endpoint - Your webhook receiver must be accessible via HTTPS
- Basic programming knowledge - Examples provided in Node.js, Python, and Rust
Quick Start (5 minutes)
Step 1: Create a Webhook Endpoint
Choose your language and create a basic webhook receiver:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.FORTIBLOX_WEBHOOK_SECRET;
app.post('/webhook', (req, res) => {
// Verify signature
const signature = req.headers['x-fortiblox-signature'];
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(JSON.stringify(req.body));
const expectedSignature = hmac.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook event
const { event, data } = req.body;
console.log(`Received ${event}:`, data);
// Acknowledge receipt
res.status(200).json({ status: 'success' });
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});Install dependencies:
npm install expressRun the server:
export FORTIBLOX_WEBHOOK_SECRET="your_webhook_secret"
node webhook-server.jsfrom flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.getenv('FORTIBLOX_WEBHOOK_SECRET')
@app.route('/webhook', methods=['POST'])
def webhook():
# Verify signature
signature = request.headers.get('X-Fortiblox-Signature')
payload = request.get_data()
expected_signature = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
if signature != expected_signature:
return jsonify({'error': 'Invalid signature'}), 401
# Process webhook event
event_data = request.json
event = event_data.get('event')
data = event_data.get('data')
print(f"Received {event}: {data}")
# Acknowledge receipt
return jsonify({'status': 'success'}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)Install dependencies:
pip install flaskRun the server:
export FORTIBLOX_WEBHOOK_SECRET="your_webhook_secret"
python webhook_server.pyuse actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use serde::{Deserialize, Serialize};
use hex;
type HmacSha256 = Hmac<Sha256>;
#[derive(Deserialize)]
struct WebhookPayload {
event: String,
data: serde_json::Value,
}
#[derive(Serialize)]
struct Response {
status: String,
}
async fn webhook_handler(
req: HttpRequest,
payload: web::Json<WebhookPayload>,
) -> HttpResponse {
let secret = std::env::var("FORTIBLOX_WEBHOOK_SECRET")
.expect("FORTIBLOX_WEBHOOK_SECRET not set");
// Verify signature
let signature = match req.headers().get("x-fortiblox-signature") {
Some(sig) => sig.to_str().unwrap_or(""),
None => return HttpResponse::Unauthorized().json("Missing signature"),
};
let payload_str = serde_json::to_string(&payload.into_inner()).unwrap();
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
mac.update(payload_str.as_bytes());
let expected_signature = hex::encode(mac.finalize().into_bytes());
if signature != expected_signature {
return HttpResponse::Unauthorized().json("Invalid signature");
}
// Process webhook event
println!("Received {}: {:?}", payload.event, payload.data);
// Acknowledge receipt
HttpResponse::Ok().json(Response {
status: "success".to_string(),
})
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/webhook", web::post().to(webhook_handler))
})
.bind("0.0.0.0:3000")?
.run()
.await
}Add dependencies to Cargo.toml:
[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
hmac = "0.12"
sha2 = "0.10"
hex = "0.4"Run the server:
export FORTIBLOX_WEBHOOK_SECRET="your_webhook_secret"
cargo runStep 2: Expose Your Endpoint (Development)
For local testing, use a tunneling service to expose your endpoint:
# Install ngrok
brew install ngrok # macOS
# or download from https://ngrok.com
# Start tunnel
ngrok http 3000
# Copy the HTTPS URL (e.g., https://abc123.ngrok.io)# Install cloudflared
brew install cloudflare/cloudflare/cloudflared # macOS
# Start tunnel
cloudflared tunnel --url http://localhost:3000
# Copy the HTTPS URL# No installation needed
ssh -R 80:localhost:3000 localhost.run
# Copy the HTTPS URL providedStep 3: Register Your Webhook
Register your webhook endpoint via the Nexus API:
curl -X POST https://nexus.fortiblox.com/api/v1/webhooks \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhook",
"events": ["transaction.confirmed"],
"filters": {
"account_include": ["7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"]
},
"description": "Monitor wallet transactions"
}'Response:
{
"webhook_id": "wh_abc123",
"url": "https://your-domain.com/webhook",
"secret": "whsec_xyz789...",
"status": "active",
"created_at": "2024-01-15T10:30:00Z"
}Save Your Secret: The webhook secret is only shown once. Store it securely in your environment variables.
Step 4: Test Your Webhook
Send a test event to verify your endpoint is working:
curl -X POST https://nexus.fortiblox.com/api/v1/webhooks/wh_abc123/test \
-H "X-API-Key: YOUR_API_KEY"You should see output in your webhook server:
Received test.webhook: { message: 'This is a test webhook' }Dashboard Setup (Alternative)
You can also register webhooks via the Nexus Dashboard:
- Navigate to Nexus Dashboard
- Go to Webhooks section
- Click Create Webhook
- Fill in the form:
- Endpoint URL: Your webhook receiver URL
- Events: Select events to subscribe to
- Description: Optional description
- Filters: Configure account/transaction filters
- Click Create
- Copy the webhook secret
Webhook Configuration
Basic Configuration
{
"url": "https://api.yourdomain.com/webhook",
"events": ["transaction.confirmed", "account.updated"],
"description": "Production webhook for payment processing"
}With Filters
Monitor specific accounts or programs:
{
"url": "https://api.yourdomain.com/webhook",
"events": ["transaction.confirmed"],
"filters": {
"account_include": [
"JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
],
"success": true,
"vote_transactions": false
},
"description": "Monitor Jupiter swaps"
}Advanced Configuration
{
"url": "https://api.yourdomain.com/webhook",
"events": ["transaction.confirmed", "transaction.finalized"],
"filters": {
"program_id": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
"commitment": "confirmed",
"min_amount": 1000000, // 0.001 SOL minimum
"token_transfers": true
},
"retry_policy": {
"max_attempts": 7,
"backoff_multiplier": 2
},
"description": "Jupiter swaps with custom retry policy"
}Managing Webhooks
List All Webhooks
curl https://nexus.fortiblox.com/api/v1/webhooks \
-H "X-API-Key: YOUR_API_KEY"Get Webhook Details
curl https://nexus.fortiblox.com/api/v1/webhooks/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY"Update Webhook
curl -X PATCH https://nexus.fortiblox.com/api/v1/webhooks/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["transaction.confirmed", "transaction.finalized"],
"description": "Updated webhook"
}'Delete Webhook
curl -X DELETE https://nexus.fortiblox.com/api/v1/webhooks/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY"Pause/Resume Webhook
# Pause
curl -X POST https://nexus.fortiblox.com/api/v1/webhooks/wh_abc123/pause \
-H "X-API-Key: YOUR_API_KEY"
# Resume
curl -X POST https://nexus.fortiblox.com/api/v1/webhooks/wh_abc123/resume \
-H "X-API-Key: YOUR_API_KEY"Monitoring Webhooks
View Delivery Logs
curl https://nexus.fortiblox.com/api/v1/webhooks/wh_abc123/deliveries \
-H "X-API-Key: YOUR_API_KEY"Response:
{
"deliveries": [
{
"id": "del_123",
"event_id": "evt_456",
"status": "success",
"status_code": 200,
"attempts": 1,
"delivered_at": "2024-01-15T10:30:00Z"
},
{
"id": "del_124",
"event_id": "evt_457",
"status": "failed",
"status_code": 500,
"attempts": 3,
"last_attempt_at": "2024-01-15T10:31:30Z",
"error": "Connection timeout"
}
]
}Retry Failed Delivery
curl -X POST https://nexus.fortiblox.com/api/v1/webhooks/deliveries/del_124/retry \
-H "X-API-Key: YOUR_API_KEY"Testing Webhooks
Local Testing with curl
Simulate a webhook event locally:
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "X-Fortiblox-Signature: $(echo -n '{"event":"test","data":{}}' | openssl dgst -sha256 -hmac "your_secret" | cut -d' ' -f2)" \
-d '{
"event": "test",
"data": {}
}'Test Event from Dashboard
- Go to Webhooks section
- Select your webhook
- Click Send Test Event
- Choose event type
- View delivery status
Production Deployment
Requirements Checklist
- HTTPS endpoint (TLS 1.2+)
- Signature verification implemented
- Idempotency handling (deduplicate by
event_id) - Response time < 5 seconds
- Returns 2xx status code for success
- Error handling and logging
- Monitoring and alerting
- Rate limiting protection
Deployment Options
// lambda.js
const crypto = require('crypto');
exports.handler = async (event) => {
const signature = event.headers['x-fortiblox-signature'];
const secret = process.env.WEBHOOK_SECRET;
// Verify signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(event.body);
const expectedSignature = hmac.digest('hex');
if (signature !== expectedSignature) {
return { statusCode: 401, body: 'Invalid signature' };
}
// Process webhook
const payload = JSON.parse(event.body);
console.log('Webhook received:', payload);
return { statusCode: 200, body: JSON.stringify({ status: 'success' }) };
};Deploy:
npm install
zip -r function.zip .
aws lambda create-function \
--function-name fortiblox-webhook \
--runtime nodejs18.x \
--role arn:aws:iam::ACCOUNT:role/lambda-role \
--handler lambda.handler \
--zip-file fileb://function.zip \
--environment Variables={WEBHOOK_SECRET=your_secret}// index.js
const crypto = require('crypto');
exports.webhook = (req, res) => {
const signature = req.headers['x-fortiblox-signature'];
const secret = process.env.WEBHOOK_SECRET;
// Verify signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(req.body));
const expectedSignature = hmac.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).send('Invalid signature');
}
// Process webhook
console.log('Webhook received:', req.body);
res.status(200).json({ status: 'success' });
};Deploy:
gcloud functions deploy fortiblox-webhook \
--runtime nodejs18 \
--trigger-http \
--allow-unauthenticated \
--set-env-vars WEBHOOK_SECRET=your_secret// api/webhook.js
import crypto from 'crypto';
export default function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const signature = req.headers['x-fortiblox-signature'];
const secret = process.env.WEBHOOK_SECRET;
// Verify signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(req.body));
const expectedSignature = hmac.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
console.log('Webhook received:', req.body);
res.status(200).json({ status: 'success' });
}Deploy:
vercel deploy
vercel env add WEBHOOK_SECRETUse any of the server examples above. Railway supports:
- Node.js, Python, Rust, Go
- Automatic HTTPS
- Environment variables
- Easy deployment from Git
railway login
railway init
railway up
railway variables set WEBHOOK_SECRET=your_secretTroubleshooting
Webhook Not Receiving Events
- Check webhook status: Ensure it's active
- Verify filters: Make sure events match your filters
- Test endpoint: Send a test event from dashboard
- Check firewall: Ensure FortiBlox IPs aren't blocked
- Review logs: Check delivery logs in dashboard
Signature Verification Failing
- Check secret: Ensure you're using the correct secret
- Verify payload: Use raw body, not parsed JSON
- Check encoding: Ensure consistent encoding (UTF-8)
- Header name: Use exact header name
x-fortiblox-signature
Timeouts
- Optimize processing: Move heavy processing to background queue
- Acknowledge immediately: Return 200 OK before processing
- Add timeout handling: Ensure response within 5 seconds
High Retry Rate
- Check error logs: Identify common errors
- Improve error handling: Return appropriate status codes
- Add monitoring: Track webhook success rate
- Optimize performance: Reduce processing time
Next Steps
Event Reference
Complete list of webhook events and payloads
Security Best Practices
Secure your webhook endpoints properly
Code Examples
Production-ready webhook implementations
API Reference
Complete webhook API documentation
Support
Need help? We're here for you:
- Discord: discord.gg/fortiblox
- Email: [email protected]
- Documentation: You're reading it!