Every velocity and reputation decision in Verifence rests on one input: the end-user's IP. Get it wrong and you don't just make one bad call — you bucket every one of a customer's users under a single address and poison the reputation signal that all customers share.
So Verifence never guesses it. The trust model, set by MODE, decides where the IP is allowed to come from, and it refuses to fall back to a source it doesn't trust.
Hosted mode: the body is authoritative
In hosted mode — the default — the request comes from the customer's own authenticated backend, calling /v1/check right before it sends an OTP. The end-user's IP arrives in the client_ip field of the request body, and that field is authoritative.
The transport peer here is the customer's server, not the end user. So it is never used as a fallback. Not when client_ip is missing, not when it looks malformed — never.
The reason is the failure mode. If Verifence fell back to the transport IP, every request from that customer would carry the same address: their server's. All their users would collapse into one velocity bucket, and that one address would get written into the shared reputation pool as if it were a real network. One customer's misconfiguration would degrade signal quality for everyone.
Missing client_ip is a hard 400
Because a wrong IP is worse than no IP, a missing client_ip in hosted mode is a hard 400 — client_ip_required. It is deliberately not a quiet, degraded 200 that runs the account counters alone.
A 400 is loud. It surfaces the integration bug immediately, at the caller, while it's cheap to fix. A silent 200 would let a broken integration run for weeks, quietly keying everything to nothing and eroding the reputation data in the background. Fail visibly, not silently.
Edge mode: trust the transport, but only from proxies
In edge mode, Verifence sits directly in front of end users. There's no authenticated backend in the middle, so the IP comes from the transport connection itself.
But transport IPs are only trusted when the peer is a configured trusted-proxy CIDR. Behind a load balancer or CDN, the real client address lives in a forwarded header, and that header is only believable if the connection actually came from your proxy. An address presented by an untrusted peer is ignored, not honored.
Why "worse than none"
A missing IP has a defined, safe behavior: address-keyed limiting is skipped and the account counters carry the request on their own. It's degraded, but honest — no counter is corrupted.
A wrong IP is corrupting. It merges users who should be separate and writes false data into a pool other customers depend on. One is a gap. The other is contamination.
Verifence would rather have the gap. That's why it takes the IP only from a source the mode says it can trust, and 400s rather than guess.