# SMS, OTP and paid endpoints

> Stop abusive requests before they become a bill.

Engineering safeguards · /otp/ · 2026-11-05

Attackers can automate public sends while the application looks healthy. The damage is the bill, often discovered after the traffic has stopped.

## Ask the agent to review paid calls

**Prompt**

```text
Review [Jira key, PR URL or diff] for how it calls paid providers.
For a Jira ticket, read its description, acceptance criteria and comments through the connected Jira MCP or authenticated acli; ask for pasted details if access is unavailable.
Trace each paid call from request checks to the provider call and cost accounting. Check rate limits, spend ceilings, the kill switch and alerts against the requirements on this page, using mocks or test services.
Report each bypass with evidence.
Report which of this page's tests exist in the project's test suite. Add the missing ones that can be automated, and list the rest.
Do not send real messages or incur charges.
```

## Check requests before sending

Diagram: Before an SMS send: server-verified bot check, rate limits, destination-country allowlist, kill switch and enforced spend ceiling. After sending, monitor the verify rate.

_Every check runs before the paid send; the verify rate is watched after it._

- **OTP-1** (high risk; checked by tests): **Verify a bot check on the server before public OTP sends.** A client-only check is bypassable.
- **OTP-2** (high risk; checked by tests): **Rate limits on every axis.** Per phone number, IP, device and account, plus a global ceiling per minute and per day.

  Caution: Check limits before the provider call, resends included.
- **OTP-3** (high risk; checked by tests): **Send only to countries the product serves.** Everything else is rejected before it reaches the provider.

  Caution: Take the country from the parsed phone number, not from a country picker the client controls.

* Normalize phone numbers before rate limiting. Counters must hold across concurrent workers. If a limit cannot be checked, do not send.
* Enforce the limits in the application. A load balancer or CDN limit helps, but only the application knows the phone number, account and device.
* Take the client IP only from your trusted proxy's header, never from one the client can set. Otherwise every user behind the load balancer shares one IP, and the per-IP limit blocks everyone or no one.
* Apply resend delays on the server: for example 30 seconds, 60 seconds, five minutes, then an hour's pause. Set the actual limits from expected traffic and record them in the repository.
* Give the same public response whether or not an account exists; keep the precise rejection reason in internal metrics.

## Cap spend and watch verification

- **OTP-4** (high risk; checked by the app itself and tests): **Enforce a hard spend ceiling before paid calls.** Reserve budget atomically across workers. Enable provider spend limits, alerts and fraud protection where available; an alert alone does not stop spend.

  Caution: Provider controls supplement application limits; verify which settings actually block requests.
- **OTP-6** (high risk; checked by tests): **A kill switch stops all sends without a deploy.**
- **OTP-5** (medium risk; checked by tests): **Alert on a drop in verify rate:** verified codes divided by sent codes over a consistent window. Investigate abuse, delivery failures and broken verification flows.

* A timed-out send may still be billed. Keep its budget reservation until the provider confirms the outcome; [a timeout means unknown](/value-abuse/#r-val-6).

> **Failure example (illustrative): The quiet Saturday**
>
> Weekend sends triple to destinations the product does not serve. Almost no codes are verified. The destination allowlist would have blocked those sends; a spend ceiling bounds the bill even when attackers spread requests across many numbers.

## Apply cost limits to other paid APIs

- Email, voice, maps, KYC and model calls need caller authorization or abuse checks suited to the flow, per-caller quotas, a global cost ceiling and a kill switch.
- Phone verification adds country and verify-rate controls. For other APIs, watch cost against successful business outcomes.
- Keep provider credentials on the server, and call providers as in [Jobs and external calls](/jobs/#call-external-services-safely): explicit timeouts, retries only when safe, and a shared rate limit.

## Test the limits

- [ ] **OTP-T1**: 100 requests for one number in a minute produce no more sends than the limit allows.
- [ ] **OTP-T2**: A request to a country outside the allowlist is rejected with no provider call.
- [ ] **OTP-T3**: Flipping the kill switch in staging stops sends within a minute.
- [ ] **OTP-T4**: The verify-rate alert fires on a synthetic drop.
- [ ] **OTP-T5**: Exhaust the spend budget with concurrent requests. No worker sends beyond the reserved ceiling.
- [ ] **OTP-T6**: Make the limit storage unavailable. The provider is not called.
- [ ] **OTP-T7**: Send a failed or missing bot-check token, and a direct backend request. Neither reaches the paid-send call.
