Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.finwatch.finance/llms.txt

Use this file to discover all available pages before exploring further.

This guide shows you how to connect FinWatch to your existing fintech infrastructure. Whether you’re integrating directly via the REST API, connecting through Blnk webhooks, or syncing historical data from PostgreSQL, this guide covers every integration pattern with production-ready examples.

Integration Patterns

FinWatch supports three primary integration patterns:
PatternBest ForLatencyComplexity
Direct API (POST /inject)Any application that processes transactionsSynchronous, ~10-50msLow
Blnk Webhook (POST /blnkwebhook)Blnk-powered applicationsEvent-driven, near real-timeLow
Data Synchronization (Watermark Sync)Historical data analysis, aggregate functionsBackground, periodicMedium
Most deployments use a combination: Direct API for real-time evaluation and Data Synchronization for historical context.

Direct API: POST /inject

The most common integration pattern. Your application sends each transaction to FinWatch for real-time risk evaluation.

Endpoint

POST http://localhost:8081/inject
Content-Type: application/json

Request Payload

{
  "transaction_id": "txn_abc123",
  "amount": 5000.00,
  "currency": "USD",
  "source": "balance_123",
  "destination": "balance_456",
  "reference": "unique_ref_001",
  "description": "Payment for services",
  "status": "pending",
  "created_at": "2026-04-18T14:30:00Z",
  "meta_data": {
    "ip_address": "192.168.1.100",
    "destination_country": "US",
    "device_type": "mobile",
    "mcc": "5411"
  }
}

Required vs. Optional Fields

FieldRequiredDefaultNotes
amountYesTransaction amount as a float
currencyYesISO 4217 currency code
referenceYesUnique reference for idempotency
transaction_idNoAuto-generated UUIDProvide your own or let FinWatch generate one
sourceNoSource account/entity identifier
destinationNoDestination account/entity identifier
descriptionNoFree-text description
statusNoTransaction status
created_atNoCurrent timeRFC 3339 UTC timestamp
meta_dataNo{}Arbitrary JSON object for custom fields

Response

  • Success: 200 OK with an empty body.
  • Bad Request: 400 Bad Request with an error message if the JSON is malformed.
  • Server Error: 500 Internal Server Error if the transaction cannot be processed.

Integration Examples

curl

curl -X POST http://localhost:8081/inject \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_id": "txn_001",
    "amount": 15000.00,
    "currency": "USD",
    "source": "acct_alice",
    "destination": "acct_bob",
    "reference": "ref_001",
    "description": "Wire transfer",
    "meta_data": {
      "destination_country": "IR",
      "ip_address": "10.0.0.1"
    }
  }'

Node.js (fetch)

async function evaluateTransaction(transaction) {
  const response = await fetch('http://localhost:8081/inject', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      transaction_id: transaction.id,
      amount: transaction.amount,
      currency: transaction.currency,
      source: transaction.sourceAccountId,
      destination: transaction.destinationAccountId,
      reference: transaction.reference,
      description: transaction.description,
      status: transaction.status,
      created_at: transaction.createdAt.toISOString(),
      meta_data: {
        ip_address: transaction.ipAddress,
        destination_country: transaction.destinationCountry,
        device_type: transaction.deviceType,
        mcc: transaction.merchantCategoryCode
      }
    })
  });

  if (!response.ok) {
    throw new Error(`FinWatch injection failed: ${response.status}`);
  }
}

Python (requests)

import requests
from datetime import datetime

def evaluate_transaction(transaction):
    payload = {
        "transaction_id": transaction["id"],
        "amount": transaction["amount"],
        "currency": transaction["currency"],
        "source": transaction["source_account"],
        "destination": transaction["destination_account"],
        "reference": transaction["reference"],
        "description": transaction.get("description", ""),
        "status": transaction.get("status", "pending"),
        "created_at": datetime.utcnow().isoformat() + "Z",
        "meta_data": {
            "ip_address": transaction.get("ip_address"),
            "destination_country": transaction.get("country"),
        }
    }

    response = requests.post(
        "http://localhost:8081/inject",
        json=payload,
        timeout=5
    )
    response.raise_for_status()

Go (net/http)

