Skip to content

Set up Approve

Let users approve a sign-in or a payment on a phone they already use: enrol the phone after login, ask it from your server, and show the number to match.

ApproveUpdated 26 October 20268 min read

Approve sends a request to a phone where the user is already signed in to your app. The phone shows what is happening (“Sign in from Chrome on Windows, Pune”) and three numbers. The user picks the number shown on your login page and confirms with a fingerprint, face or screen lock. Your server gets approved in a few seconds.

Use it for sign-ins on a new computer, payments, payouts, password and email changes, and anything else you would confirm with an SMS code today.

What you need first

  • An Android or iOS app where users sign in. Approve works only on a phone that has signed in before; sign-up and a lost phone need another way in (email OTP, a passkey, or authenticator backup codes).
  • Push set up for that app, because the request reaches the phone as a push: Android (Firebase service account) and iOS (.p8 key or Firebase).
  • The SignOps phone SDK: Android (com.signops:android), iOS (SignOps on Swift Package Manager), React Native (@signops/react-native) or Flutter (signops). They are not on Maven Central, Swift Package Manager, npm or pub.dev yet. The REST calls they make are in the API reference, so you can see exactly what they do.
  • Your publishable key (pk_live_...), from Approve, Setup or API keys. It can only enrol a phone and answer approvals, so it is safe inside the app.

1. Trust the phone after your own login

Once the user has signed in to your app on their phone, your server makes a one-time enrol token for them, and the app enrols:

// Your server, for the user who just signed in
const { token } = await signops.approve.enrollToken({ user_id: "usr_8f21c4" });
// send token to the app
// Android
val signops = SignOps(context, "pk_live_...")
signops.approve.enroll(token, FirebaseMessaging.getInstance().token.await())
// iOS
let signops = SignOps(key: "pk_live_...")
try await signops.approve.enroll(token: token, pushToken: SignOps.apnsToken(from: deviceToken))   // or the FCM token

The SDK makes a P-256 key pair in the Keystore or Secure Enclave, sends only the public half, and adds the push token as one of the user’s devices. Your dashboard’s Users page now shows the phone as trusted, and your webhook gets device.trusted.

On iOS, add NSFaceIDUsageDescription to Info.plist. On Android, the activity that shows the prompt must be a FragmentActivity (AppCompatActivity is one).

2. Show the request when the push arrives

// Android: in your FirebaseMessagingService
override fun onMessageReceived(m: RemoteMessage) {
    Approve.requestId(m.data)?.let { signops.approve.open(this, it); return }   // the ready-made screen
    // ... your other pushes
}
override fun onNewToken(t: String) { scope.launch { signops.approve.updatePushToken(t) } }
// iOS (SwiftUI): the ready-made screen
.sheet(item: $approveId) { id in ApproveView(signops: signops, requestId: id.value) { _ in approveId = nil } }

Some phones hold pushes while the app sleeps, so also check when the app opens: if (signops.approve.isEnrolled && signops.approve.pending().isNotEmpty()) signops.approve.open(this). To use your own screen instead, read signops.approve.pending() and call approve(req, picked) or deny(req).

3. Ask the phone from your server

When the user signs in on the website (or starts a payment), create a request and show its match number on that page:

const req = await signops.approve.create({
  user_id: "usr_8f21c4",
  context: "Sign in from Chrome on Windows",   // shown on the phone
  city: "Pune",
});
// Show req.match ("47") on the login page: "Tap 47 on your phone"

const result = await signops.approve.wait(req.id, { timeout: 120 });
if (result.status === "approved") startSession(result.user_id);
// "denied": the user said it was not them. "expired": nobody answered in 2 minutes.

wait holds the connection for up to 120 seconds. If your server cannot hold a request that long, take the approve.approved, approve.denied and approve.expired webhooks instead, or poll GET /v1/approve/:id.

For a payment, put the amount and the payee in context, such as "Pay Rs 4,999 to Kirana Express", so the user approves exactly that.

4. Test it on your own phone

  1. Install your app on your phone with your test publishable key, sign in, and let it enrol (step 1).
  2. In the dashboard, open Approve, Setup, Try it on a trusted phone, type your user id and choose Send a test approval.
  3. The phone shows the request. Pick the number the dashboard shows and confirm with your fingerprint or face. The dashboard changes to Approved.

5. Plan the ways back in

Approve proves the user has their phone. When they do not:

  • New phone, old one gone: sign in with an email code or a passkey, then enrol the new phone. The old one can be untrusted from the Users page or with signops.approve.untrust(deviceId).
  • No trusted phone yet: create answers 404; offer email OTP or the authenticator.
  • Turn Approve off for the whole app under Approve, Settings. Requests then answer 403 not_allowed, so your login page should fall back.

To try several methods in a fixed order in one session (passkey, then Approve, then authenticator, then email), see Verify in one call on Starter and Pro.

Frequently asked questions

More questions? Email us and the answer goes on this page.

hello@signops.dev
Why does Approve need code in my app?

Each answer is signed with a key made inside the phone's Android Keystore or iOS Secure Enclave, and the key never leaves the phone. Only code in your app can make that key and ask for the fingerprint or face before signing.

What happens if the user has no trusted phone?

Creating a request answers 404 not_found with "This user has no trusted phone". Fall back to an email code, a passkey or the authenticator app.

What if the user picks the wrong number?

A wrong number counts as denied. That is the point of number matching, it stops a user approving a request they did not start just to make the pushes stop.

How many phones can a user trust?

Five. Enrolling a sixth stops the oldest one being trusted. A phone that moves to another user stops being trusted for the first one.

Is an expired request billed?

No. Only approved and denied requests are counted.

Send your first push today.

Free for 1,000 push devices, 100 approvals and 1,000 passkey sign-ins a month, and 500 email codes to try. No card until you pass a free amount.