Bans & Fraud Signals

The admin operations that take a user out of circulation, the automated bans, and the fraud signals consulted during profile changes. Status effects are summarised in User Status.

Ban

POST /{user_id}/user/ban (BanUser); body optional (source, description).

  1. An unknown user is 404; an already BANNED user is a 200 no-op.

  2. The current membership, when one exists, is re-written with status BANNED, event BANNED and the source from the body (falling back to the caller-derived source).

  3. The Subscription Service and the Float Service ban the user.

  4. The user becomes BANNED.

  5. With a description, a note is added to the user in the Admin API.

  6. The Auth0 account is blocked and a full-scope cleanup message (raw user_id) is queued for the janitor.

  7. 200 with an empty body; any failure after step 1 is 500.

Clear

POST /{user_id}/user/clear (ClearUser) lifts a ban or investigation: a BANNED or INVESTIGATE user becomes PAUSED, the Auth0 account is unblocked, and the membership (when one exists) is re-written with status PAUSED, event MX_UNBLOCK and a cleared reactivation_date so the cooldown does not apply. 202 with an empty body. The user must then reactivate.

Flag

POST /{user_id}/user/flag (FlagUser) marks the user INVESTIGATE and re-writes the membership (when one exists) with status and event INVESTIGATE. 200 with an empty body. An INVESTIGATE user cannot reactivate and cannot be reached by the collect flow.

Chargeback Ban

The payment-worker Lambda consumes {env}-payments, filtered by Terraform to FLOAT_DEBIT_CHARGED_BACK and SUBSCRIPTION_CHARGED_BACK events. For a payment whose status is CHARGED_BACK it calls BanUser through the user-service client with source System and description user banned from user-service for returned payment — only when ENABLE_CHARGEBACK_BAN is true (it is false in both environments, so chargebacks are currently logged and skipped). A processing error reports the record as a batch item failure for retry.

POST /users-by-related-accounts and POST /users-by-related-accounts/count find other users sharing identifiers with the request (user_id, phone_number, email, install_id):

found_by Match

phone_number

Users with the same phone number (DynamoDB and user-phone-numbers)

install_id

Users with the same Firebase install id; empty or null values are not searched

email

Users with the same email

account_hash

Users the Payments Service reports as sharing a bank account hash; only when CHECK_RELATED_ACCOUNT_HASH is true (prod)

Source: pkg/api/users.go, pkg/api/models.go

The requesting user is excluded from the results. The count endpoint requires user_id (400 without it) and de-duplicates users across match types into unique_total; when RELATED_USERS_BAN_COUNT is greater than 0 (4 in prod) and unique_total exceeds it, the user is banned with source System and description Related users count exceeded ban threshold, and user.related_accounts.banned is emitted. In the test environment the list is capped at 10,000 rows.

Castle

Every self-service profile update is scored by Castle with a user-update-attempted request built from the raw HTTP request (client headers and address) and the user’s current and requested profile. With ENABLE_CASTLE_BLOCKING (true in both environments) a Castle error is 500, and a missing verdict or a deny policy action is 401; without the flag the verdict is logged only. Admin updates are not scored.

Pause Fraud Signal

A pause request from a user whose pause tag is SUB_PENDING_RESUME is allowed but emits user.membership.pauseFraud, marking a user who pauses again right before a collection — see Membership Pause.