func evaluateTransaction(t Transaction) error {
    payload := map[string]interface{}{
        "transaction_id": t.ID,
        "amount":         t.Amount,
        "currency":       t.Currency,
        "source":         t.Source,
        "destination":    t.Destination,
        "reference":      t.Reference,
        "description":    t.Description,
        "meta_data":      t.Metadata,
    }

    body, err := json.Marshal(payload)
    if err != nil {
        return fmt.Errorf("failed to marshal transaction: %w", err)
    }

    resp, err := http.Post(
        "http://localhost:8081/inject",
        "application/json",
        bytes.NewReader(body),
    )
    if err != nil {
        return fmt.Errorf("failed to send to FinWatch: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("FinWatch returned status %d", resp.StatusCode)
    }
    return nil
}

Best Practices for Direct API Integration

  1. Send transactions asynchronously. Don’t block your main transaction processing pipeline waiting for FinWatch’s response. Use a background queue or goroutine.
  2. Set a timeout. FinWatch should respond within 50ms for most transactions. Set a 5-second timeout to handle edge cases, and fall back to allowing the transaction if FinWatch is unresponsive.
  3. Include rich metadata. The more data you send in meta_data, the more expressive your rules can be. Include IP addresses, device types, geo-location, MCC codes, account age, KYC level — anything that could be relevant for fraud detection.
  4. Use consistent field names. If your rules reference metadata.destination_country, make sure every transaction includes that exact field name. A typo means the rule silently fails.

Blnk Webhook Integration

If you’re using Blnk as your ledger, FinWatch can receive transaction events directly from Blnk’s webhook system.

Endpoint

POST http://localhost:8081/blnkwebhook
Content-Type: application/json

Webhook Payload Format

Blnk sends webhook events in this format:
{
  "event": "transaction.created",
  "data": {
    "transaction_id": "txn_123",
    "amount": 1000.00,
    "currency": "USD",
    "source": "balance_123",
    "destination": "balance_456",
    "reference": "ref_001",
    "status": "applied",
    "created_at": "2026-04-18T10:30:00Z",
    "meta_data": {
      "key": "value"
    }
  }
}

Configuring Blnk to Send Events

In your Blnk configuration, set the webhook URL to point to FinWatch:
{
  "webhook_url": "http://finwatch:8081/blnkwebhook"
}

How It Works

  1. Blnk processes a transaction and emits a transaction.created event.
  2. The webhook payload is sent to FinWatch’s /blnkwebhook endpoint.
  3. FinWatch extracts the data field and converts it into a Transaction struct.
  4. The transaction is injected into DuckDB and evaluated against all active rules.
  5. FinWatch returns 200 OK with a confirmation message.

Response

Transaction txn_123 from webhook event 'transaction.created' processed successfully

When to Use Webhook vs. Direct API

ScenarioRecommended Pattern
You’re using Blnk as your ledgerWebhook integration
You’re using a custom ledgerDirect API
You need to evaluate before the transaction completesDirect API (pre-authorization)
You want passive monitoring of completed transactionsWebhook integration
You’re integrating multiple transaction sourcesDirect API for each source

Data Synchronization

For aggregate functions to work effectively, FinWatch needs historical transaction data. If you’re using Blnk with a PostgreSQL database, the watermark sync feature automatically copies data from PostgreSQL into FinWatch’s local DuckDB.

Why Data Sync Matters

Consider this rule:
when count(when source == $current.source, "PT24H") > 10
This needs to count transactions from the last 24 hours. If FinWatch only has transactions that were injected via the API, it won’t know about transactions that were processed before FinWatch was deployed, or transactions that came through other channels. The watermark sync ensures FinWatch has a complete view of all historical transactions, enabling accurate aggregate computations.

Configuration

Set the BLNK_DSN environment variable to your Blnk PostgreSQL connection string:
export BLNK_DSN="postgres://user:password@localhost:5432/blnk?sslmode=disable"

What Gets Synced

The watermark sync copies four entity types from PostgreSQL to DuckDB:
EntitySource TableDuckDB TableKey Fields
Transactionstransactionstransactionstransaction_id, amount, source, destination, timestamp
IdentitiesidentitiesidentitiesIdentity records for user context
BalancesbalancesbalancesAccount balance snapshots
LedgersledgersledgersLedger records

Sync Behavior

  • Incremental: Only new records (since the last sync) are transferred.
  • Automatic: Runs periodically in the background.
  • Resilient: Uses a watermark (timestamp + ID) to track progress. If interrupted, it resumes from the last watermark.
  • Non-blocking: Sync runs in a background goroutine and does not block transaction processing or rule evaluation.
For the complete technical specification of the watermark sync mechanism, see the Watermark Sync Documentation.

Handling Verdicts

When a transaction triggers one or more rules, FinWatch produces a consolidated risk assessment. Here’s how to handle each verdict type in your application:

Verdict Response Flow

FinWatch Verdict          Your Application Action
─────────────────         ──────────────────────────────
block / deny         →    Reject the transaction. Return an error to the user.
review               →    Hold the transaction. Queue for human analyst review.
alert                →    Allow the transaction. Log the alert for monitoring.
allow / approve      →    Allow the transaction. No additional action needed.

Implementation Pattern

Since FinWatch processes transactions asynchronously via the risk evaluation worker, verdicts are communicated through:
  1. Anomaly Notifications — Sent via the WebSocket tunnel to the Blnk Cloud dashboard.
  2. Logs — All verdicts are logged with full context (transaction ID, score, verdict, reason).
  3. Instructions API — You can query the GET /instructions endpoint to see all active rules and their compiled state.

Querying Active Instructions

# List all active compiled rules
curl http://localhost:8081/instructions

# Get a specific instruction by ID
curl http://localhost:8081/instructions/1

Anomaly Notifications

FinWatch sends real-time anomaly notifications to the Blnk Cloud dashboard via a persistent WebSocket tunnel.

What Gets Reported

When the risk consolidator determines a transaction is risky, it sends an AnomalyMessage containing:
{
  "type": "anomaly",
  "transaction_id": "txn_001",
  "description": "High-value transaction from new account during late night hours",
  "risk_level": "high",
  "risk_score": 0.85,
  "verdict": "block",
  "reason": "Multiple risk signals detected",
  "source_count": 3,
  "timestamp": "2026-04-18T14:30:00Z",
  "additional_data": {
    "transaction_amount": 15000,
    "transaction_reference": "ref_001",
    "source_ledger": "acct_alice",
    "destination_ledger": "acct_bob",
    "original_metadata": { ... }
  }
}

Risk Level Mapping

The risk consolidator maps the aggregated risk score to a risk level:
Risk ScoreRisk Level
>= 0.8high
>= 0.6medium
>= 0.3low
< 0.3very_low

Resilience

  • If the WebSocket tunnel is disconnected, anomalies are logged locally but not sent. Transaction processing is never blocked by a reporting failure.
  • The tunnel automatically reconnects when the connection is restored.
  • No anomaly data is lost from the DuckDB storage — it’s always available locally regardless of tunnel status.

Health Monitoring

Verifying FinWatch is Running

The simplest health check is to call the instructions endpoint:
curl -s http://localhost:8081/instructions | head -c 100
A 200 OK response (even with an empty array []) confirms FinWatch is running and the API is responsive.

Checking Git Repository Status

If you’re using GitOps, check the sync status:
curl http://localhost:8081/git/status

Monitoring Key Indicators

In production, monitor these indicators:
IndicatorWhat to WatchWarning Threshold
API response timePOST /inject latency> 100ms
Rule compilation errorsParse errors in logsAny error
DuckDB memory usageMemory consumption> 80% of memory_limit
Watermark sync lagTime since last successful sync> 5 minutes
WebSocket tunnel statusConnection stateDisconnected for > 1 minute
Active rule countNumber of compiled instructionsUnexpected changes

Integration Checklist

Before going to production, verify:
  • Transactions are flowing. Inject a test transaction and verify it appears in FinWatch.
  • Rules are loaded. Check GET /instructions returns your expected rules.
  • Rules are firing. Inject a transaction that should trigger a rule and verify the verdict.
  • Metadata is complete. Verify that all fields referenced by your rules are present in the transaction payload.
  • Data sync is working. If using BLNK_DSN, verify that historical data is being synced.
  • Anomaly reporting works. If using the WebSocket tunnel, verify anomalies appear on the dashboard.
  • Error handling is in place. Test what happens when FinWatch is unavailable. Your application should fail gracefully.
  • Timeouts are configured. Set appropriate timeouts on all HTTP calls to FinWatch.

Next Steps