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

> Common KYT issues — txn_id collisions, rules not firing, AML timeouts, webhook signature mismatches, currency quirks — with causes and fixes.

A reference of common Transaction Monitoring issues, with symptom, cause, and fix.

## Duplicate `txn_id` error

**Symptom.** Submitting a transaction returns the existing record instead of an error — and your code doesn't realize it's a duplicate.

**Cause.** `txn_id` is unique per application. Re-submitting the same `txn_id` returns the existing transaction (idempotent).

**Fix.**

* Generate `txn_id` deterministically on your side (e.g. use your own transaction DB ID).
* Compare the response's `transaction_id` with what you expect; if the response's `created_at` doesn't match "now", you hit an existing record.

## Rule not firing

**Symptom.** You created a rule that should match, but no transactions are showing it in `rule_runs[]` with `matched: true`.

**Causes and fixes.**

* **Rule mode is `DISABLED` or `TEST`** — check *Rules → \[rule] → Mode*. Only `ACTIVE` rules contribute to scoring; `TEST` rules evaluate but don't affect status.
* **Scope filter excludes the transaction** — e.g. scope set to `transaction_type = finance` won't match `travelRule` transactions.
* **Aggregation window is shorter than needed** — a "20 transactions in 30 days" rule won't match after only 15 transactions.
* **Condition field is null** — e.g. your rule checks `counterparty.country_code` but you didn't send a counterparty.
* **Currency conversion** — the rule thresholds on `amount_in_default_currency`; if your application's default currency differs from the transaction currency, the converted amount may be below threshold.

Use the *Rules → \[rule] → Backtest* feature to replay a past transaction and see why the rule did / didn't fire.

## Rule firing on everything

**Symptom.** A rule fires on 90%+ of transactions — your review queue is flooded.

**Cause.** Conditions are too broad, or thresholds are too low.

**Fix.**

* Add `scope` filters to narrow applicability (e.g. only outbound, only certain transaction types).
* Raise thresholds (amounts, velocity counts).
* Put the rule in `TEST` mode for a week — analyze the `rule_runs` to see the match rate and tighten conditions before re-enabling.

## Crypto screening timed out

**Symptom.** Transaction completed with `status: APPROVED` but the `provider_results[]` array shows `status: FAILED` for a wallet, transaction hash, or crypto screening provider.

**Causes.**

* Provider API was briefly unavailable.
* Query timed out (typically \~10s).

**Fix.**

* Transactions never fail because of provider issues — enrichment is best-effort.
* If a specific provider fails repeatedly, check its status page or open a support ticket.
* Consider configuring provider preferences in *Settings → Transaction Monitoring → Providers* to route to alternative providers.

## IP enrichment missing

**Symptom.** A transaction's `applicant.ip_enrichment` is absent or partial.

**Causes.**

* No `applicant.device.ip` in the submission.
* IP is private / reserved (not routable).
* All enrichment providers were temporarily unavailable.

**Fix.**

* Capture the submitter's IP in your client/server and pass it on every transaction.
* Use only routable IPs — Didit silently skips private ranges.
* Check *Settings → Status* in the console for provider health.

## Webhook signature mismatch

**Symptom.** Your endpoint rejects events because the signature doesn't match.

**Causes.**

* Raw body was modified (JSON re-stringified with different whitespace).
* Wrong secret.
* Multiple destinations with the same URL — signatures differ per destination.

**Fix.**

* **Sign over the raw request body**, not the parsed JSON. Many frameworks expose a raw-body middleware.
* Rotate the secret if compromised: *Webhook destinations → \[destination] → Rotate secret*.
* Keep one destination per URL to avoid secret confusion.

## Webhook not arriving

**Symptom.** Transactions are being created but webhooks never arrive.

**Causes.**

* Destination `enabled` is `false`.
* URL not publicly reachable.
* Destination auto-disabled after repeated failures.
* Event not in `subscribed_events`.

**Fix.**

* Send a test event: *Webhook destinations → \[destination] → Send test*.
* Check recent deliveries: *Webhooks → \[destination] → Deliveries*. See 4xx/5xx responses with body/headers.
* Re-enable disabled destinations.

## Currency conversion looks wrong

**Symptom.** `amount_in_default_currency` doesn't match your expected rate.

**Causes.**

* FX rate is the mid-market rate at `txn_date`.
* Backdated transactions use the historical rate at that date.
* Some currencies (exotic FX) have coarser update intervals.

**Fix.**

* Use `amount` and `currency` (native) for display.
* Use `amount_in_default_currency` only for internal rule thresholding.
* For audit, the transaction record pins the conversion rate used at evaluation time.

## Counterparty not matching my blocklist

**Symptom.** You blocklisted a business by `vendor_data`, but a transaction with that counterparty still went through.

**Causes.**

* Counterparty in the transaction payload has no `vendor_data` — stored as external snapshot.
* `vendor_data` casing differs.

**Fix.**

* Always pass `counterparty.vendor_data` when the counterparty is an entity on your platform.
* Use the same casing everywhere — `vendor_data` is case-sensitive.

## AWAITING\_USER transactions stuck

**Symptom.** Transactions are stuck in `AWAITING_USER` because the user never completes the remediation session.

**Causes.**

* User didn't open the remediation session URL.
* Remediation session expired.

**Fix.**

* The transaction response's `remediation_session_url` is the link to deliver. Re-deliver to the user.
* Remediation sessions follow the same [KYC quickstart](/getting-started/quick-start) pattern — subscribe to completion webhooks.
* Set a timeout in your own system to expire stale transactions if remediation never completes.

## Rate limit on create

**Symptom.** `429 Too Many Requests` during high-volume bursts.

**Fix.**

* Respect the `Retry-After` header.
* Throttle client-side for burst traffic.
* Contact support for higher limits on proven production volume.

See [rate limiting](/integration/rate-limiting).

## Next steps

<CardGroup cols={3}>
  <Card title="Integration guide" icon="book" href="/transaction-monitoring/integration-guide">
    Full integration patterns.
  </Card>

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

  <Card title="Webhooks" icon="bolt" href="/transaction-monitoring/webhooks">
    Webhook mechanics.
  </Card>
</CardGroup>
