# Fraud warnings

Read issuer alerts, receive lifecycle webhooks and test your integration safely.


# Fraud warnings

An early fraud warning is an issuer report that a card payment may be fraudulent. It is not a formal dispute and does not itself refund a payment. A full refund can reduce the risk of a later chargeback, but prevention is not guaranteed.

This reference describes the **Merchant API** and **Merchant webhooks**. For dashboard guidance, see [Fraud warnings in the Help Center](/help/fraud-warnings).

## Authentication and environments

Base URL: `https://api.southbill.com/v1`.

Send a **secret Southbill API key** in `Authorization: Bearer YOUR_SECRET_KEY`. The key must include `fraud_warnings:read`. Never expose secret keys in browser code or mobile apps. Calls return only warnings belonging to the key's merchant and environment: test keys read test warnings; live keys read live warnings. Account restrictions and normal API rate limits apply.

See [API keys & authentication](/docs/getting-started/api-keys). These routes do not accept a Stripe key or an App API OAuth token.

## List warnings

`GET /v1/fraud_warnings`

```bash
curl 'https://api.southbill.com/v1/fraud_warnings?limit=25&status=open&actionable=true' \
  -H 'Authorization: Bearer YOUR_SECRET_KEY'
```

| Parameter | Meaning |
| --- | --- |
| `limit` | Page size. Use an integer from 1 to 100; default 25. |
| `starting_after` | Southbill warning UUID used as the cursor. Use an ID returned by this API, not an issuer warning ID or charge ID. |
| `status` | Optional: `open`, `refunded` or `disputed`. |
| `actionable` | Optional: literal `true` or `false`. |
| `created_gte` | Inclusive lower bound for warning creation, in Unix seconds. |
| `created_lte` | Inclusive upper bound for warning creation, in Unix seconds. |

The response is `{ "object": "list", "has_more": false, "data": [...] }`. Results are ordered newest first. When `has_more` is true, pass the last returned warning's `id` as `starting_after`, retaining your filters.

Pagination is keyset-based on creation time and ID, so no warning is skipped at a page boundary. An unknown or foreign `starting_after` returns `400`.

## Retrieve a warning

`GET /v1/fraud_warnings/{id}`

```bash
curl 'https://api.southbill.com/v1/fraud_warnings/WARNING_UUID' \
  -H 'Authorization: Bearer YOUR_SECRET_KEY'
```

Replace `WARNING_UUID` with the `id` returned by the list or webhook. The response is the warning object directly, not a list wrapper. An unknown ID, an ID belonging to another merchant, or an ID from the other environment returns `404` with `error.code: "resource_missing"`.

## Warning object

The following is an illustrative test object; IDs and timestamps are examples.

```json
{
  "id": "7b69f46b-46ba-4c76-8ed8-b76204f1a837",
  "object": "fraud_warning",
  "livemode": false,
  "charge_id": "ch_test_example",
  "payment_intent_id": null,
  "fraud_type": "unauthorized_use_of_card",
  "amount": 2500,
  "currency": "usd",
  "status": "open",
  "actionable": true,
  "created": 1791878400,
  "updated": 1791878400,
  "resolved": null
}
```

| Field | Meaning |
| --- | --- |
| `id` | Southbill warning UUID. Stable resource identifier, distinct from the webhook event ID. |
| `object` | Always `fraud_warning`. |
| `livemode` | `true` for live warnings, `false` for test warnings. |
| `charge_id` | Linked charge ID, or `null` if unavailable. |
| `payment_intent_id` | Linked PaymentIntent ID, or `null` if unavailable. |
| `fraud_type` | Issuer-reported fraud category, or `null` if unavailable. |
| `amount` | Original charge amount in currency minor units, or `null` if unavailable. It is not a fee, outstanding debt or remaining refundable amount. |
| `currency` | Lowercase currency code, or `null` if unavailable. Use the currency's minor-unit convention. |
| `status` | `open`, `refunded` or `disputed`. |
| `actionable` | Issuer actionability flag. Evaluate it together with `status`. |
| `created` | Warning creation time in Unix seconds. |
| `updated` | Stored warning update time in Unix seconds. Not a guarantee that an update webhook was emitted. |
| `resolved` | Recorded resolution time in Unix seconds, or `null` while unresolved. |

For USD, `2500` means USD 25.00. See [Currencies & amount format](/docs/getting-started/currencies-and-amounts).

Reported fraud categories are `card_never_received`, `fraudulent_card_application`, `made_with_counterfeit_card`, `made_with_lost_card`, `made_with_stolen_card`, `misc`, and `unauthorized_use_of_card`. Handle unknown future values gracefully.

## Status and safe handling

- `open` with `actionable: true`: review the current payment promptly. It may still be eligible for a refund.
- `open` with `actionable: false`: no currently actionable warning. This is not proof that the payment is legitimate.
- `refunded`: a full charge refund has been recorded. A partial refund does not by itself set this status.
- `disputed`: a dispute has been recorded; it takes precedence over refund status. Follow the dispute workflow and its deadline.

