> ## 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.

# Integration Guide

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

```shellscript theme={null}
POST http://localhost:8081/inject
Content-Type: application/json
```

### Request Payload

```json theme={null}
{
  "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

```bash theme={null}
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)

```javascript theme={null}
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)

```python theme={null}
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)

```go theme={null}
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](https://blnkfinance.com/) as your ledger, FinWatch can receive transaction events directly from Blnk's webhook system.

### Endpoint

```shellscript theme={null}
POST http://localhost:8081/blnkwebhook
Content-Type: application/json
```

### Webhook Payload Format

Blnk sends webhook events in this format:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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

```text theme={null}
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:

```ws theme={null}
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:

```bash theme={null}
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](../WATERMARK_SYNC.md).

***

## 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

```text theme={null}
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

```bash theme={null}
# 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:

```json theme={null}
{
  "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:

```bash theme={null}
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:

```bash theme={null}
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

* [**Production Deployment**](production-deployment.md) — Configure FinWatch for production workloads.
* [**GitOps Rule Management**](gitops-rule-management.md) — Set up Git-based rule deployment.
* [**Troubleshooting**](troubleshooting.md) — Resolve common integration issues.
* [**API Documentation**](../../watch/API_DOCUMENTATION.md) — Full REST API reference with all endpoints.
