Refund a held payment and block the customer
The risk.reviews.reject operation: POST /api/v1/risk/reviews/{id}/reject in the REST API and the risk_reviews_reject tool in the MCP server.
/api/v1/risk/reviews/{id}/rejectRejects a payment the risk rules held, in one transaction:
- Refund, exactly as
refunds.createwith typefullmakes it: a credit note at the original rates, the ledger reversal and the processor refund (a credit pack only for its unused credits; the hold kept the wallet frozen, so normally all of them). Support refunds up to the brand's limit, finance without one.refund: falseskips it (for a payment already refunded). - Block: the customer's email and the card's fingerprint go on the brand's blocklist (
blockchooses;ipNetwork: trueadds the IP network, which can block neighbours). An entry already listed is reused. - The customer's risk becomes
blocked; their frozen wallets stay frozen.
risk.review.rejected goes to your webhook endpoints. Financial tier: approval policies apply to agents and
apps; dry-run it first to see the refund.
At a glance
- REST:
POST /api/v1/risk/reviews/{id}/reject - MCP tool:
risk_reviews_reject - Operation ID:
risk.reviews.reject - Scope: Brand
- Permission:
refunds:write - Risk tier:
financial - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyrequired - Modes: Test and live
- Authentication: API key with the
refunds:writescope - Since: M9
Request
Path parameters
| Field | Type | Description |
|---|---|---|
id | rsk_… ID |
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | Yes | |
refund | boolean | No | |
block | object | No | |
block.email | boolean | No | |
block.cardFingerprint | boolean | No | |
block.ipNetwork | boolean | No | |
expectedVersion | integer (at least 1) | No |
Response
200 OK with a JSON object:
| Field | Type | Description |
|---|---|---|
id | rsk_… ID | |
object | "risk_evaluation" | |
livemode | boolean | |
brandId | brd_… ID | |
subject | object | |
subject.type | one of checkout_session, auto_reload | |
subject.checkoutSessionId | cs_… ID or null | |
subject.walletId | wal_… ID or null | |
customerId | cus_… ID or null | |
paymentId | pay_… ID or null | |
stage | one of intent, payment_method, confirm, off_session | |
decision | one of allow, require_3ds, review, block | |
score | integer | |
reasons | array of object | |
reasons[].code | one of 21 values, e.g. customer_blocked, customer_elevated, blocklist_email (all in the OpenAPI document) | |
reasons[].action | one of block, review, require_3ds, score | |
reasons[].points | integer or null | |
reasons[].detail | string | |
signals | object | |
signals.emailDomain | string or null | |
signals.ipNetwork | string or null | |
signals.ipCountry | string or null | |
signals.billingCountry | string or null | |
signals.cardCountry | string or null | |
signals.cardFingerprint | string or null | |
signals.amount | money or null | |
signals.topup | boolean | |
signals.newCustomer | boolean | |
signals.firstPayment | boolean | |
signals.disposableEmail | boolean | |
history | array of object | |
history[].at | date-time | |
history[].stage | one of intent, payment_method, confirm, off_session | |
history[].decision | one of allow, require_3ds, review, block | |
history[].score | integer | |
review | object or null | |
createdAt | date-time | |
updatedAt | date-time |
Examples
Refund and block a stolen card
POST /api/v1/risk/reviews/rsk_01m4ttwd3veh9apbewxheq5xr1/reject
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"reason": "The cardholder doesn't know the customer."
}
Response (200):
{
"id": "rsk_01m4ttwd3veh9apbewxheq5xr1",
"object": "risk_evaluation",
"livemode": true,
"brandId": "brd_01m4ttwd3veh9apbewxheq5xh5",
"subject": {
"type": "checkout_session",
"checkoutSessionId": "cs_01m4ttwd3veh9apbewxheq5xr4",
"walletId": null
},
"customerId": "cus_01m4ttwd3veh9apbewxheq5xr5",
"paymentId": "pay_01m4ttwd3veh9apbewxheq5xr6",
"stage": "confirm",
"decision": "review",
"score": 70,
"reasons": [
{
"code": "first_payment",
"action": "require_3ds",
"points": null,
"detail": "The customer's first payment."
},
{
"code": "new_customer",
"action": "score",
"points": 10,
"detail": "A new customer."
},
{
"code": "country_mismatch_ip_billing",
"action": "score",
"points": 20,
"detail": "IP country DE isn't the billing country HR."
},
{
"code": "country_mismatch_card_billing",
"action": "score",
"points": 25,
"detail": "Card country US isn't the billing country HR."
},
{
"code": "country_mismatch_ip_card",
"action": "score",
"points": 15,
"detail": "IP country DE isn't the card country US."
},
{
"code": "score_review",
"action": "review",
"points": null,
"detail": "Score 70 ≥ 70."
}
],
"signals": {
"emailDomain": "corp.example",
"ipNetwork": "203.0.113.0",
"ipCountry": "DE",
"billingCountry": "HR",
"cardCountry": "US",
"cardFingerprint": "Xo1abC9dEf2GhI3j",
"amount": {
"amount": "90000000",
"currency": "EUR"
},
"topup": true,
"newCustomer": true,
"firstPayment": true,
"disposableEmail": false
},
"history": [
{
"at": "2026-10-20T09:00:00.000Z",
"stage": "intent",
"decision": "require_3ds",
"score": 30
},
{
"at": "2026-10-20T09:01:00.000Z",
"stage": "payment_method",
"decision": "review",
"score": 70
},
{
"at": "2026-10-20T09:01:30.000Z",
"stage": "confirm",
"decision": "review",
"score": 70
}
],
"review": {
"status": "rejected",
"openedAt": "2026-10-20T09:02:00.000Z",
"decidedAt": "2026-10-20T11:30:00.000Z",
"decidedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
"note": "The cardholder doesn't know the customer.",
"refundId": "re_01m4ttwd3veh9apbewxheq5xr8",
"blocklistEntryIds": ["rbl_01m4ttwd3veh9apbewxheq5xr2", "rbl_01m4ttwd3veh9apbewxheq5xr3"],
"frozenWalletIds": ["wal_01m4ttwd3veh9apbewxheq5xr7"]
},
"createdAt": "2026-10-20T09:00:00.000Z",
"updatedAt": "2026-10-20T11:30:00.000Z"
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
resource_not_found | 404 | No risk evaluation with this ID in this brand and mode. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
risk_evaluation_not_held | 400 | The evaluation held no payment for review. | Only payments the risk rules held are in the review queue (risk.reviews.list). Refund other payments with refunds.create. |
risk_review_not_pending | 409 | The review was already approved or rejected. | Reload the review: it was approved or rejected (details.status). A decision is final; refund the payment or unfreeze the wallet by hand if something must change. |
refund_payment_not_refundable | 409 | The payment can't be refunded (disputed, or refunded already): reject with refund: false. | Refund a succeeded card payment that has an invoice (details.status says what the payment is now). A disputed payment is settled through the dispute; an invoice paid by bank transfer or only from the credit balance has no payment to refund. |
refund_nothing_to_refund | 409 | Nothing is left to refund (the credits were used): reject with refund: false. | The invoice is fully credited, its credits are used, or its period is over. Check the invoice's credit notes and the wallet before trying another type. |
refund_limit_exceeded | 403 | The refund is above your role's limit: ask finance. | Ask a member with the finance role (or an API key with refunds:admin) to make it, or refund at most details.limit. The limit is the brand's (brands.policies.supportRefundLimits). |
refund_outside_policy | 403 | Support can't refund these credits under the brand's policy. | Credits are refundable by support within the brand's refund window when none were used; anything else is the finance role's decision (refunds:admin). |
version_conflict | 409 | The review changed since expectedVersion, or at the same moment. | Fetch the object again and retry the change. |
permission_denied | 403 | You can't refund this brand's payments. | Ask an organization owner or admin for a role or scope that allows this action. |
It can also return these common errors: validation_failed, api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, scope_required, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.