Trust & safety
Peer-to-peer fraud is usually social, not stolen-card. Someone is on the phone walking your user through a payment. This page covers how both sides are identified, the signals that run before money moves, and how to work a payment that looks wrong.
A new number, a name mismatch, or a device that looks wrong can all be caught in the payment flow, while your user can still stop. You still decide whether to place the hold. If something slips through, you work it against that payment’s record.
Requests look different: the other person is paying you. The card-side risk is issuer decline, stolen cards, and friendly fraud. Phone and passkey checks still run. Most of the scoring below is about payments, because that’s where your deposit is leaving.
Your user #
They don’t get a Moov Money password. Your app is already the session. Your backend mints a short-lived token for the signed-in user and hands it to the SDK. The token is opaque on device, scoped to that user, and expires in about an hour. Mint another when it expires. See sender authentication.
If you disable or deny the participant, token minting stops. That’s the off switch for Moov Money without closing the deposit account.
The other person #
A payment is addressed to a phone number. Claiming requires proving possession of that number:
- A one-time code, sent by Moov, or
- A passkey bound to their device, if they enrolled after a previous payment.
There is no password to phish, reuse, or be talked out of on a call. Passkeys are offered at the end of a successful hosted-page visit, not as a prerequisite the first time.
The link #
Claim URLs look like https://moov.money/c/{code} (or your custom
host). Pay links look like https://moov.money/r/{code}. The code is
the secret. Moov serves the page; the hosted UI never talks to your
core. A ping-scoped token on first load is upgraded only after OTP or
passkey succeeds.
Moov will never ask anyone for that one-time code, or to send money back to “release” a payment. Consumer copy for that lives in the help center; your staff should use the same line.
Card data #
Manual card entry and Apple Pay / Google Pay happen on Moov’s hosted page, not in your app. You do not see a PAN. You should not try to. That keeps your app out of PCI scope for the counterparty’s card.
Cards are checked with the issuer when they’re added, name included. A mismatch is caught before funds move. Only eligible US Visa and Mastercard debit (and prepaid that can receive or pay) work; credit cards can’t receive a payment.
Wallet payments on claim are disbursement (push-to-apple-pay / push-to-google-pay). Paying a request from a wallet is the inbound equivalent, not a card-on-file you store.
What runs on every payment #
Before your ledger ever sees a hold, and then again while your user is still in the flow:
| Signal | What it catches |
|---|---|
| On a call | Pressure scams. If they’re on a call while they send, the SDK says so and tells them to hang up first. Shipped in the iOS SDK as a warning, not a hard block. |
| Device intelligence | Rooted or jailbroken phones, emulators, automation. The hosted page has its own view of the other person’s device. |
| Contacts | A mistyped digit; a number that isn’t in their contacts. |
| Name match | The recipient is registered under a different name than the one in the sender’s contacts. |
| Issuer name check | The name on the card doesn’t match, before anything is pushed. |
| Signup check | A new participant is pending until this passes; denied never gets a sender token. |
| Carrier / SIM | A number that was just ported or moved to a new SIM. Joining the stack; not a second vendor. |
Apple Trust Insights (iOS 27) feed the same stack when they ship. You don’t integrate them separately.
None of this is a second product. It arrives with the SDK and the hosted page.
The score you receive #
On authorize, the
request includes fraudScore from 0.0 to 1.0. Higher is riskier.
You may:
- Approve anyway (you still place a hold; Moov may also have put the payment in manual review).
- Deny, with a
reason(insufficient funds, your own rules, or a step-up you require above a threshold you choose). - Return a retryable HTTP status if your ledger is down. Don’t invent a third outcome to mean your ledger crashed.
A risk denial from Moov (the fraud check refuses the payment) is
different from you denying the hold. Neither is an error the sender
sees as a failed request. The payout is stored as denied with a
reason (the fraud check refusing, or your ledger declining the
authorize), and the SDK surfaces that outcome to your app. Your
user-facing copy should not treat the two as a generic “try again.”
Interventions #
The SDK does not silently score and proceed. It tells the user what’s wrong at the moment it matters:
- You’re on a call. Hang up before you send.
- This number isn’t in your contacts.
- The name doesn’t match the account.
That’s the difference versus a transfer that posts and then generates a case. The hold is still yours to refuse. The warning is so they can refuse it first.
Review holds #
When the check isn’t a clean allow or deny, the payment is placed in a manual review hold. The other person cannot claim until you approve, or until the hold lapses. Rejecting the last pending review cancels a cancellable payment and releases the hold.
How to list and resolve reviews is in operations. Work the case on that payment. The reason, risk-assessment id, recipient, amount, and status are on the review. You should not need to search core-ledger memos to remember why a $400 send looked off.
Requests and inbound cards #
On a request, the other person pays with a debit card or wallet. Moov
still verifies the phone (or passkey). Stolen-card and never-received
claims are issuer and network disputes, not a case of your user being
scammed into sending. Inbound card risk is why credit retries if you
are unreachable, and why refused on
credit account
is terminal.
Your exposure is inbound: a payer who disputes the card charge after
you’ve credited your user. Keep the request ID, the creditReference,
timestamps, and the confirmation the hosted page showed. Who absorbs
an uncollectable shortfall is part of your operating model with Moov.
Moov will not keep a negative balance on the participant.
Disputes #
Treat these as two different workflows. Don’t run them through one unlabeled inbox.
Authorized payment, user regrets it (scam / APP). The payment was
authorized against their deposit. Card-network chargeback rules for
push-to-card are not the same as a stolen-card pull. Your team works
this as an authorized-push case: warnings shown, whether they were on
a call, contact match, whether they confirmed after a warning, whether
you denied or reviewed. The review record and the authorize
fraudScore are the evidence. Reimbursement policy is yours. The
review queue and your own case tools are the workflow.
Card dispute on a paid request. The other person’s issuer, on a pull into your wallet. Network authorization code, descriptor, and timestamps are the trace. Disputes surface in your Moov Dashboard, and that’s where you work them. A user who “doesn’t recognize” a descriptor you made cryptic is a configuration problem; see statement descriptor.
Encrypted ledger calls #
Authorize, capture, release, and credit bodies are encrypted JWEs
(A256KW / A256GCM). Amounts, identities, and hold references never
travel in the outer JSON. The wrapping key is a 32-byte secret you
issue once and store in your secrets manager. See
encryption.
Moov is the client; you host the endpoints. There is no “Moov logs into
your core.” Each call is a signed, short-lived request (exp is five
minutes) you decrypt, honor, and answer.
What you set versus what Moov runs #
| Yours | Moov’s |
|---|---|
| Who is signed into your app, and whether they may use Moov Money | Hosted-page identity, OTP, and passkeys |
| Limit groups and who is in them | Device, call, and contact signals |
| Approve / deny / step-up on authorize | The score those signals produce |
| Review queue decisions | Placing a payment in review |
| Reimbursement policy | PCI and card capture on the hosted page |
| Participant disable / deny | Signup fraud check that sets pending → active / denied |
Raising limits and skipping the review queue makes the program faster and increases the loss tail. Keeping limits tight and working the queue is how the product is designed to run.