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

# Event Ingestion

> Send non-monetary activity - logins, gameplay, bonuses, payments, disputes, account security - to the same engine that scores your transactions.

Transactions are the money. Events are everything else your platform knows: the login that preceded the withdrawal, the hand that moved chips between two accounts, the bonus that was claimed twice, the chargeback that arrived a week later.

Events use one envelope for every category, are idempotent by design, and are evaluated by the [same rule engine](/transaction-monitoring/event-rules) that scores your [transactions](/transaction-monitoring/transactions).

<Note>
  Event ingestion is **opt-in**. Turn it on in **Transactions → Settings → Event monitoring**.
  Until you do, nothing changes: your existing transaction rules keep firing on transactions only, and an event rule cannot be created by accident because a rule applies to transactions unless you explicitly include events.
</Note>

## Endpoints

| Endpoint                                 | Auth                 | Use it for                       |
| ---------------------------------------- | -------------------- | -------------------------------- |
| `POST /v3/events/`                       | API key              | One event from your backend      |
| `POST /v3/events/batch/`                 | API key              | Up to 500 events in one request  |
| `POST /v3/events/sdk-token/`             | API key              | Mint a scoped device credential  |
| `POST /v1/events/`                       | `X-Monitoring-Token` | One event from the device SDK    |
| `POST /v1/events/batch/`                 | `X-Monitoring-Token` | A flushed SDK buffer             |
| `GET /v3/events/`                        | API key              | List and filter events           |
| `GET /v3/events/{event_uuid}/`           | API key              | One event with its rule evidence |
| `GET /v3/events/subjects/{vendor_data}/` | API key              | One subject's timeline           |
| `GET /v3/events/enrichment-metrics/`     | API key              | IP-enrichment cache health       |

The `/v3/` lane is the server lane and uses the same `x-api-key` authentication as the rest of the transaction API. The `/v1/events/` lane is the device lane - see [SDK event submission](/transaction-monitoring/sdk-event-submission).

## The envelope

```bash theme={null}
curl -X POST https://verification.didit.me/v3/events/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "login-2026-08-31-000001",
    "event_category": "auth",
    "action_type": "login",
    "occurred_at": "2026-08-31T20:40:31.123Z",
    "time_zone": "UTC",
    "subject": {
      "entity_type": "individual",
      "vendor_data": "user_391"
    },
    "session": { "session_id": "web-session-abc", "sequence": 142 },
    "device_context": {
      "fingerprint": "didit-fp-v2:a8c1",
      "network_context": { "ip_address": "203.0.113.42" }
    },
    "payload": { "method": "password" }
  }'
```

```json theme={null}
{
  "event_uuid": "0f2b...",
  "event_id": "login-2026-08-31-000001",
  "status": "ACCEPTED",
  "score": 0,
  "rules_evaluated_count": 6,
  "rules_matched_count": 0,
  "correlation_pending": false,
  "retention_class": "standard",
  "expires_at": "2026-09-30T20:40:31.123Z",
  "idempotent_replay": false,
  "rule_matches": []
}
```

| Field                  | Required | Notes                                                                                 |
| ---------------------- | -------- | ------------------------------------------------------------------------------------- |
| `event_id`             | yes      | Your own identifier, unique per application. It is the idempotency key.               |
| `event_category`       | yes      | One of the ten categories below.                                                      |
| `action_type`          | yes      | Open string, up to 128 characters.                                                    |
| `occurred_at`          | yes      | When it happened on your side. Up to 7 days in the past, 5 minutes in the future.     |
| `subject.vendor_data`  | yes      | Your user id. Resolved to the same User a transaction would resolve to.               |
| `subject.entity_type`  | no       | `individual` (default) or `company`.                                                  |
| `business.vendor_data` | no       | The business the event belongs to.                                                    |
| `session`              | no       | `session_id`, `sequence`, `page_url`, `referrer`.                                     |
| `device_context`       | no       | `fingerprint`, `persistent_device_id`, `bot_score`, and `network_context.ip_address`. |
| `transaction_context`  | no       | `amount`, `currency`, `direction`, `payment_reference_id`.                            |
| `payload`              | no       | The category's domain fields. Up to 16 KB.                                            |
| `custom_values`        | no       | Up to 100 scalar keys, addressable from rules as `custom_values.<key>`.               |

## Categories

