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:
| Pattern | Best For | Latency | Complexity |
|---|
Direct API (POST /inject) | Any application that processes transactions | Synchronous, ~10-50ms | Low |
Blnk Webhook (POST /blnkwebhook) | Blnk-powered applications | Event-driven, near real-time | Low |
| Data Synchronization (Watermark Sync) | Historical data analysis, aggregate functions | Background, periodic | Medium |
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
| Field | Required | Default | Notes |
|---|
amount | Yes | — | Transaction amount as a float |
currency | Yes | — | ISO 4217 currency code |
reference | Yes | — | Unique reference for idempotency |
transaction_id | No | Auto-generated UUID | Provide your own or let FinWatch generate one |
source | No | — | Source account/entity identifier |
destination | No | — | Destination account/entity identifier |
description | No | — | Free-text description |
status | No | — | Transaction status |
created_at | No | Current time | RFC 3339 UTC timestamp |
meta_data | No | {} | 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
- Send transactions asynchronously. Don’t block your main transaction processing pipeline waiting for FinWatch’s response. Use a background queue or goroutine.
- 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.
- 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.
- 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
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
- Blnk processes a transaction and emits a
transaction.created event.
- The webhook payload is sent to FinWatch’s
/blnkwebhook endpoint.
- FinWatch extracts the
data field and converts it into a Transaction struct.
- The transaction is injected into DuckDB and evaluated against all active rules.
- 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
| Scenario | Recommended Pattern |
|---|
| You’re using Blnk as your ledger | Webhook integration |
| You’re using a custom ledger | Direct API |
| You need to evaluate before the transaction completes | Direct API (pre-authorization) |
| You want passive monitoring of completed transactions | Webhook integration |
| You’re integrating multiple transaction sources | Direct 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:
| Entity | Source Table | DuckDB Table | Key Fields |
|---|
| Transactions | transactions | transactions | transaction_id, amount, source, destination, timestamp |
| Identities | identities | identities | Identity records for user context |
| Balances | balances | balances | Account balance snapshots |
| Ledgers | ledgers | ledgers | Ledger 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:
- Anomaly Notifications — Sent via the WebSocket tunnel to the Blnk Cloud dashboard.
- Logs — All verdicts are logged with full context (transaction ID, score, verdict, reason).
- 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 Score | Risk Level |
|---|
| >= 0.8 | high |
| >= 0.6 | medium |
| >= 0.3 | low |
| < 0.3 | very_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:
| Indicator | What to Watch | Warning Threshold |
|---|
| API response time | POST /inject latency | > 100ms |
| Rule compilation errors | Parse errors in logs | Any error |
| DuckDB memory usage | Memory consumption | > 80% of memory_limit |
| Watermark sync lag | Time since last successful sync | > 5 minutes |
| WebSocket tunnel status | Connection state | Disconnected for > 1 minute |
| Active rule count | Number of compiled instructions | Unexpected 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