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

# Early Fraud Warnings

Early Fraud Warnings (EFWs) are real-time alerts issued by card networks when a cardholder reports potential fraud on a transaction. They arrive **before** a formal dispute (chargeback) is filed, giving you a window to proactively refund a suspicious charge and potentially avoid a chargeback entirely.

***

## Table of Contents

* [Overview](#overview)
* [How EFWs Differ from Disputes](#how-efws-differ-from-disputes)
* [The EarlyFraudWarning Object](#the-earlyfraudwarning-object)
* [Fraud Type Reference](#fraud-type-reference)
* [Webhook Events](#webhook-events)
* [Recommended Response Strategy](#recommended-response-strategy)
* [End-to-End Testing Guide](#end-to-end-testing-guide)

***

## Overview

```text theme={null}
Card issuer flags transaction as potentially fraudulent
       │
       ▼
Card network fires radar.early_fraud_warning.created
       │
       ▼
Flex receives event → resolves charge → creates EFW record
       │
       ▼
Partner receives webhook event: radar.early_fraud_warning.created
       │
       ├─── actionable: true  → Consider proactive refund to prevent chargeback
       └─── actionable: false → Warning is informational only (already refunded or disputed)
```

**There is no REST API to list or retrieve EFWs.** All EFW data is delivered exclusively via webhook events. Subscribe to `radar.early_fraud_warning.created` and `radar.early_fraud_warning.updated` in your webhook configuration.

### Key facts

* EFWs are **read-only** — Flex does not expose an API to query or acknowledge them.
* An EFW does **not** mean a dispute has been filed. It is a signal that the cardholder may have flagged the transaction.
* A single charge may receive multiple EFWs (one created, then updated as circumstances change).
* `actionable: true` means the charge has not yet been refunded or disputed — act now.
* `actionable: false` means the charge has already been fully refunded or a dispute has been filed. No action needed.

***

## How EFWs Differ from Disputes

|                        | Early Fraud Warning                              | Dispute (Chargeback)                             |
| ---------------------- | ------------------------------------------------ | ------------------------------------------------ |
| **Trigger**            | Card issuer files fraud report with card network | Cardholder formally disputes with their bank     |
| **Impact on funds**    | No immediate fund withdrawal                     | Funds withdrawn immediately                      |
| **Fee**                | No fee                                           | Dispute fee charged                              |
| **Flex API**           | Webhook only                                     | Full REST API                                    |
| **Recommended action** | Consider proactive refund                        | Submit evidence or accept loss                   |
| **Time pressure**      | Respond before a formal dispute is filed         | Evidence deadline (`due_by`) typically 7–21 days |

***

## The EarlyFraudWarning Object

This is the object delivered inside every `radar.early_fraud_warning.*` webhook event.

```json theme={null}
{
  "early_fraud_warning_id": "fefw_01jx3q8kt1b9a7n2c4d5e6f7g8",
  "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
  "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
  "actionable": true,
  "fraud_type": "card_never_received",
  "client_reference_id": "order_12345",
  "test_mode": false,
  "created_at": "2026-02-15T10:20:00Z"
}
```

### Field Reference

| Field                    | Type           | Description                                                                                                                              |
| ------------------------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `early_fraud_warning_id` | string         | Flex EFW ID (`fefw_` prefix).                                                                                                            |
| `charge_id`              | string \| null | Flex charge ID (`fch_`) for the flagged transaction.                                                                                     |
| `payment_intent_id`      | string \| null | Flex payment intent ID (`fpi_`) linked to the charge.                                                                                    |
| `actionable`             | boolean        | `true` if the charge is still refundable and no dispute has been filed. `false` once refunded or disputed.                               |
| `fraud_type`             | string         | Fraud category from the card issuer. See [Fraud Type Reference](#fraud-type-reference).                                                  |
| `client_reference_id`    | string \| null | The `client_reference_id` you passed when creating the checkout session, if any. Useful for correlating EFWs to your internal order IDs. |
| `test_mode`              | boolean        | `true` for test-mode transactions.                                                                                                       |
| `created_at`             | string         | ISO 8601 timestamp when Flex received the EFW.                                                                                           |

***

## Fraud Type Reference

| Value                         | Description                                                                    |
| ----------------------------- | ------------------------------------------------------------------------------ |
| `card_never_received`         | Physical card was never received by the cardholder (e.g. intercepted in mail). |
| `fraudulent_card_application` | Card was obtained through a fraudulent application.                            |
| `made_with_counterfeit_card`  | Physical counterfeit card was used.                                            |
| `made_with_lost_card`         | Lost card was used without authorisation.                                      |
| `made_with_stolen_card`       | Stolen card was used without authorisation.                                    |
| `unauthorized_use_of_card`    | Card was used without the cardholder's authorisation (general).                |
| `misc`                        | Other / unclassified fraud type.                                               |

`unauthorized_use_of_card` and `card_never_received` are the most common in ecommerce.

***

## Webhook Events

### `radar.early_fraud_warning.created`

Fired when Flex receives a new early fraud warning for one of your charges.

```json theme={null}
{
  "event_id": "fevt_01jx3q8kt1b9a7n2c4d5e6f7g8",
  "event_type": "radar.early_fraud_warning.created",
  "event_dt": 1770000000,
  "object": {
    "early_fraud_warning_id": "fefw_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
    "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
    "actionable": true,
    "fraud_type": "card_never_received",
    "client_reference_id": "order_12345",
    "test_mode": false,
    "created_at": "2026-02-15T10:20:00Z"
  }
}
```

### `radar.early_fraud_warning.updated`

Fired when an existing EFW changes — most commonly when `actionable` transitions from `true` to `false` (because the charge was refunded or a formal dispute was filed).

```json theme={null}
{
  "event_id": "fevt_01jx3q9ab1c2d3e4f5g6h7j8k9",
  "event_type": "radar.early_fraud_warning.updated",
  "event_dt": 1770003600,
  "object": {
    "early_fraud_warning_id": "fefw_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
    "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
    "actionable": false,
    "fraud_type": "card_never_received",
    "client_reference_id": "order_12345",
    "test_mode": false,
    "created_at": "2026-02-15T10:20:00Z"
  }
}
```

### Webhook signature verification

All Flex webhooks include a `flex-signature` header. Verify it using your webhook signing secret from the Flex dashboard. See the Webhooks documentation for signature verification details.

### Idempotency

EFWs are upserted on `early_fraud_warning_id` — receiving the same event twice will not create duplicate records. Your webhook handler should be idempotent: processing the same `event_id` twice should have no side-effects.

***

## Recommended Response Strategy

When you receive `radar.early_fraud_warning.created` with `actionable: true`:

### 1. Assess the risk

Check the `fraud_type` and correlate the `charge_id` or `client_reference_id` to the order in your system.

* Is this order already shipped? Shipped orders are harder to recover.
* Is the `fraud_type` high-risk (`card_never_received`, `made_with_stolen_card`)? These have a higher probability of becoming a formal chargeback.

### 2. Decide: refund or wait

| Scenario                                        | Recommendation                                                      |
| ----------------------------------------------- | ------------------------------------------------------------------- |
| Order not yet shipped                           | Cancel and refund immediately.                                      |
| Order shipped, high-value, high-risk fraud type | Proactive refund may still be cheaper than losing a dispute + fee.  |
| Order shipped, low-value                        | Weigh refund cost vs dispute fee (\~\$15) + chargeback rate impact. |
| `actionable: false`                             | No action needed — already resolved.                                |

### 3. Issue a refund (if decided)

Use the Flex Refunds API to issue a full or partial refund for the `charge_id` in the EFW. Once refunded, the EFW will be updated to `actionable: false` and you will receive a `radar.early_fraud_warning.updated` event.

### 4. Track your chargeback rate

A high chargeback rate (typically >1%) can result in penalties from card networks. EFWs are an early signal to detect patterns — monitor `fraud_type` across EFWs to identify systemic fraud patterns in your customer base.

***

## End-to-End Testing Guide

> **Note**: EFW events cannot be triggered via test card payments alone — card networks do not issue EFWs in response to standard test transactions. Use the synthetic webhook method below to simulate the full flow end-to-end against your local Flex server.

### Prerequisites

* Flex API key and webhook signing secret (from your Flex dashboard)
* Your webhook endpoint URL (must be reachable — use [ngrok](https://ngrok.com/) for local development)
* Python 3 or any HMAC-SHA256 capable language

### Step 1 — Register a webhook endpoint

In your Flex dashboard, register your endpoint URL and subscribe to:

* `radar.early_fraud_warning.created`
* `radar.early_fraud_warning.updated`

### Step 2 — Create a charge

Create a checkout session and complete a payment. Note the `charge_id` (`fch_...`) and `payment_intent_id` (`fpi_...`) from the checkout session response.

Optionally include a `client_reference_id` in your checkout session request to verify it is propagated to EFW events:

```http theme={null}
POST /v1/checkout/sessions
Authorization: Bearer fsk_...
Content-Type: application/json

{
  "checkout_session": {
    "line_items": [{ "price": "fprice_...", "quantity": 1 }],
    "client_reference_id": "order_12345",
    "mode": "payment",
    "success_url": "https://example.com/success",
    "cancel_url": "https://example.com/cancel"
  }
}
```

### Step 3 — Send a synthetic EFW event

Use the script below to generate a correctly signed event and POST it to your local Flex server. Flex will process it and forward the resulting `radar.early_fraud_warning.created` event to your registered webhook endpoint.

```python theme={null}
import hmac
import hashlib
import json
import time
import urllib.request

# Your webhook signing secret (from your .env or Flex dashboard)
WEBHOOK_SECRET = "whsec_your_signing_secret_here"
FLEX_WEBHOOK_URL = "http://localhost:8000/v1/webhooks/stripe"

CHARGE_ID = "ch_..."   # The underlying charge ID — see note below
PI_ID = "pi_..."       # The underlying payment intent ID — see note below

timestamp = int(time.time())

payload = {
    "id": "evt_synthetic_efw_001",
    "object": "event",
    "api_version": "2023-10-16",
    "created": timestamp,
    "livemode": False,
    "pending_webhooks": 1,
    "request": {"id": None, "idempotency_key": None},
    "type": "radar.early_fraud_warning.created",
    "data": {
        "object": {
            "id": "efw_synthetic_001",
            "object": "radar.early_fraud_warning",
            "actionable": True,
            "charge": CHARGE_ID,
            "created": timestamp,
            "fraud_type": "card_never_received",
            "livemode": False,
            "payment_intent": PI_ID
        }
    }
}

body = json.dumps(payload, separators=(',', ':'))
signed_payload = f"{timestamp}.{body}"
mac = hmac.new(
    WEBHOOK_SECRET.encode('utf-8'),
    signed_payload.encode('utf-8'),
    hashlib.sha256
)
sig_header = f"t={timestamp},v1={mac.hexdigest()}"

req = urllib.request.Request(
    FLEX_WEBHOOK_URL,
    data=body.encode('utf-8'),
    headers={
        'Content-Type': 'application/json',
        'Stripe-Signature': sig_header,  # required header name for webhook signature
    },
    method='POST'
)

with urllib.request.urlopen(req) as resp:
    print(f"Status: {resp.status}")  # expect 200
```

> **Where to find the charge and payment intent IDs**: After completing a payment in the Flex checkout, look at the `checkout_session` response — the `payment_intent` field contains the payment intent ID (`pi_...`). The corresponding charge ID (`ch_...`) can be retrieved by fetching the payment intent from the Flex API.

### Step 4 — Verify the event arrives at your endpoint

Your webhook handler should receive:

```json theme={null}
{
  "event_id": "fevt_...",
  "event_type": "radar.early_fraud_warning.created",
  "event_dt": 1770000000,
  "object": {
    "early_fraud_warning_id": "fefw_...",
    "charge_id": "fch_...",
    "payment_intent_id": "fpi_...",
    "actionable": true,
    "fraud_type": "card_never_received",
    "client_reference_id": "order_12345",
    "test_mode": false,
    "created_at": "2026-02-15T10:20:00Z"
  }
}
```

Verify:

* `early_fraud_warning_id` has `fefw_` prefix (Flex ID)
* `charge_id` and `payment_intent_id` are Flex IDs (`fch_`, `fpi_`)
* `client_reference_id` matches what you set on the checkout session (if applicable)
* `actionable: true`

### Step 5 — Test the updated event

Re-send with `actionable: false` and `type: "radar.early_fraud_warning.updated"`, keeping the same `id` in `data.object`:

```python theme={null}
payload["type"] = "radar.early_fraud_warning.updated"
payload["data"]["object"]["actionable"] = False
```

Verify:

* Your endpoint receives `radar.early_fraud_warning.updated`
* The same `early_fraud_warning_id` is in the event (upserted, not duplicated)
* `actionable` is now `false`

### Step 6 — Test idempotency

Resend the same `radar.early_fraud_warning.created` event (same `id` in `data.object` — e.g. `efw_synthetic_001` — with a different event timestamp). Verify your handler processes it without creating a duplicate record.

### Step 7 — Verify `client_reference_id` propagation

If you created the checkout session with a `client_reference_id`:

* Confirm it appears in the `object` of the EFW event
* This lets you correlate the EFW directly to your internal order without any additional lookups

### Checklist

| Scenario                                      | Expected outcome                                      |
| --------------------------------------------- | ----------------------------------------------------- |
| `created` event received and parsed           | `fefw_` ID in `object`, all fields populated          |
| `client_reference_id` set on checkout session | Appears in EFW event `object.client_reference_id`     |
| `updated` event changes `actionable: false`   | Same `fefw_` ID, `actionable` updated                 |
| Duplicate `created` event (same EFW ID)       | No duplicate — idempotent upsert                      |
| Missing signature header                      | `400 Bad Request` from Flex                           |
| Wrong signing secret                          | `400 Bad Request` from Flex                           |
| EFW referencing a charge not in Flex          | Event accepted (200), EFW not persisted, error logged |
