> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Transaction Monitoring Integration Guide

> Architecture and best practices for KYT: real-time vs batch, parties, idempotency, historical ingestion, webhooks. Pay-per-call $0.02 per transaction.

This guide covers the full integration surface of Transaction Monitoring. The [quickstart](/transaction-monitoring/quickstart) gets you running; this page gets you to production.

## Architecture

```mermaid theme={null}
sequenceDiagram
    participant You as Your platform
    participant Didit as Didit API
    participant Rules as Rule engine
    participant AML as AML providers
    participant Webhook as Your webhook

    You->>Didit: POST /v3/transactions/ (txn_id, parties, amount)
    Didit->>Didit: IP enrichment (inline)
    Didit->>AML: Screen wallet or transaction hash + address
    AML-->>Didit: Hits / clean
    Didit->>Rules: Evaluate all active rules
    Rules-->>Didit: Score + matched rules + actions
    Didit-->>You: Transaction record with status
    Didit->>Webhook: transaction.created
    Didit->>Webhook: transaction.status.updated (if async rules)
```

## Authentication

Standard `x-api-key` header. Same keys used for sessions and entities. See [API authentication](/getting-started/api-authentication).

## Submitting a transaction

Single endpoint: `POST /v3/transactions/`. See [submitting transactions](/transaction-monitoring/transactions) for the full schema.

**Minimal payload:**

```json theme={null}
{
  "transaction_id": "tx-0001",
  "transaction_category": "finance",
  "transaction_details": {
    "direction": "outbound",
    "amount": "500.00",
    "currency": "EUR"
  },
  "subject": { "entity_type": "person", "vendor_data": "user-42", "full_name": "John Doe" },
  "counterparty": { "entity_type": "company", "full_name": "Acme Corp" }
}
```

**Richer payload with payment methods, device context, and custom properties:**

```json theme={null}
{
  "transaction_id": "tx-0002",
  "transaction_at": "2026-04-16T10:00:00Z",
  "transaction_category": "finance",
  "transaction_details": {
    "direction": "outbound",
    "amount": "12500.00",
    "currency": "USD",
    "action_type": "withdrawal"
  },
  "subject": {
    "entity_type": "person",
    "vendor_data": "user-42",
    "full_name": "John Doe",
    "country_code": "US",
    "device": {
      "fingerprint": "fp-9f2c...",
      "user_agent": "Mozilla/5.0 ...",
      "network_context": { "ip_address": "203.0.113.5" }
    },
    "payment_method": {
      "method_type": "bank_account",
      "account_id": "US12345678901234567890",
      "issuing_country": "US"
    }
  },
  "counterparty": {
    "entity_type": "company",
    "vendor_data": "biz-acme",
    "full_name": "Acme Corp",
    "country_code": "US",
    "payment_method": {
      "method_type": "bank_account",
      "account_id": "US09876543210987654321",
      "issuing_country": "US"
    }
  },
  "custom_properties": {
    "invoice_id": "INV-2026-00042",
    "category": "b2b_goods"
  }
}
```

camelCase aliases are accepted for every top-level field (`txnId`, `txnDate`, `type`, `info`, `applicant`, `props`, `travelRule`, `includeCryptoScreening`) - the SDK wire format and the snake\_case format land in the same place.

## Party modeling

Every transaction has a required `subject` (your customer - the party you monitor) and an optional `counterparty` (the other side). Each carries its own `payment_method` and optional `device` context.

In the response, they come back in the `parties` array with roles `APPLICANT` and `COUNTERPARTY`; a party without `vendor_data` is stored as an external snapshot (`kind: EXTERNAL`) with no linked entity.

See [transactions payload](/transaction-monitoring/transactions) for party fields.

## Idempotency

`txn_id` is unique per application. Re-submitting the same `txn_id` returns the existing transaction's current state (not an error) — effectively idempotent for retries.

**Always generate `txn_id` on your side** before submitting; don't rely on a Didit-generated ID for idempotency. Use your own database ID, a UUID, or a hash of transaction fields.

## Real-time vs batch

* **Real-time** — recommended for most use cases. Submit the transaction the moment it happens in your system. Rules evaluate synchronously and return the decision.
* **Batch / backfill** — for historical ingestion, pass `txn_date` in the past. Rules still run and can trigger webhooks. To avoid triggering rules on backfill, use the console's CSV import with the "dry ingest" flag.

## Historical ingestion

When you're migrating from another transaction monitoring system:

1. Export your historical transactions to a CSV.
2. Use *Transactions → Import history* in the console (or submit via API in a loop) with `txn_date` in the past.
3. Enable rules only after ingestion finishes, or use dry ingest to skip rule evaluation.

<Warning>
  Be deliberate about historical rule evaluation. Feeding 6 months of history through a "20 transfers in 30 days" rule will generate thousands of alerts at once.
</Warning>

## Webhooks

Subscribe to:

* `transaction.created` — every submitted transaction.
* `transaction.status.updated` — status changes (e.g. analyst moves from `IN_REVIEW` to `APPROVED`, or an async AML check updates the score).

