Duplicate txn_id error
Symptom. Submitting a transaction returns 400 with A transaction with this transaction_id already exists. on the transaction_id field, or 409 when two identical requests race each other.
Cause. txn_id (the transaction_id you send) is unique per application.
Didit rejects a second transaction with the same transaction_id and does not return the existing record.
Fix.
- Generate
txn_iddeterministically on your side (e.g. use your own transaction DB ID). - On a
400or409for a duplicatetransaction_idduring a retry, look the existing transaction up instead of resubmitting: read it withGET /v3/transactions/{uuid}/using theuuidfrom the first response, or use thetransaction_id(the Didit uuid) that thetransaction.createdwebhook carries next to yourtxn_id. - If the request was not a retry, give the new transaction its own
transaction_id.
Rule not firing
Symptom. You created a rule that should match, but no transactions are showing it inrule_runs[] with matched: true.
Causes and fixes.
- The application is in sandbox mode - a sandbox application stores the transaction and skips rule evaluation, AML screening, webhooks, and billing; only a Travel Rule exchange still runs.
rule_runscomes back empty and the score stays0, whatever mode your rules are in, and the transaction staysAPPROVEDunless an expiring Travel Rule transfer applies yourtimeout_outcome. Checkenvironmenton the transaction response:"sandbox"means it was never evaluated. See Transaction Monitoring in sandbox for how to validate a rule before it goes live. - Rule mode is
DISABLEDorTEST— check Rules → [rule] → Mode. OnlyACTIVErules contribute to scoring;TESTrules evaluate but don’t affect status. - Scope filter excludes the transaction — e.g. scope set to
transaction_type = financewon’t matchtravelRuletransactions. - 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_codebut 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.
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
scopefilters to narrow applicability (e.g. only outbound, only certain transaction types). - Raise thresholds (amounts, velocity counts).
- Put the rule in
TESTmode for a week — analyze therule_runsto see the match rate and tighten conditions before re-enabling.
Crypto screening timed out
Symptom. Transaction completed withstatus: 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).
- 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’sapplicant.ip_enrichment is absent or partial.
Causes.
- No
applicant.device.ipin the submission. - IP is private / reserved (not routable).
- All enrichment providers were temporarily unavailable.
- 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.
- 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
enabledisfalse. - URL not publicly reachable.
- Destination auto-disabled after repeated failures.
- Event not in
subscribed_events.
- 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.
- Use
amountandcurrency(native) for display. - Use
amount_in_default_currencyonly 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 byvendor_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_datacasing differs.
- Always pass
counterparty.vendor_datawhen the counterparty is an entity on your platform. - Use the same casing everywhere —
vendor_datais case-sensitive.
AWAITING_USER transactions stuck
Symptom. Transactions are stuck inAWAITING_USER because the user never completes the remediation session.
Causes.
- User didn’t open the remediation session URL.
- Remediation session expired.
- The transaction is a Travel Rule transfer that a library rule such as
travel-rule-pending-actionmoved toAWAITING_USER: Didit does not create its default remediation session for a Travel Rule transaction, so unless the rule names its own remediation workflow there is no link to deliver.
- The transaction response’s
remediation_session_urlis the link to deliver. Re-deliver to the user. - For a Travel Rule transaction, resolve the transfer from its Travel rule tab, then change the transaction status yourself, because resolving the transfer does not by itself move the transaction out of
AWAITING_USER. - Remediation sessions follow the same KYC quickstart 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-Afterheader. - Throttle client-side for burst traffic.
- Contact support for higher limits on proven production volume.
Next steps
Integration guide
Full integration patterns.
Rules
How rule evaluation works.
Webhooks
Webhook mechanics.