Skip to main content

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.

This guide walks you through everything you need to do to handle Stripe Radar fraud reviews through Flex — from subscribing to webhooks, to building an internal review queue, to testing the integration end-to-end. By the end of this guide, you will be able to:
  • React in real time when Radar opens a review on one of your charges.
  • Take the right action when a review is closed (approved, refunded, fraud, disputed).
  • Build a “Reviews queue” view for your fraud-ops team using the Flex REST API.
  • Join reviews back to your own order IDs without maintaining an extra mapping.
  • Verify the entire flow against test mode before shipping to production.
What is a review? A review is a Stripe Radar fraud-review record surfaced through Flex. When Radar flags one of your charges — either via a Radar rule or a manual review action in Stripe — Flex persists the review and notifies you via a webhook event and via a queryable record on the Flex REST API. A review is open while the decision is pending, and closed once it has been resolved (approved, refunded, marked as fraud, disputed, or redacted).

Before you begin

Make sure you have:
  • A Flex API key with the reviews:read scope. Add it to an existing key in the Flex dashboard, or include it when minting a new key.
  • A webhook endpoint registered with Flex that can receive review.opened and review.closed events.
  • (Recommended) client_reference_id set on your POST /v1/checkout/sessions calls — this is the single highest-leverage change you can make for joining reviews back to your internal order IDs.
Reviews are read-only in Flex. Approving a review must be done in the Stripe dashboard — there is no POST /v1/reviews/:id/approve endpoint.

How the flow works

Before wiring anything up, here is the lifecycle you are integrating against:
1

Stripe Radar opens a review

Triggered by a Radar rule firing, or by a teammate opening a manual review in the Stripe dashboard.
2

Stripe fires `review.opened`

The event flows to Flex via Stripe’s webhook pipeline.
3

Flex resolves the charge and persists the review

Flex assigns a frv_… ID and stores the record.
4

You receive the event in two places

A review.opened webhook is forwarded to your endpoint, and GET /v1/reviews immediately returns the new record.
5

The review is resolved in Stripe

Someone refunds, approves, accepts the dispute, or redacts the review.
6

Stripe fires `review.closed`

Flex updates the record: open=false and closed_reason is set.
7

You receive the close

A review.closed webhook is forwarded, and GET /v1/reviews/:id reflects the new state.

Step 1 — Subscribe to the webhook events

In your Flex webhook configuration, subscribe to:
  • review.opened
  • review.closed
Watch the payload shape. The review fields are nested under object.review, not flattened directly under object. Your handler should read object.review.review_id, object.review.open, etc. The wrapper key (review) corresponds to the event_type family.
A review.opened payload looks like this:
review.opened
{
  "event_id": "fevt_01jx3q8kt1b9a7n2c4d5e6f7g8",
  "event_type": "review.opened",
  "event_dt": 1770000000,
  "object": {
    "review": {
      "review_id": "frv_01jx3q8kt1b9a7n2c4d5e6f7g8",
      "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
      "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
      "partner_id": "facct_b8b56d3f3ba09d6c5dc61f7916e03ae3",
      "reason": "rule",
      "opened_reason": "rule",
      "open": true,
      "billing_zip": "94103",
      "ip_address": "203.0.113.42",
      "client_reference_id": "order_12345",
      "test_mode": false,
      "created_at": "2026-04-29T10:20:00Z"
    }
  }
}
A review.closed payload adds closed_reason and flips open to false:
review.closed
{
  "event_id": "fevt_01jx3q9ab1c2d3e4f5g6h7j8k9",
  "event_type": "review.closed",
  "event_dt": 1770003600,
  "object": {
    "review": {
      "review_id": "frv_01jx3q8kt1b9a7n2c4d5e6f7g8",
      "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
      "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
      "partner_id": "facct_b8b56d3f3ba09d6c5dc61f7916e03ae3",
      "reason": "refunded_as_fraud",
      "opened_reason": "rule",
      "closed_reason": "refunded_as_fraud",
      "open": false,
      "billing_zip": "94103",
      "ip_address": "203.0.113.42",
      "client_reference_id": "order_12345",
      "test_mode": false,
      "created_at": "2026-04-29T10:20:00Z"
    }
  }
}

Verify the webhook signature

Flex signs every outbound webhook so you can confirm it actually came from Flex. The canonical delivery path uses Svix and includes svix-id, svix-timestamp, and svix-signature headers — see the Verifying Webhooks guide for the full verification flow and SDK examples.
Legacy webhook path — Partners on Flex’s legacy webhook path will see flex-signature, flex-event-id, and flex-timestamp headers instead. This path is deprecated and being removed once all partners are migrated to Svix; new integrations should target the Svix flow.

Make your handler idempotent

Stripe and Flex both retry webhooks. Your handler should be safe to invoke twice with the same payload.
ScenarioWhat to expect
Same event delivered twiceBe idempotent on event_id.
review.opened retried after review.closed already processedClosed state is preserved. open stays false, closed_reason retained.
Late-arriving event with stale datapartner_id, charge_id, payment_intent_id, and created_at are write-once and not overwritten.

Step 2 — Handle review.opened

When a review opens, the charge is still captured but Stripe (or your own ops team) has flagged it for human review. You usually want to pause downstream side effects until the review resolves. A typical handler:
  1. Look up the order in your system using object.review.client_reference_id (or fall back to payment_intent_id).
  2. If the order has not yet shipped or been provisioned, hold fulfilment until the review closes.
  3. Optionally surface the review in your internal admin tool so an ops person can investigate.
If you set client_reference_id on the underlying checkout session, Flex hydrates it onto the review automatically — you do not need a separate payment_intent_id → order lookup.

Step 3 — Handle review.closed

When you receive a review.closed, branch on closed_reason and take the matching action:
closed_reasonWhat it meansWhat to do
approvedThe charge was reviewed and approved. Funds remain captured.Release any held fulfilment and continue normally.
refundedThe charge was refunded as part of closing the review.Cancel the order in your system if you have not already.
refunded_as_fraudRefunded and explicitly marked as fraud. Feeds back into Radar’s ML model.Cancel the order, and consider flagging the customer / IP / device.
disputedThe cardholder disputed the charge before the review was manually closed.Switch to the dispute response workflow — see the Disputes API guide.
redactedRare — typically a data-removal request.No customer-facing action; remove related records if relevant.
Closed reviews stay closed. If a review.opened retry arrives after Flex has already processed review.closed, the row stays closed. open does not flip back to true, and closed_reason is preserved. Treat your own internal state the same way.

Step 4 — Build a “Reviews queue” view

Webhooks tell you what just happened, but for an ops dashboard you also want to ask “what is open right now?” Use the REST API for this.

Authenticate

RequirementDetail
Auth typeBearer token (Flex API key)
Required scopereviews:read

List open reviews

Poll for everything currently in review:
curl -X GET "https://api.withflex.com/v1/reviews?open=true&limit=100" \
  -H "Authorization: Bearer fsk_live_..."
Render the response so your fraud-ops team can:
  • See which orders are currently in review.
  • Click through to the corresponding charge / order in your admin tools.
  • Identify reviews that have been open unusually long.
Pair this with GET /v1/reviews?open=false filtered by date for a historical audit log.

Paginate through the results

Reviews are returned newest-first as a flat array — there is no has_more field.
# First page
curl "https://api.withflex.com/v1/reviews?open=true&limit=100" \
  -H "Authorization: Bearer fsk_live_..."

# Next page — pass the review_id of the LAST item from the previous response
curl "https://api.withflex.com/v1/reviews?open=true&limit=100&starting_after=frv_01jx..." \
  -H "Authorization: Bearer fsk_live_..."
If you receive fewer than limit items, you have reached the end. Use ending_before for backward pagination.

Available filters

ParameterTypeUse it to…
openbooleanReturn only open (true) or only closed (false) reviews. Omit for both.
charge_idstringFilter to reviews on a specific Flex charge.
payment_intent_idstringFilter to reviews on a specific Flex payment intent.
client_reference_idstringFilter to reviews tied to one of your order IDs.
starting_afterstringForward pagination cursor (a review_id).
ending_beforestringBackward pagination cursor.
limitintegerPage size, between 1 and 100. Default 20.

Audit a single review

When you need to look up one review by Flex ID:
curl -X GET "https://api.withflex.com/v1/reviews/frv_01jx3q8kt1b9a7n2c4d5e6f7g8" \
  -H "Authorization: Bearer fsk_live_..."
Response
{
  "review": {
    "review_id": "frv_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "charge_id": "fch_01jx...",
    "payment_intent_id": "fpi_01jx...",
    "partner_id": "facct_...",
    "reason": "refunded_as_fraud",
    "opened_reason": "rule",
    "closed_reason": "refunded_as_fraud",
    "open": false,
    "billing_zip": "94103",
    "ip_address": "203.0.113.42",
    "client_reference_id": "order_12345",
    "test_mode": false,
    "created_at": "2026-04-29T10:20:00Z"
  }
}

Handle errors

StatusWhen
401 UnauthorizedAPI key is missing or lacks the reviews:read scope.
404 Not FoundReview does not exist, or belongs to another partner, or test_mode does not match the API key being used. (Cross-partner existence is never leaked.)

Step 5 — Join reviews to your order IDs

The cleanest way to tie a review back to your own system is via client_reference_id. Set it on the checkout session when you create it:
curl -X POST "https://api.withflex.com/v1/checkout/sessions" \
  -H "Authorization: Bearer fsk_live_..." \
  -d "client_reference_id=order_12345" \
  ...
Flex hydrates that value onto every review for the resulting charge. To pull every review (open or closed) tied to a given order:
curl -X GET "https://api.withflex.com/v1/reviews?client_reference_id=order_12345" \
  -H "Authorization: Bearer fsk_live_..."
If you already set client_reference_id on every checkout session (most integrations do), you are done — no extra mapping table required. If you do not, adding it is the single highest-leverage change for working with reviews.

Step 6 — Test the integration end-to-end

Stripe does not open reviews on standard test card payments. Use one of the methods below to generate a review event end-to-end.

Method A — stripe trigger

If you have the Stripe CLI authenticated against your Stripe test account, fire a synthetic event:
stripe trigger review.opened
This is the same path Stripe production webhooks take, so it exercises the full Flex flow: signature verification → charge resolution → DB write → outbound review.opened to your endpoint. Then verify the review landed in Flex:
curl -s "https://api.withflex.com/v1/reviews?open=true&limit=5" \
  -H "Authorization: Bearer fsk_test_..." | jq
You should see the new review with a fresh frv_… ID.

Method B — Synthetic webhook (local development)

For local development against localhost, send a signed synthetic Stripe webhook directly to your Flex server. The event differences for Reviews:
FieldValue
type"review.opened" (or "review.closed")
data.objectA Stripe review object (id starts with prv_, includes charge, reason, opened_reason, open, etc.)
Example data.object for a review.opened:
{
  "id": "prv_synthetic_001",
  "object": "review",
  "billing_zip": "94103",
  "charge": "ch_...",
  "closed_reason": null,
  "created": 1770000000,
  "ip_address": "203.0.113.42",
  "livemode": false,
  "open": true,
  "opened_reason": "rule",
  "payment_intent": "pi_...",
  "reason": "rule"
}
For review.closed, set open: false, reason: "refunded_as_fraud", and closed_reason: "refunded_as_fraud". Keep the same id so Flex upserts onto the existing record rather than creating a new one.

Verification checklist

Walk through these scenarios before considering your integration done:
ScenarioExpected outcome
review.opened receivedNew frv_… review in DB; webhook forwarded with object.review.open: true
client_reference_id set on checkout sessionAppears in object.review.client_reference_id on the forwarded webhook
review.closed receivedSame frv_… review updated; object.review.open: false, closed_reason set; webhook forwarded
Out-of-order: review.opened retried after review.closedDB row stays closed; open does not flip back to true
Duplicate review.opened (same prv_ ID)No duplicate row — idempotent upsert
GET /v1/reviews?open=true after review.closedClosed review is no longer in the response
GET /v1/reviews/frv_… for another partner’s review404 Not Found
API key without reviews:read scope401 Unauthorized on /v1/reviews
Test-mode key reading a live review (or vice-versa)404 Not Found

Reference

The review object

{
  "review_id": "frv_01jx3q8kt1b9a7n2c4d5e6f7g8",
  "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
  "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
  "partner_id": "facct_b8b56d3f3ba09d6c5dc61f7916e03ae3",
  "reason": "rule",
  "opened_reason": "rule",
  "open": true,
  "billing_zip": "94103",
  "ip_address": "203.0.113.42",
  "client_reference_id": "order_12345",
  "test_mode": false,
  "created_at": "2026-04-29T10:20:00Z"
}
review_id
string
Flex review ID (frv_ prefix). Use this in REST calls.
charge_id
string | null
Flex charge ID (fch_) for the reviewed transaction.
payment_intent_id
string | null
Flex payment intent ID (fpi_) linked to the charge.
partner_id
string
Flex partner that owns the review.
reason
string
Current high-level reason. Tracks opened_reason while open; tracks closed_reason once closed.
opened_reason
string
Why the review was opened. One of rule, manual.
closed_reason
string | omitted
Why the review was closed. Present only when open: false. One of approved, disputed, redacted, refunded, refunded_as_fraud.
open
boolean
true while the review is pending. false once resolved.
billing_zip
string | null
Billing ZIP captured at payment, when present.
ip_address
string | null
IP address captured at payment, when present.
client_reference_id
string | null
The client_reference_id you set on the underlying checkout session.
test_mode
boolean
true for test-mode reviews.
created_at
string
ISO 8601 timestamp when Flex first persisted the review.

Reason codes

opened_reason
ValueMeaning
ruleA Radar rule fired and opened the review automatically.
manualA teammate opened the review manually from the Stripe dashboard.
closed_reason (set once open: false)
ValueMeaning
approvedThe charge was reviewed and approved. Funds remain captured.
refundedThe charge was refunded as part of closing the review.
refunded_as_fraudRefunded and explicitly marked as fraud — feeds back into Radar’s ML model.
disputedThe cardholder disputed the charge (chargeback) before the review was manually closed.
redactedThe review was redacted (rare; e.g. data-removal request).

Reviews vs. Early Fraud Warnings

A single charge may have both a review and an EFW. They are independent signals — subscribe to both for full visibility.
ReviewEarly Fraud Warning
TriggerRadar rule fires, or a teammate opens a manual review in StripeCard issuer files a fraud report with the network
State modelopenclosed (with closed_reason)actionable: trueactionable: false
Implies a chargeback?No — Stripe-internal flag for human reviewNo, but is a stronger signal one may follow
REST APIGET /v1/reviews, GET /v1/reviews/:idNone (webhook only)
Webhook eventsreview.opened, review.closedradar.early_fraud_warning.created / .updated
Required auth scopereviews:readn/a

What is not forwarded

  • Stripe reviews that have no associated charge are silently dropped (no DB write, no webhook). This is rare but possible during certain failure modes.
  • Reviews on charges belonging to partners not onboarded to Flex are not forwarded.