Ten first-class categories, each with typed validation and managed [rule fields](/transaction-monitoring/event-rules#event-fields).

| Category               | Typical actions                                                                                                                                   |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth`                 | `signup`, `login`, `login_failed`, `logout`, `password_reset`, `mfa_reset`, `mfa_challenge`                                                       |
| `session`              | `session_started`, `session_ended`, `page_view`, `sdk_loaded`, `device_seen`, `bot_signal_observed`                                               |
| `gameplay`             | `round_started`, `round_completed`, `bet_placed`, `bet_settled`, `cashout`, `limit_change`, `p2p_settlement`                                      |
| `bonus`                | `bonus_claimed`, `bonus_wagered`, `bonus_released`, `promotion_viewed`                                                                            |
| `affiliate`            | `affiliate_attributed`, `referral_clicked`, `campaign_joined`, `commission_event`                                                                 |
| `payment`              | `deposit_initiated`, `deposit_completed`, `withdrawal_requested`, `withdrawal_completed`, `withdrawal_address_changed`, `payment_instrument_seen` |
| `dispute`              | `chargeback_opened`, `chargeback_won`, `chargeback_lost`, `refund_issued`, `payment_dispute_updated`                                              |
| `security`             | `login_attempt`, `login_succeeded`, `login_failed`, `password_changed`, `mfa_changed`, `credential_reset`, `country_changed`, `device_changed`    |
| `responsible_gambling` | `self_exclusion_checked`, `self_exclusion_match`, `re_registration_attempt`, `limit_breach_attempt`, `registry_status_changed`                    |
| `custom`               | Anything you define                                                                                                                               |

`auth` is the broad product category; `security` is the risk-grade subset with the ordered lifecycle that account-takeover rules read.

### Required payload keys

Some actions cannot be correlated without specific keys, so validation rejects them rather than storing evidence nobody can join.

| Action                                                                                                           | Required                                                                                                                           |
| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `gameplay.p2p_settlement`                                                                                        | `game_id`, `counterparty_vendor_data`, `stake_amount`, `net_result_amount`, `role`, and one of `hand_id` / `round_id` / `match_id` |
| `gameplay.bet_placed`, `gameplay.bet_settled`                                                                    | `game_id`                                                                                                                          |
| `payment.deposit_*`, `payment.withdrawal_completed`, `payment.withdrawal_requested`, `payment.deposit_initiated` | `payment_reference_id`, `amount`, `currency`, `payment_method`, `status`                                                           |
| `payment.withdrawal_address_changed`                                                                             | `withdrawal_address_hash`                                                                                                          |
| `dispute.chargeback_*`                                                                                           | `payment_reference_id`, and one of `dispute_id` / `chargeback_id`                                                                  |
| `security.*_changed`                                                                                             | `changed_field`; account- or payout-control changes also need `previous_reference_hash` or `new_reference_hash`                    |
| `session.bot_signal_observed`                                                                                    | `telemetry_schema_version`, plus a `bot_score` or at least one telemetry metric                                                    |
| `responsible_gambling.self_exclusion_match`                                                                      | `registry_source`, `exclusion_status`, `identity_match_ref`                                                                        |

<Warning>
  Send **references and hashes**, never raw secrets. Payment instruments, withdrawal destinations and account-control changes are stored as the hashed reference you send. Never send a card number, a full withdrawal address, a password hash or an MFA secret.
</Warning>

### Example: a P2P settlement

```json theme={null}
{
  "event_id": "p2p-hand-8842-user-391",
  "event_category": "gameplay",
  "action_type": "p2p_settlement",
  "occurred_at": "2026-08-31T12:34:17.392Z",
  "subject": { "vendor_data": "user_391" },
  "payload": {
    "game_id": "poker_holdem",
    "table_id": "table_17",
    "hand_id": "hand_8842",
    "role": "winner",
    "counterparty_vendor_data": "user_812",
    "stake_amount": "250.00",
    "net_result_amount": "240.50",
    "effective_margin": "0.038",
    "operator_ledger_ref": "ledger_998123"
  }
}
```

### Example: a chargeback

```json theme={null}
{
  "event_id": "chargeback-cb_921-opened",
  "event_category": "dispute",
  "action_type": "chargeback_opened",
  "occurred_at": "2026-08-31T09:22:05.000Z",
  "subject": { "vendor_data": "user_391" },
  "transaction_context": {
    "amount": "1000.00",
    "currency": "EUR",
    "payment_reference_id": "dep_735"
  },
  "payload": {
    "dispute_id": "disp_498",
    "chargeback_id": "cb_921",
    "reason_code": "fraudulent",
    "liability_shift": false
  }
}
```

Dispute lifecycles are append-only: a correction is a new `dispute` event, never an edit of the first one.

## Idempotency

`event_id` is unique per application and is the whole idempotency contract.

* **Same `event_id`, same content** returns `200` with `"idempotent_replay": true` and the originally stored event. Retry as often as you like.
* **Same `event_id`, different content** returns `409`. You reused an id by mistake.

## Batches

`POST /v3/events/batch/` accepts up to **500 events**. A batch is partially successful by design: one malformed page view must never take down the chargeback next to it.

```json theme={null}
{
  "accepted_count": 2,
  "replayed_count": 0,
  "rejected_count": 1,
  "results": [
    { "index": 0, "outcome": "rejected", "errors": { "occurred_at": ["This field is required."] } },
    { "index": 1, "outcome": "accepted", "event_uuid": "…", "status": "ACCEPTED" },
    { "index": 2, "outcome": "accepted", "event_uuid": "…", "status": "IN_REVIEW" }
  ]
}
```

The response is `207 Multi-Status` when anything was rejected and `201 Created` when everything landed.

## Statuses

| Status      | Meaning                                                                           |
| ----------- | --------------------------------------------------------------------------------- |
| `ACCEPTED`  | Stored. No rule changed its standing (this is the normal answer for most events). |
| `APPROVED`  | A rule ran and the score stayed under your review threshold.                      |
| `IN_REVIEW` | A rule pushed it over the review threshold, or set the status directly.           |
| `DECLINED`  | A rule declined it.                                                               |

`correlation_pending: true` means a cross-subject rule still has to run; it resolves within minutes and the event's status and score update in place. See [asynchronous rules](/transaction-monitoring/event-rules#real-time-and-asynchronous-rules).

## Retention

Events are far higher volume than transactions, so each one is filed into a retention class on arrival.

| Class                    | Default hot retention | What lands here                                                            |
| ------------------------ | --------------------- | -------------------------------------------------------------------------- |
| `low_value`              | 14 days               | Page views, heartbeats, device-seen pings, bot telemetry, promotion views  |
| `standard`               | 30 days               | Auth, session, gameplay, bonus, affiliate, custom                          |
| `risk_relevant`          | 90 days               | Security and responsible-gambling events, and **any event a rule matched** |
| `monetary_or_compliance` | Transaction retention | Payment and dispute events                                                 |

Two rules follow from the table:

* An event that matches a rule is evidence, so it is promoted to `risk_relevant` automatically.
* Payment and dispute events keep transaction retention. They are never expired on the event schedule, and the per-class overrides cannot shorten them.

Override the three expirable classes per application in **Transactions → Settings → Event monitoring** (1-730 days each).

## IP enrichment

Events carry the same [IP enrichment](/transaction-monitoring/ip-enrichment) as transactions - country, ASN, VPN/proxy and data-centre flags - and enrichment is resolved **per unique IP address, never per event**. A session that fires 200 page views from one address resolves that address once.

Low-value telemetry goes further: it reads enrichment only if some other event already resolved that address. It never triggers a lookup of its own.

`GET /v3/events/enrichment-metrics/` reports the result, and the console shows the same numbers above the events list:

```json theme={null}
{
  "events_ingested": 128450,
  "enrichment_lookups": 41230,
  "enrichment_cache_hits": 40118,
  "enrichment_provider_calls": 1112,
  "enrichment_skipped_low_value": 74320,
  "unique_ips_seen": 1112,
  "cache_hit_rate": 0.973,
  "unique_ip_ratio": 0.0087,
  "provider_calls_per_1k_events": 8.65,
  "period_days": 30
}
```

## Reading events back

```bash theme={null}
curl "https://verification.didit.me/v3/events/?event_category=payment&event_category=dispute&matched_only=true" \
  -H "x-api-key: YOUR_API_KEY"
```

Filters: `event_category`, `action_type`, `status`, `retention_class`, `subject_vendor_data`, `business_vendor_data`, `session_id`, `device_fingerprint`, `ip_address`, `game_id`, `round_id`, `affiliate_code`, `payment_reference_id`, `counterparty_vendor_data`, `matched_only`, `occurred_from`, `occurred_to`, `search`. Multi-value filters repeat the parameter (`?status=A&status=B`).

`GET /v3/events/{event_uuid}/` adds the payload, the device context, the IP enrichment, the subject's cached risk signals, and every rule match with its evidence - including whether each matched field was server-derived, SDK-derived or customer-supplied.

`DELETE /v3/events/{event_uuid}/delete/` deletes one event. It stops being returned by every read, and deletion is irreversible.

## Next

<CardGroup cols={2}>
  <Card title="Rules on events" icon="filter" href="/transaction-monitoring/event-rules">
    Velocity, ratio, sequence and correlation rules over event data.
  </Card>

  <Card title="SDK event submission" icon="mobile" href="/transaction-monitoring/sdk-event-submission">
    Capture events straight from the device with a scoped credential.
  </Card>
</CardGroup>
