FortiBlox LogoFortiBlox Docs
NexusWebhooks

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 express

Run the server:

export FORTIBLOX_WEBHOOK_SECRET="your_webhook_secret"
node webhook-server.js
from 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 flask

Run the server:

export FORTIBLOX_WEBHOOK_SECRET="your_webhook_secret"
python webhook_server.py
use 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 run

Step 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 provided

Step 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:

  1. Navigate to Nexus Dashboard
  2. Go to Webhooks section
  3. Click Create Webhook
  4. Fill in the form:
    • Endpoint URL: Your webhook receiver URL
    • Events: Select events to subscribe to
    • Description: Optional description
    • Filters: Configure account/transaction filters
  5. Click Create
  6. 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

  1. Go to Webhooks section
  2. Select your webhook
  3. Click Send Test Event
  4. Choose event type
  5. 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_SECRET

Use 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_secret

Troubleshooting

Webhook Not Receiving Events

  1. Check webhook status: Ensure it's active
  2. Verify filters: Make sure events match your filters
  3. Test endpoint: Send a test event from dashboard
  4. Check firewall: Ensure FortiBlox IPs aren't blocked
  5. Review logs: Check delivery logs in dashboard

Signature Verification Failing

  1. Check secret: Ensure you're using the correct secret
  2. Verify payload: Use raw body, not parsed JSON
  3. Check encoding: Ensure consistent encoding (UTF-8)
  4. Header name: Use exact header name x-fortiblox-signature

Timeouts

  1. Optimize processing: Move heavy processing to background queue
  2. Acknowledge immediately: Return 200 OK before processing
  3. Add timeout handling: Ensure response within 5 seconds

High Retry Rate

  1. Check error logs: Identify common errors
  2. Improve error handling: Return appropriate status codes
  3. Add monitoring: Track webhook success rate
  4. Optimize performance: Reduce processing time

Next Steps

Support

Need help? We're here for you: