Risk Controls
Flint evaluates each payment attempt in two stages. Pre-authorization checks use facts available before the card is sent for authorization, such as amount, card fingerprint, email, IP address, saved-method state, and payment flow. Post-authorization checks add the normalized authorization outcome, risk level, and a risk score when scoring is available.
Each confirmation of a payment creates a payment attempt, and the decision and its input snapshot are attached to that attempt. Rule edits and list edits made after an attempt begins cannot change that attempt's result. A later retry creates a new attempt and evaluates the then-current controls.
Limited availability
Risk scoring is not enabled in every environment. risk.score is nullable, and the risk_score rule attribute reports available: false when scoring is unavailable. Build rules around risk_level or pre-authorization attributes unless GET /v1/risk-rules/attributes confirms that risk_score is available.
Start with the Attribute Registry#
Do not hardcode an assumed attribute catalog. Retrieve the registry before building or validating rules:
curl https://api.withflintpay.com/v1/risk-rules/attributes \
-H "Authorization: Bearer YOUR_API_KEY"
Each attribute includes its type, allowed operators, nullability, earliest evaluation stage, environment availability, and enum values where applicable. If a nullable attribute is missing on an attempt, comparisons against it do not match; use an explicit is_missing comparison to target missing values.
Predicate Grammar#
A predicate is either a comparison or a boolean group. Comparison fields are strict: a predicate with unknown fields is rejected.
{
"all": [
{"attribute": "amount_money", "operator": "gte", "amount_money": {"amount": 50000, "currency": "USD"}},
{
"any": [
{"attribute": "email", "operator": "in_list", "list_alias": "flagged_emails"},
{"attribute": "payment_flow", "operator": "in", "values": ["checkout", "payment_link"]}
]
}
]
}
Use all, any, and not to compose predicates. Scalar comparisons use eq, neq, ordered operators where supported, in, in_list, and is_missing. Money comparisons use amount_money; in uses values; in_list uses an immutable list alias.
Rules support four actions:
| Action | Result |
|---|---|
allow | Allows the attempt through the current decision stage. |
block | Stops the attempt before authorization and returns a generic payment failure. |
review | Opens a review. Automatic capture is held, and manual capture is gated. |
require_3ds | Requires buyer authentication for an on-session attempt. It does not apply to off-session attempts. |
Rules that depend on post-authorization attributes cannot use block or require_3ds. Validate before creating or updating a rule:
curl -X POST https://api.withflintpay.com/v1/risk-rules/validate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"action": "review",
"predicate": {"attribute": "risk_level", "operator": "in", "values": ["elevated", "highest"]}
}'
POST /v1/risk-rules/evaluate uses supplied attributes to explain whether a rule would match. Both validation and evaluation are side-effect free.
Lists#
Lists give rules stable sets of values without embedding those values in every rule. A list has an immutable alias used by predicates and an item_type such as card_fingerprint, card_bin, email, email_domain, ip_address, country, customer_id, string, or case_sensitive_string. Aliases are unique within an environment. Creating another list with the same alias returns RISK_LIST_ALIAS_ALREADY_EXISTS.
curl -X POST https://api.withflintpay.com/v1/risk-lists \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: create-flagged-emails-001" \
-d '{"name":"Flagged emails","alias":"flagged_emails","item_type":"email"}'
Add one value with value or a batch with values, but never both. Batch results report each item as created or already present. List mutations and rule mutations accept Idempotency-Key.
Reviews#
Subscribe to review.opened or list GET /v1/reviews?status=open. Reviews contain a payment snapshot, payment flow, risk level, nullable score, matched rule, network information when available, and authorization expiry for manual-capture payments.
Approve with:
curl -X POST https://api.withflintpay.com/v1/reviews/rev_1kmn0aExample/approve \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: approve-review-001"
Approval makes a manual-capture payment eligible for a later capture. It never captures the payment. Your fulfillment system must still call capture before authorization_expires_at.
Decline with an optional request to add the attempt's observed card, email, and IP signals to default block lists:
curl -X POST https://api.withflintpay.com/v1/reviews/rev_1kmn0aExample/decline \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: decline-review-001" \
-d '{"add_to_block_list": true}'
Declining an uncaptured authorization cancels it. Declining a captured payment creates a refund for the remaining refundable amount. The decline request can return 202 Accepted while that work finishes; poll the review per Retry-After until it closes, or consume review.closed.
A review closes when its action finishes, when the payment is canceled, refunded in full, or disputed, or when its authorization expires. Expiry closes the review and releases the hold; it does not approve the payment or unlock capture.
Fraud Warnings#
An issuer-reported fraud warning is separate from Flint's earlier risk decision. Read it from fraud_warning.created or GET /v1/fraud-warnings?actionable=true. A warning contains a payment snapshot and may later gain a dispute_id. fraud_warning.updated fires when actionability or dispute linkage changes.
Treat an actionable warning as an operational signal to inspect fulfillment, refund eligibility, and any linked dispute. A full refund or a dispute makes the warning unactionable. See Disputes.
Payment Fields and Errors#
Payment intents expose an immutable payment_flow, the latest normalized payment failure, and a risk object. risk.score may be null; do not convert a missing score to zero. Use the risk level and outcome fields that are present.
Handle these codes explicitly:
| Code | Handling |
|---|---|
PAYMENT_BLOCKED | Do not disclose fraud or rule details to the buyer. Ask for a different payment method. |
PAYMENT_REVIEW_OPEN | Do not capture. Retrieve or process the linked review. |
PAYMENT_OPERATION_IN_PROGRESS | A capture, review resolution, expiry, or cancellation already owns the payment. Re-read state and retry only if remediation permits it. |
RISK_EVALUATION_UNAVAILABLE | Retry the same confirmation with the same idempotency key after a short delay. |
Access and Webhooks#
Use risk.read for reviews, fraud warnings, rules, and lists. Use risk.reviews.write for review decisions and risk.controls.write for rule and list mutations. The validate and evaluate endpoints also require risk.controls.write, even though they are side-effect free.
Risk webhook events are review.opened, review.closed, fraud_warning.created, and fraud_warning.updated. Webhook payloads are snapshots. Re-fetch a review or warning before acting on data that may have changed, and deduplicate deliveries by webhook_event_id.
