Baley Webhooks

Axle sends webhook notifications when investigation statuses are updated. This document explains how to receive and validate these webhooks.

Setup

  1. Login to Axle
  2. Within settings, navigate to the Webhooks tab
  3. Submit the form to create your webhook by pressing "Create Webhook"

Webhook Details

Event Types

Currently only supports: investigation.updated

Status Fields

# Processing State
- COMPLETED
- FAILED

# Approval State
- AI_CLEARED
- AI_ESCALATED

Sample Payload

{
  "event_type": "investigation.updated",
  "investigation_id": "14c08b5d-e32c-46d7-ba5e-156bd543eefa",
  "organization_id": "766b0fc0-bfe2-4032-a510-ccd068a0c160",
  "investigation_type": "individual_investigation", // OR "business_investigation" OR "transaction_monitoring_investigation"
  "status": {
    "approval_state": "AI_CLEARED",
    "processing_state": "COMPLETED"
  },
  "timestamp": "2025-02-07T23:24:32.436517188"
}

Security

  • All webhooks are signed using HMAC-SHA256
  • Verify signature before processing payload
  • Only HTTPS endpoints with TLS 1.2 or higher are supported
  • Keep webhook secret secure

Need help? Contact Axle support.

Implementation Examples

from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib

app = FastAPI()

# Load this securely in production
WEBHOOK_SECRET = "your_webhook_secret"

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    """Verify webhook signature"""
    computed = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, computed)

@app.post("/webhook")
async def webhook_handler(request: Request):
    payload = await request.body()
    signature = request.headers.get("X-Signature")
    
    if not signature:
        raise HTTPException(status_code=400, detail="Missing signature")
    
    if not verify_signature(payload, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Process webhook payload
    data = await request.json()
    return {"status": "success"}
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"log"
	"net/http"
)

const (
	// In production, load this securely from environment variables or secret management
	WebhookSecret = "your_webhook_secret"
)

// VerifySignature verifies the HMAC signature of the webhook payload
func VerifySignature(payload []byte, signature string, secret string) bool {
	h := hmac.New(sha256.New, []byte(secret))
	h.Write(payload)
	expectedSignature := hex.EncodeToString(h.Sum(nil))
	return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Read the request body
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	// Get the signature from headers
	signature := r.Header.Get("X-Signature")
	if signature == "" {
		http.Error(w, "Missing signature", http.StatusBadRequest)
		return
	}

	// Verify the signature
	if !VerifySignature(body, signature, WebhookSecret) {
		http.Error(w, "Invalid signature", http.StatusUnauthorized)
		return
	}

	// Process the webhook payload here
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status": "success"}`))
}

func main() {
	http.HandleFunc("/webhook", webhookHandler)
	
	log.Println("Server starting on :8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}