Full details: [TM webhooks](/transaction-monitoring/webhooks).

```typescript theme={null}
app.post('/webhooks/didit-tm', (req, res) => {
  if (!verifySignature(req)) return res.status(401).end();

  const { event, data } = req.body;
  if (event === 'transaction.status.updated' && data.status === 'DECLINED') {
    suspendTransaction(data.txn_id);
  }
  res.status(200).end();
});
```

## Handling statuses

| Status          | What to do                                                                                                                                                                                                                                     |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APPROVED`      | Process the transaction as normal.                                                                                                                                                                                                             |
| `IN_REVIEW`     | Hold the transaction until analyst review or automated re-check resolves.                                                                                                                                                                      |
| `DECLINED`      | Reject the transaction. Surface a generic error to the user.                                                                                                                                                                                   |
| `AWAITING_USER` | The user must act before the transaction can settle — read the `action_required` block and deliver its `url` to the user (the SDKs can [handle this automatically](/transaction-monitoring/sdk-transaction-submission#required-user-actions)). |

See [TM statuses](/transaction-monitoring/statuses).

## Linking transactions to entities

Pass `vendor_data` on every party. Benefits:

* Every transaction aggregates into the User or Business profile.
* Velocity and behavioral rules have the full history to evaluate against.
* Blocking an entity propagates to future transactions automatically.

Missing `vendor_data` isn't a hard error — the transaction is stored as an external snapshot — but you lose entity-level aggregation.

## Remediation sessions

When a rule action moves a transaction to `AWAITING_USER`, Didit automatically creates a **remediation session** - a standard hosted verification session for the same `vendor_data`.
Custom rules always name the workflow on the action itself; for built-in library rules the workflow falls back to the [remediation workflow in your transaction settings](/transaction-monitoring/settings#remediation-workflow), then your application's Biometric Authentication workflow (when the subject has a stored reference face), then your application's default workflow.
Travel Rule transactions are excluded from this automatic fallback - their pending action is wallet-ownership proof.
The transaction (response, every subsequent read, and every webhook) carries it in the canonical `action_required` block:

```json theme={null}
{
  "status": "AWAITING_USER",
  "action_required": {
    "type": "verification_session",
    "url": "https://verify.didit.me/session/...",
    "session_id": "11111111-2222-3333-4444-555555555555",
    "session_token": "SESSION_TOKEN",
    "status": "Not Started"
  },
  "remediation": {
    "session_id": "11111111-2222-3333-4444-555555555555",
    "session_token": "SESSION_TOKEN",
    "url": "https://verify.didit.me/session/...",
    "status": "Not Started"
  }
}
```

`remediation` is kept for backward compatibility; `action_required` is the canonical block (it also carries `wallet_ownership` actions for [Travel Rule](/transaction-monitoring/travel-rule) transfers).

The user completes the verification - for a **Biometric Authentication** workflow, a liveness-checked selfie matched against the face captured at their original KYC onboarding, the strongest step-up for [high-value transactions](/transaction-monitoring/sdk-transaction-submission#example-step-up-biometrics-for-a-high-value-withdrawal).
Once the session reaches a decision, the transaction follows it automatically - `APPROVED` (`decision_reason_code: user_action_approved`), `DECLINED` (`user_action_declined`), or `IN_REVIEW` (`user_action_in_review`) - and a `transaction.status.updated` webhook fires.

<Note>
  Any workflow type except KYB can be selected.
  Biometric Authentication workflows (and workflows where face match runs before ID verification) require the subject to already have a stored reference face from an approved verification; users without one are sent to your application's default workflow instead, with a note on the transaction recording the substitution.
</Note>

## Best practices

| Practice                                                                 | Why                                                                                                              |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| Always pass `vendor_data`                                                | Enables aggregation.                                                                                             |
| Use your own `txn_id`                                                    | Idempotency.                                                                                                     |
| Subscribe to both `transaction.created` and `transaction.status.updated` | Catch async state changes (e.g. analyst review outcomes).                                                        |
| Set rule thresholds deliberately                                         | Low thresholds = more alerts; high = more missed risk. See [risk scoring](/transaction-monitoring/risk-scoring). |
| Monitor rule run counts in console                                       | Catch rules that never fire (misconfigured) or fire on everything (too loose).                                   |
| Test rules in TEST mode before going live                                | See [rules](/transaction-monitoring/rules).                                                                      |

## Rate limits

See [rate limiting](/integration/rate-limiting). Transaction create is one of the highest-throughput endpoints; typical limits are in the hundreds of requests per second. Burst carefully.

## Next steps

<CardGroup cols={3}>
  <Card title="Transactions" icon="arrow-right-to-bracket" href="/transaction-monitoring/transactions">
    Full payload reference.
  </Card>

  <Card title="Rules" icon="scale-balanced" href="/transaction-monitoring/rules">
    How rules evaluate.
  </Card>

  <Card title="Webhooks" icon="bolt" href="/transaction-monitoring/webhooks">
    Event catalog.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/transaction-monitoring/troubleshooting">
    Common integration issues.
  </Card>
</CardGroup>