These are **read-only** endpoints. There is no update, delete or refund operation on a fraud-warning resource. Before taking a financial action, retrieve the warning and check the current payment, refund and dispute state. Do not automatically refund based solely on an old webhook snapshot. Use the separate [Refunds API](/docs/api/refunds) with its own permissions and idempotency requirements if your integration initiates refunds.

Southbill fees properly incurred remain non-refundable under Terms §11.6. A warning itself is not a chargeback fee; if a dispute follows, normal dispute rules apply. Do not infer or charge fees from the warning's `amount`.

## Webhook events

Configure a Merchant webhook endpoint to receive:

| Event | When it is emitted |
| --- | --- |
| `fraud_warning.created` | A warning is first stored for the merchant. It may already be refunded, disputed or non-actionable. |
| `fraud_warning.updated` | The stored `status` or `actionable` value changes. A timestamp-only or other field change does not trigger this event. |

The resource snapshot is under `data.object`. Merchant events use this envelope:

```json
{
  "id": "evt_example",
  "object": "event",
  "type": "fraud_warning.created",
  "livemode": false,
  "created": 1791878400,
  "data": {
    "object": {
      "id": "7b69f46b-46ba-4c76-8ed8-b76204f1a837",
      "object": "fraud_warning",
      "livemode": false,
      "charge_id": "ch_test_example",
      "payment_intent_id": null,
      "fraud_type": "unauthorized_use_of_card",
      "amount": 2500,
      "currency": "usd",
      "status": "open",
      "actionable": true,
      "created": 1791878400,
      "updated": 1791878400,
      "resolved": null
    }
  }
}
```

1. Enable the endpoint in the correct mode (`test` or `live`) and select both event types, or `*`.
2. Verify `Southbill-Signature` on the **raw request body** before trusting the payload. It contains `t=<Unix seconds>,v1=<HMAC-SHA256>` over `t.rawBody`. During secret rotation there can be multiple `v1` values; accept a valid matching signature and enforce a timestamp tolerance, such as 300 seconds.
3. Persist the event ID and queue work durably, then return a `2xx` response promptly. The delivery timeout is 15 seconds.
4. Deduplicate by `event.id`, not warning ID: the same warning can legitimately produce multiple lifecycle events. Do not assume delivery order; retrieve current state before irreversible actions.

Failed deliveries are retried with backoff, up to seven attempts. Unchanged issuer replays normally do not create a lifecycle event, but this is **not an exactly-once delivery guarantee**. Build duplicate-safe handlers and reconcile periodically through the API.

See [Webhook endpoints](/docs/api/webhook-endpoints), [Verify signatures](/docs/webhooks/signature-verification), and [Events & replay](/docs/api/events).

## Test a warning

`POST /v1/test_helpers/fraud_warnings` requires a **test secret key** with `fraud_warnings:read`.

```bash
curl -X POST 'https://api.southbill.com/v1/test_helpers/fraud_warnings' \
  -H 'Authorization: Bearer YOUR_TEST_SECRET_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"amount":2500,"currency":"usd","fraud_type":"unauthorized_use_of_card"}'
```

Returns `201` with a synthetic warning (`status: "open"`, `actionable: true`, `livemode: false`) and emits `fraud_warning.created` for matching test endpoints.

| Body field | Constraints / default |
| --- | --- |
| `amount` | Optional integer, 1–100,000,000 minor units. Default 2500. |
| `currency` | Optional three-letter code. Default `usd`. |
| `fraud_type` | Optional category from the list above. Default `unauthorized_use_of_card`. |
| `charge_id` | Optional test label; a synthetic charge ID is generated if omitted. Supplying an ID does not validate ownership or create a real charge. |

Test warnings do not appear in the merchant's live Fraud warnings dashboard and do not represent real money. Every helper call creates a new warning; do not assume repeated calls are idempotent. The helper has no status-update simulation operation: it tests creation, retrieval and webhook delivery, not a real refund or a complete issuer lifecycle.

## Errors

| HTTP status | Meaning |
| --- | --- |
| `400` | Invalid filter/body, or `livemode_forbidden` when a live key calls the test helper. |
| `401` | Missing or invalid secret API key. |
| `403` | Missing scope or an account restriction. |
| `404` | `resource_missing`: warning unavailable to this merchant and environment. |
| `405` | Unsupported method. |
| `429` | API rate limit exceeded. |
| `500` | API processing failure. |
| `503` | `sandbox_unavailable`: test mode is unavailable in the current environment. |

See [Error reference](/docs/api/errors). Log event/resource IDs for troubleshooting, not secret keys or sensitive payment information.

## Developer-app webhooks

Installed developer apps can receive `fraud_warning.created` and `fraud_warning.updated` only when the merchant has granted `fraud_warnings:read` and the app is subscribed to the events. App webhooks use the **App webhook envelope**, not the Merchant envelope shown above. This does not make the Merchant API routes accessible with an App OAuth token. See [App webhook event catalog](/docs/dev/dev-webhooks/events) and [App signatures & retries](/docs/dev/dev-webhooks/signatures).

